WORKFLOW · N8N

Inbox Rail Audit

Productivity👁 4
WORKFLOW JSON
{
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "name": "Inbox Rail Audit",
  "nodes": [
    {
      "id": "manual-trigger",
      "name": "When clicking ‘Execute workflow’",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -240,
        0
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "set-window",
      "name": "Set window",
      "type": "n8n-nodes-base.set",
      "position": [
        0,
        0
      ],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "w1",
              "name": "WINDOW_AFTER",
              "type": "string",
              "value": "2025/08/01"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "gmail-paypal",
      "name": "Gmail PayPal",
      "type": "n8n-nodes-base.gmail",
      "position": [
        280,
        -720
      ],
      "parameters": {
        "simple": true,
        "filters": {
          "q": "={{ 'after:' + $('Set window').first().json.WINDOW_AFTER + ' (from:paypal.com OR from:paypal.nl OR from:[email protected]) (betaling OR receipt OR ontvangstbewijs OR \"automatische betaling\" OR payment)' }}"
        },
        "options": {
          "maxResults": 200,
          "downloadAttachments": false
        },
        "operation": "getAll"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "REPLACE",
          "name": "Gmail"
        }
      },
      "typeVersion": 2.1,
      "alwaysOutputData": true
    },
    {
      "id": "gmail-apple",
      "name": "Gmail Apple",
      "type": "n8n-nodes-base.gmail",
      "position": [
        280,
        -480
      ],
      "parameters": {
        "simple": true,
        "filters": {
          "q": "={{ 'after:' + $('Set window').first().json.WINDOW_AFTER + ' (from:apple.com OR from:email.apple.com) (invoice OR factuur OR receipt OR Apple Pay)' }}"
        },
        "options": {
          "maxResults": 200,
          "downloadAttachments": false
        },
        "operation": "getAll"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "REPLACE",
          "name": "Gmail"
        }
      },
      "typeVersion": 2.1,
      "alwaysOutputData": true
    },
    {
      "id": "gmail-play",
      "name": "Gmail Google Play",
      "type": "n8n-nodes-base.gmail",
      "position": [
        280,
        -240
      ],
      "parameters": {
        "simple": true,
        "filters": {
          "q": "={{ 'after:' + $('Set window').first().json.WINDOW_AFTER + ' (from:googleplay OR from:google.com OR subject:\"Google Play\") (subscription OR abonnement OR \"Google LLC\" OR receipt)' }}"
        },
        "options": {
          "maxResults": 200,
          "downloadAttachments": false
        },
        "operation": "getAll"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "REPLACE",
          "name": "Gmail"
        }
      },
      "typeVersion": 2.1,
      "alwaysOutputData": true
    },
    {
      "id": "gmail-gpay",
      "name": "Gmail Google Pay",
      "type": "n8n-nodes-base.gmail",
      "position": [
        280,
        0
      ],
      "parameters": {
        "simple": true,
        "filters": {
          "q": "={{ 'after:' + $('Set window').first().json.WINDOW_AFTER + ' (\"Google Pay\" OR GPay OR \"kaart toegevoegd\" OR \"card added\")' }}"
        },
        "options": {
          "maxResults": 200,
          "downloadAttachments": false
        },
        "operation": "getAll"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "REPLACE",
          "name": "Gmail"
        }
      },
      "typeVersion": 2.1,
      "alwaysOutputData": true
    },
    {
      "id": "gmail-stripe",
      "name": "Gmail Stripe/Paddle",
      "type": "n8n-nodes-base.gmail",
      "position": [
        280,
        240
      ],
      "parameters": {
        "simple": true,
        "filters": {
          "q": "={{ 'after:' + $('Set window').first().json.WINDOW_AFTER + ' (from:stripe.com OR from:paddle.com OR from:lemonsqueezy.com) (receipt OR invoice OR payment)' }}"
        },
        "options": {
          "maxResults": 200,
          "downloadAttachments": false
        },
        "operation": "getAll"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "REPLACE",
          "name": "Gmail"
        }
      },
      "typeVersion": 2.1,
      "alwaysOutputData": true
    },
    {
      "id": "gmail-sepa",
      "name": "Gmail SEPA",
      "type": "n8n-nodes-base.gmail",
      "position": [
        280,
        480
      ],
      "parameters": {
        "simple": true,
        "filters": {
          "q": "={{ 'after:' + $('Set window').first().json.WINDOW_AFTER + ' (incasso OR \"automatische incasso\" OR sepa OR machtiging OR \"direct debit\")' }}"
        },
        "options": {
          "maxResults": 200,
          "downloadAttachments": false
        },
        "operation": "getAll"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "REPLACE",
          "name": "Gmail"
        }
      },
      "typeVersion": 2.1,
      "alwaysOutputData": true
    },
    {
      "id": "gmail-klarna",
      "name": "Gmail Klarna",
      "type": "n8n-nodes-base.gmail",
      "position": [
        280,
        720
      ],
      "parameters": {
        "simple": true,
        "filters": {
          "q": "={{ 'after:' + $('Set window').first().json.WINDOW_AFTER + ' (from:klarna OR from:afterpay) -unsubscribe -nieuwsbrief' }}"
        },
        "options": {
          "maxResults": 200,
          "downloadAttachments": false
        },
        "operation": "getAll"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "REPLACE",
          "name": "Gmail"
        }
      },
      "typeVersion": 2.1,
      "alwaysOutputData": true
    },
    {
      "id": "merge-rails",
      "name": "Wait for all rails",
      "type": "n8n-nodes-base.merge",
      "position": [
        560,
        0
      ],
      "parameters": {
        "numberInputs": 7
      },
      "typeVersion": 3.1
    },
    {
      "id": "classify",
      "name": "Classify rails",
      "type": "n8n-nodes-base.code",
      "position": [
        800,
        0
      ],
      "parameters": {
        "jsCode": "function last4(text) {\n  const s = String(text || '');\n  const m = s.match(/(?:ending in|eindigend op|card ending|xxxx|••••)\\s*(\\d{4})/i);\n  if (m) return m[1];\n  return '';\n}\nfunction amountOf(text) {\n  const s = String(text || '');\n  const m = s.match(/\\u20ac\\s*[\\d.,]+|\\$\\s*[\\d.,]+|[\\d.,]+\\s*EUR|[\\d.,]+\\s*USD/i);\n  return m ? m[0] : '';\n}\nfunction currencyOf(amount) {\n  const a = String(amount || '');\n  if (a.indexOf('\\u20ac') !== -1 || /EUR/i.test(a)) return 'EUR';\n  if (a.indexOf('$') !== -1 || /USD/i.test(a)) return 'USD';\n  return '';\n}\nfunction isCardAdded(text) {\n  return /card added|kaart toegevoegd/i.test(String(text || ''));\n}\nfunction isEmptyMsg(j) {\n  if (!j) return true;\n  return !(j.snippet || j.text || j.textPlain || j.subject || j.from);\n}\nfunction row(j, rail) {\n  const snippet = j.snippet || j.text || j.textPlain || '';\n  const subject = j.subject || '';\n  const blob = (subject + ' ' + snippet);\n  const date = j.date || j.internalDate || '';\n  const from = j.from || '';\n  const amt = amountOf(blob);\n  const added = isCardAdded(blob);\n  let evidence = amt ? 'VERIFIED' : 'UNCERTAIN';\n  let note = '';\n  if (added && !amt) {\n    evidence = 'UNCERTAIN';\n    note = 'Card added / kaart toegevoegd is not a charge';\n  }\n  if (rail === 'Stripe/Paddle' && /onboard|connect your|payout/i.test(blob) && !amt) {\n    note = 'Possible seller onboarding, not spend';\n  }\n  return {\n    rail: rail,\n    merchant: String(subject).slice(0, 120),\n    amount: amt,\n    currency: currencyOf(amt),\n    cadence: '',\n    last_charge: date,\n    last4: last4(blob),\n    evidence: evidence,\n    note: note,\n    from: from,\n    subject: subject,\n    snippet: String(snippet).slice(0, 240)\n  };\n}\nconst rails = [\n  ['Gmail PayPal', 'PayPal'],\n  ['Gmail Apple', 'Apple'],\n  ['Gmail Google Play', 'Google Play'],\n  ['Gmail Google Pay', 'Google Pay'],\n  ['Gmail Stripe/Paddle', 'Stripe/Paddle'],\n  ['Gmail SEPA', 'SEPA'],\n  ['Gmail Klarna', 'Klarna']\n];\nconst out = [];\nfor (let r = 0; r < rails.length; r++) {\n  const name = rails[r][0];\n  const rail = rails[r][1];\n  try {\n    const items = $(name).all();\n    for (let i = 0; i < items.length; i++) {\n      if (isEmptyMsg(items[i].json)) continue;\n      out.push(row(items[i].json, rail));\n    }\n  } catch (e) {}\n}\nreturn out.map(function (json) { return { json: json }; });\n"
      },
      "typeVersion": 2
    },
    {
      "id": "sheet",
      "name": "Append sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1040,
        0
      ],
      "parameters": {
        "columns": {
          "value": {},
          "mappingMode": "autoMapInputData"
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "rails"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "REPLACE_SHEET_ID"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE",
          "name": "Google Sheets"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "note-main",
      "name": "Sticky Note — main",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -260,
        -680
      ],
      "parameters": {
        "color": 1,
        "width": 460,
        "height": 620,
        "content": "## Inbox Rail Audit (free Hub workflow)\n\n**Who it is for:** solopreneurs with PayPal + Apple + Play + Stripe (and NL SEPA/Klarna) on one Gmail.\n\n**What it does:** one pass over ~13 months of Gmail *receipt metadata*. Groups by **payment rail**, not merchant dump. Writes a Google Sheet. Does not cancel anything, does not download PDFs, does not send mail, does not write labels.\n\n**How it works**\n1. Set `WINDOW_AFTER` (default `2025/08/01`).\n2. Seven Gmail searches run in parallel (cap 200 each). `downloadAttachments` is false.\n3. Merge waits for every rail, including empty inboxes.\n4. Code classifies rail / amount / last4 / VERIFIED vs UNCERTAIN. \"Card added\" is not a charge. Stripe seller onboarding is flagged.\n5. Rows append to sheet tab `rails`.\n\n**Setup**\n- Attach your Gmail OAuth and Google Sheets OAuth (placeholders are REPLACE).\n- Create a Sheet, tab name `rails`.\n- Paste the Sheet ID into **Append sheet**.\n- Execute once. Read `evidence` before you cancel.\n\n**Limits:** household SEPA (rent, health, telco) is often UNAVAILABLE in Gmail — that is a blind spot, not \"you have no rent\". Do not convert EUR/USD without a rate in the mail.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "note-rails",
      "name": "Sticky Note — rails",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        240,
        -800
      ],
      "parameters": {
        "color": 5,
        "width": 300,
        "height": 1680,
        "content": "## Rail searches (Gmail, read-only)\n\nCap 200 threads each. No `has:attachment`. Metadata + snippet only.\n\n1. PayPal\n2. Apple / Apple Pay\n3. Google Play\n4. Google Pay (card-added is not spend)\n5. Stripe / Paddle / Lemon\n6. SEPA / incasso\n7. Klarna / Afterpay\n"
      },
      "typeVersion": 1
    },
    {
      "id": "note-setup",
      "name": "Sticky Note — setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        540,
        280
      ],
      "parameters": {
        "color": 4,
        "width": 360,
        "height": 320,
        "content": "## Before you run\n\n1. Credentials: Gmail + Google Sheets. IDs stay REPLACE until you attach your own.\n2. `WINDOW_AFTER` = `YYYY/MM/DD` (13-month window).\n3. Sheet tab = `rails`. Document ID = your Sheet.\n4. Keep **downloadAttachments = false**.\n5. Human still cancels. This map is not a mandate list.\n\nEmpty rails still continue (always output data).\n"
      },
      "typeVersion": 1
    },
    {
      "id": "note-classify",
      "name": "Sticky Note — classify",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        780,
        -320
      ],
      "parameters": {
        "color": 6,
        "width": 320,
        "height": 280,
        "content": "## Classify + sheet\n\nOutput columns: rail, merchant (subject), amount, currency, last_charge, last4, evidence, note, from, snippet.\n\nEvidence: VERIFIED (amount in snippet) or UNCERTAIN.\n\nCadence / grouping by merchant is a second human pass. Do not treat every food order as recurring.\n"
      },
      "typeVersion": 1
    }
  ],
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "connections": {
    "Gmail SEPA": {
      "main": [
        [
          {
            "node": "Wait for all rails",
            "type": "main",
            "index": 5
          }
        ]
      ]
    },
    "Set window": {
      "main": [
        [
          {
            "node": "Gmail PayPal",
            "type": "main",
            "index": 0
          },
          {
            "node": "Gmail Apple",
            "type": "main",
            "index": 0
          },
          {
            "node": "Gmail Google Play",
            "type": "main",
            "index": 0
          },
          {
            "node": "Gmail Google Pay",
            "type": "main",
            "index": 0
          },
          {
            "node": "Gmail Stripe/Paddle",
            "type": "main",
            "index": 0
          },
          {
            "node": "Gmail SEPA",
            "type": "main",
            "index": 0
          },
          {
            "node": "Gmail Klarna",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail Apple": {
      "main": [
        [
          {
            "node": "Wait for all rails",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Gmail Klarna": {
      "main": [
        [
          {
            "node": "Wait for all rails",
            "type": "main",
            "index": 6
          }
        ]
      ]
    },
    "Gmail PayPal": {
      "main": [
        [
          {
            "node": "Wait for all rails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify rails": {
      "main": [
        [
          {
            "node": "Append sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail Google Pay": {
      "main": [
        [
          {
            "node": "Wait for all rails",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "Gmail Google Play": {
      "main": [
        [
          {
            "node": "Wait for all rails",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Wait for all rails": {
      "main": [
        [
          {
            "node": "Classify rails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail Stripe/Paddle": {
      "main": [
        [
          {
            "node": "Wait for all rails",
            "type": "main",
            "index": 4
          }
        ]
      ]
    },
    "When clicking ‘Execute workflow’": {
      "main": [
        [
          {
            "node": "Set window",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
source →

RELATED WORKFLOWS

COMMENTS

or sign in to comment under your account

No comments yet — be the first.