Skip to content

Instantly share code, notes, and snippets.

@alex-phillips
Created December 3, 2025 13:02
Show Gist options
  • Select an option

  • Save alex-phillips/deb74af8832c05fc3239e48ccf6d7630 to your computer and use it in GitHub Desktop.

Select an option

Save alex-phillips/deb74af8832c05fc3239e48ccf6d7630 to your computer and use it in GitHub Desktop.
{
"name": "Transactions to YNAB",
"nodes": [
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const body = $json[\"text\"]\n\nconst amount = body.match(/\\$(\\d+\\.\\d+)/)[1]\nconst payee = body.match(/Where:\\s+(.+?)[\\n|$]/)[1]\n\nreturn {\n data: {\n bank: \"BoA Credit\",\n amount,\n payee, \n }\n}"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
272,
560
],
"id": "0494e970-35ec-40d7-9b5e-c9a186cb2fbc",
"name": "Parse BoA Trx"
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const body = $json[\"text\"]\n\nconst parsed = body.match(/these\\s+large\\s+purchase\\s+notifications\\s+online\\.\\s+([^\\n]+)\\s+\\$(\\d+\\.\\d+)/m)\nconst payee = parsed[1]\nconst amount = parsed[2]\n\nreturn {\n data: {\n bank: \"Amex Blue\",\n amount,\n payee,\n }\n}"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
272,
752
],
"id": "d6947a69-0527-4a30-a7c1-dac53d162b33",
"name": "Parse Amex Trx"
},
{
"parameters": {
"url": "https://api.ynab.com/v1/budgets/YNAB_BUDGET_ID/payees",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "httpBearerAuth",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
272,
176
],
"id": "2616aa6c-08c0-4132-869d-cf60cea1ffb9",
"name": "YNAB Payees",
"executeOnce": true
},
{
"parameters": {
"url": "https://api.ynab.com/v1/budgets/YNAB_BUDGET_ID/accounts",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "httpBearerAuth",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
272,
368
],
"id": "9305e209-eb83-45ca-86e2-61fecd39cd4b",
"name": "YNAB Accounts",
"executeOnce": true
},
{
"parameters": {
"numberInputs": 3
},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [
496,
640
],
"id": "5d003e38-dc93-4001-87ff-cff45903a7fd",
"name": "Merge"
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "3d7c2894-cb6b-41d0-b496-4f0746ec1aea",
"leftValue": "={{ $json.subject }}",
"rightValue": "BoA Alert Subject",
"operator": {
"type": "string",
"operation": "contains"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "BoA"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "896bab85-f204-414b-a754-c70525777059",
"leftValue": "={{ $json.subject }}",
"rightValue": "=Amex Alert Subject",
"operator": {
"type": "string",
"operation": "contains"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Amex"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "10300c45-209c-4643-93a0-ae7b474a062e",
"leftValue": "={{ $json.subject }}",
"rightValue": "Citi Alert Subject",
"operator": {
"type": "string",
"operation": "contains"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Citi"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "5d8ab884-97a4-4b1c-8e16-03c35a994b4d",
"leftValue": "={{ $json.subject }}",
"rightValue": "Transaction Alert",
"operator": {
"type": "string",
"operation": "contains"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Discover"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.3,
"position": [
-176,
720
],
"id": "1a33496a-897d-41ee-ae18-b0e7cd761be1",
"name": "Switch"
},
{
"parameters": {
"jsCode": "const input = $input.all()\nconst payees = input[0].json.data.payees\nconst accounts = input[1].json.data.accounts\nconst amount = input[2].json.data.amount\nconst payee = input[2].json.data.payee\nconst bank = input[2].json.data.bank\n\nlet account_id = null\nfor (const account of accounts) {\n if (account.name == bank) {\n account_id = account.id\n }\n}\n\nreturn {\n account_id,\n amount: amount, // doesn't account for credits\n payee: payee.replace(\"*\", \"\"),\n bank,\n}"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
720,
656
],
"id": "5d845b17-2aa3-49f2-adc4-8958233814a1",
"name": "Code in JavaScript"
},
{
"parameters": {
"method": "POST",
"url": "https://api.ynab.com/v1/budgets/YNAB_BUDGET_ID/transactions",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "httpBearerAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"transaction\": {\n \"account_id\": \"{{ $json.account_id }}\",\n\"date\": \"{{ $now.format('yyyy-LL-dd') }}\",\n\"amount\": {{ $json.amount }},\n\"payee_name\": \"{{ $json.payee }}\",\n\"memo\": \"{{ $json.memo }}\"\n} \n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
1616,
656
],
"id": "3be8075f-5e54-48e0-8957-4a1217d42316",
"name": "HTTP Request"
},
{
"parameters": {
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"simple": false,
"filters": {},
"options": {}
},
"type": "n8n-nodes-base.gmailTrigger",
"typeVersion": 1.3,
"position": [
-400,
752
],
"id": "1a1a4fe8-93d1-4176-8207-a3f751ba985f",
"name": "Gmail Trigger"
},
{
"parameters": {
"operation": "sendAndWait",
"chatId": "TELEGRAM_CHAT_ID",
"message": "=New {{ $json.bank }} transaction\nPayee: {{ $json.payee }}\nAmount: {{ $json.amount }}\n\nPlease reply with the payee name to auto-add the transaction. Optionally include a memo. Will timeout in 5 minutes.",
"responseType": "customForm",
"formFields": {
"values": [
{
"fieldLabel": "Payee",
"placeholder": "={{ $json.payee }}"
},
{
"fieldLabel": "Amount",
"placeholder": "={{ $json.amount }}"
},
{
"fieldLabel": "Memo"
}
]
},
"options": {
"limitWaitTime": {
"values": {
"resumeAmount": 45,
"resumeUnit": "minutes"
}
}
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
944,
592
],
"id": "bbc809db-896b-480b-9231-49c6cd6c8fa0",
"name": "Send Message",
"webhookId": "REMOVED",
"alwaysOutputData": false
},
{
"parameters": {},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [
1168,
656
],
"id": "97199a62-71a3-4722-9372-35d5bd51a44a",
"name": "Merge1"
},
{
"parameters": {
"jsCode": "const input = $input.all()\n\nif (!input[0].json.data) {\n input[0].json.data = {\n Payee: input[1].json.payee,\n Amount: input[1].json.amount,\n Memo: \"\",\n }\n} else {\n input[0].json.data = {\n Payee: input[0].json.data.Payee || input[1].json.payee,\n Amount: input[0].json.data.Amount || input[1].json.amount,\n Memo: input[0].json.data.Memo,\n }\n}\n\nreturn {\n account_id: input[1].json.account_id,\n payee: input[0].json.data.Payee,\n amount: parseFloat(input[0].json.data.Amount) * -1000,\n memo: input[0].json.data.Memo,\n}"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1392,
656
],
"id": "f9f9dc90-f090-49b1-8bcf-677525fcf7e2",
"name": "Code in JavaScript1"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "b9cdf27c-b8a7-4fa6-8650-5c84a3ece028",
"leftValue": "1",
"rightValue": "1",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
48,
272
],
"id": "6faec602-d7a8-45be-b8a2-ca99572ab1e7",
"name": "Junction"
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "function extractMerchant(html) {\n // Regex explanation:\n // 1. Find the exact <span>Merchant</span>\n // 2. Match any characters (including newlines)\n // 3. Capture the NEXT <span>...</span>\n const regex = /<span[^>]*>\\s*Merchant\\s*<\\/span>[\\s\\S]*?<span[^>]*>(.*?)<\\/span>/i;\n\n const match = regex.exec(html);\n if (!match) return null;\n\n return match[1].trim();\n}\n\nconst body = $json[\"html\"]\n\nconst amount = body.match(/Amount:\\s+\\$(\\d+\\.\\d+)/)[1]\nconst payee = extractMerchant(body)\n\nreturn {\n data: {\n bank: \"Citi Costco Visa\",\n amount,\n payee, \n }\n}"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
272,
944
],
"id": "2f162499-f1a9-4e58-af13-434dcb59186d",
"name": "Parse Citi"
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const body = $json[\"html\"]\n\nconst payee = body.match(/Merchant:\\s+([^<]+)/)[1]\nconst amount = body.match(/Amount:\\s+\\$(\\d+\\.\\d+)/)[1]\n\nreturn {\n data: {\n bank: \"Discover\",\n amount,\n payee, \n }\n}"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
272,
1136
],
"id": "6737f836-cde2-41a5-a0c2-3cabed6b6cf6",
"name": "Parse Discover"
}
],
"connections": {
"Parse BoA Trx": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 2
}
]
]
},
"Parse Amex Trx": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 2
}
]
]
},
"YNAB Payees": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"YNAB Accounts": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"Merge": {
"main": [
[
{
"node": "Code in JavaScript",
"type": "main",
"index": 0
}
]
]
},
"Switch": {
"main": [
[
{
"node": "Parse BoA Trx",
"type": "main",
"index": 0
},
{
"node": "Junction",
"type": "main",
"index": 0
}
],
[
{
"node": "Parse Amex Trx",
"type": "main",
"index": 0
},
{
"node": "Junction",
"type": "main",
"index": 0
}
],
[
{
"node": "Parse Citi",
"type": "main",
"index": 0
},
{
"node": "Junction",
"type": "main",
"index": 0
}
],
[
{
"node": "Junction",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript": {
"main": [
[
{
"node": "Send Message",
"type": "main",
"index": 0
},
{
"node": "Merge1",
"type": "main",
"index": 1
}
]
]
},
"Gmail Trigger": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
},
"Send Message": {
"main": [
[
{
"node": "Merge1",
"type": "main",
"index": 0
}
]
]
},
"Merge1": {
"main": [
[
{
"node": "Code in JavaScript1",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript1": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"Junction": {
"main": [
[
{
"node": "YNAB Payees",
"type": "main",
"index": 0
},
{
"node": "YNAB Accounts",
"type": "main",
"index": 0
}
]
]
},
"Parse Citi": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 2
}
]
]
},
"Parse Discover": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 2
}
]
]
}
},
"active": true,
"settings": {
"executionOrder": "v1"
},
"versionId": "56a19910-8b7d-40e8-a4bf-abe7bb9c5611",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "f3a8e78c3a1b243e336773b7f26ea7b990b57cf6ae74b2f0e20ac9dec41dbfb6"
},
"id": "6Xeu0RbaMJVl4ihM",
"tags": []
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment