This workflow corresponds to n8n.io template #8794 — we link there as the canonical source.
This workflow follows the Agent → HTTP Request recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "wnEDtKnbAvH6Nwzl",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "stripe-failed-payment-recovery-ai-emails-by-flycode",
"tags": [],
"nodes": [
{
"id": "d5c159bf-cf8b-4247-b8c2-e2ec52325c8c",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
0,
0
],
"parameters": {
"path": "7c277748-d5fe-46f1-a33b-771acf6a7fe5",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2.1
},
{
"id": "3736d60f-82a0-4012-b703-22e895abcbac",
"name": "Code in JavaScript",
"type": "n8n-nodes-base.code",
"position": [
416,
0
],
"parameters": {
"jsCode": "// Process all incoming items from Webhook (Run Once mode)\nconst items = $input.all();\n\nreturn items.map(({ json }) => {\n const obj = json?.body?.data?.object ?? {};\n\n // Extract and split name on the FIRST space\n const fullName = String(obj.customer_name ?? \"\").trim().replace(/\\s+/g, \" \");\n const spaceIdx = fullName.indexOf(\" \");\n const firstName = spaceIdx === -1 ? fullName : fullName.slice(0, spaceIdx);\n const lastName = spaceIdx === -1 ? \"\" : fullName.slice(spaceIdx + 1);\n\n // Other fields\n const currency = obj.currency ?? \"\";\n const customer_email = obj.customer_email ?? \"\";\n const hosted_invoice_url = obj.hosted_invoice_url ?? \"\";\n const invoice_number = obj.number ?? \"\";\n const invoice_amount = obj.total != null ? obj.total / 100 : null; // cents -> float\n const type = json?.body?.type ?? \"\";\n const billing_reason = obj.billing_reason ?? \"\";\n const collection = obj.collection_method ?? \"\";\n const account_name = obj.account_name ?? \"\";\n\n // Description: prefer top-level; fallback to first line item\n const lineItemDesc =\n Array.isArray(obj?.lines?.data) && obj.lines.data.length > 0\n ? obj.lines.data[0]?.description ?? \"\"\n : \"\";\n\n const description = obj.description ?? lineItemDesc ?? \"\";\n\n return {\n json: {\n firstName,\n lastName,\n currency,\n customer_email,\n hosted_invoice_url,\n invoice_number,\n invoice_amount,\n type,\n billing_reason,\n collection,\n description,\n account_name,\n },\n };\n});"
},
"typeVersion": 2
},
{
"id": "aa432ab0-badb-4f64-a2c0-e8f6d64dc207",
"name": "If",
"type": "n8n-nodes-base.if",
"position": [
672,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "4b276567-deec-472c-b5a9-f3c9ff36a627",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.type }}",
"rightValue": "invoice.payment_failed"
},
{
"id": "47631a70-18f9-4c67-a096-3f90b25c99c6",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.billing_reason }}",
"rightValue": "subscription_cycle"
},
{
"id": "9d8725bd-25d0-42bf-91e6-b093ace80808",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.collection }}",
"rightValue": "charge_automatically"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "4356bb04-49c4-4343-a2dd-f48dc2f87669",
"name": "AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
1024,
-128
],
"parameters": {
"text": "=You are writing a short, friendly, and urgent FAILED PAYMENT email.\n\nVARIABLES (may be missing):\n- firstName: {{ $json.firstName }}\n- lastName: {{ $json.lastName }}\n- account_name: {{ $json.account_name }}\n- customer_email: {{ $json.customer_email }}\n- hosted_invoice_url: {{ $json.hosted_invoice_url }}\n- invoice_number: {{ $json.invoice_number }}\n- invoice_amount: {{ $json.invoice_amount }}\n- currency: {{ $json.currency }}\n- description: {{ $json.description }}\n- type: {{ $json.type }}\n- billing_reason: {{ $json.billing_reason }}\n- collection: {{ $json.collection }}\n\nGOAL:\nPolitely but clearly convey urgency to pay the failed invoice to avoid interruption/cancellation.\n\nSUBSCRIPTION NAME:\n- If \"description\" exists, extract the plan name by removing any leading \"<qty> \u00d7 \" and taking the text before the first \"(\"; trim.\n Example: \"1 \u00d7 daily_test_2 (at $2.00 / day)\" -> \"daily_test_2\".\n- Otherwise use \"{{ $json.account_name }} subscription\".\n\nAMOUNT FORMATTING:\n- Two decimals.\n- Currency symbol map: usd->$, eur->\u20ac, gbp->\u00a3; otherwise render as \"<amount> <UPPERCASE CODE>\" (e.g., 12.00 AUD).\n\nEMAIL BODY REQUIREMENTS (HTML):\n- Produce valid, minimal HTML **snippet** (no <html> or <body> tags). Use ASCII only (no smart quotes).\n- Structure:\n - Use <p> for paragraphs; use <br> only for small line breaks inside a paragraph (e.g., in the signature).\n - Include: greeting, the subscription/plan name, the invoice number, the formatted amount due, and the consequence of non-payment (interruption/cancellation).\n - Include ONE clear call-to-action as a **hyperlink button** to {{ $json.hosted_invoice_url }}:\n <a href=\"{{ $json.hosted_invoice_url }}\" target=\"_blank\" rel=\"noopener\"\n style=\"display:inline-block;padding:10px 16px;text-decoration:none;border-radius:6px;border:1px solid #222;\">\n Pay invoice\n </a>\n Do not paste the raw URL in the body; use only the hyperlink above.\n - Close with a polite reassurance and a **signature block** like:\n <p>Thank you,<br>{{ $json.account_name }} Billing Team</p>\n If account_name is missing, render \"Billing Team\" only.\n- Tone: polite, reassuring, action-oriented, concise (about 120\u2013180 words).\n\nFALLBACKS:\n- If any variable is missing, use an empty string.\n- If the link is missing, set href=\"#\" but still render the button.\n\nOUTPUT FORMAT (STRICT):\n- Output ONE valid JSON object ONLY (no code fences, no markdown, no extra keys, no arrays).\n- Start with \"{\" and end with \"}\".\n- EXACT keys with string values:\n {\n \"to_email\": \"...\",\n \"email_subject\": \"...\",\n \"email_body\": \"...\" // HTML string; do not escape tags; normal JSON string quoting is OK\n }\n- \"to_email\" MUST be {{ $json.customer_email }} if present, else \"\".\n- Subject pattern: \"Action required: Payment failed for <subscription or account_name> (Invoice <invoice_number>)\".\n- Greeting rule: \"Hi {{ $json.firstName }},\" else \"Hi there,\".",
"options": {
"systemMessage": "You are the company\u2019s Billing & Payments Manager. Write a warm, personal one-to-one email to the customer about their field subscription invoice."
},
"promptType": "define"
},
"typeVersion": 2.2
},
{
"id": "154b2939-f614-4023-9d4b-16e3b47bd525",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
1024,
64
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini"
},
"options": {
"responseFormat": "json_object"
}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "f053f493-2898-41d4-bec3-b3fe21bc53a6",
"name": "HTTP Request1",
"type": "n8n-nodes-base.httpRequest",
"position": [
1584,
-128
],
"parameters": {
"url": "https://api.postmarkapp.com/email",
"method": "POST",
"options": {},
"sendBody": true,
"sendHeaders": true,
"authentication": "predefinedCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "From",
"value": "{{POSTMARK_FROM_EMAIL}}"
},
{
"name": "To",
"value": "={{ $json.to_email }}"
},
{
"name": "Subject",
"value": "={{ $json.email_subject }}"
},
{
"name": "HtmlBody",
"value": "={{ $json.email_body }}"
},
{
"name": "MessageStream",
"value": "n8n_demo"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/json"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"nodeCredentialType": "postmarkApi"
},
"credentials": {
"postmarkApi": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "6dea1a66-716b-4420-a8b0-6ac8f8a25ce4",
"name": "Code in JavaScript1",
"type": "n8n-nodes-base.code",
"position": [
1376,
-128
],
"parameters": {
"jsCode": "// Use this if you only ever have one incoming item.\nfunction extractJsonString(s) {\n if (typeof s !== 'string') return '';\n let t = s.trim();\n t = t.replace(/^```(?:json)?\\n?/, '').replace(/```$/, '').trim();\n if (t.startsWith('{') && t.endsWith('}')) return t;\n const m = t.match(/{[\\s\\S]*}/);\n return m ? m[0] : '';\n}\n\nconst raw = $json?.text ?? $json?.output ?? '';\nconst jsonStr = extractJsonString(raw);\nif (!jsonStr) {\n throw new Error('Could not find a JSON object in the model response.');\n}\nconst obj = JSON.parse(jsonStr);\nreturn [{ json: obj }];\n"
},
"typeVersion": 2
},
{
"id": "810cbecc-938d-4635-b73b-0c4624a14503",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
336,
-400
],
"parameters": {
"width": 304,
"height": 368,
"content": "## Code in JavaScript1\nextracts fields from `body.data.object` and maps:\n- firstName\n- lastName (split on first space)\n- currency\n- customer_email,\n- hosted_invoice_url\n- invoice_number\n- invoice_amount (divide total by 100)\n- type\n- billing_reason\n- collection\n- description (falls back to first line item)\n- account_name"
},
"typeVersion": 1
},
{
"id": "a992aba3-badc-4f9a-8dcd-acd159d99934",
"name": "Filter",
"type": "n8n-nodes-base.filter",
"position": [
208,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "ee074e73-33fe-4291-baa5-7bf601bfa74f",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.body.data.object.object }}",
"rightValue": "invoice"
},
{
"id": "59ae193e-bc21-4e4a-a2ca-f1e332652cf5",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.body.object }}",
"rightValue": "event"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "6b6bef23-3e45-4f81-8e76-9c7d98354687",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
144,
128
],
"parameters": {
"height": 176,
"content": "## Filter non-stripe webhooks\nIf you might receive non-Stripe posts on the Webhook, add a quick guard in the first Code node to bail if."
},
"typeVersion": 1
},
{
"id": "0e2b8c1e-c4f3-4a35-b8f4-b6272981fa83",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
576,
128
],
"parameters": {
"width": 288,
"height": 176,
"content": "## Filter non recurring subscription invoices \nIf \u2014 guards the flow so you only email on the exact scenario. And ensures one-off invoices or manual-collection accounts don\u2019t get this email."
},
"typeVersion": 1
},
{
"id": "fd8c7a80-ea7f-4f3f-9e69-9f70f6637c22",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-96,
-192
],
"parameters": {
"width": 288,
"height": 176,
"content": "## Webhook\nReceives Stripe events.\n\n*Path is currently a mock UUID; Don't forget to replace it with the path generated by n8n.*"
},
"typeVersion": 1
},
{
"id": "32d67c3f-e485-4bac-93a4-3425d9f3891b",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1280,
32
],
"parameters": {
"width": 288,
"height": 144,
"content": "## Code in JavaScript2\nParses the model\u2019s JSON string returned by AI Agent to a real JSON object (`to_email`, `email_subject`, `email_body`)."
},
"typeVersion": 1
},
{
"id": "d07fa2e0-ba3e-477c-92c0-e7b8ab20dba3",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
944,
-272
],
"parameters": {
"width": 384,
"height": 128,
"content": "## AI Agent + OpenAI Chat Model\nGenerates the email JSON from Stripe invoice data using the prompt. Returns a single JSON object with keys: `to_email`, `email_subject`, `email_body`.\n\n*Don't forget replace the sender (From) and attach your own Postmark credentials at import time.*"
},
"typeVersion": 1
},
{
"id": "0ac3ec94-8cad-4c6a-814f-ebf1b70ffb23",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
1456,
-320
],
"parameters": {
"width": 368,
"height": 176,
"content": "## HTTP Request to POSTMARK\nSends the email using, `From`, `To`, `Subject`, `HtmlBody`, and `MessageStream`. \n\n*Don't forget replace the sender (From) and attach your own Postmark credentials at import time.*"
},
"typeVersion": 1
}
],
"active": true,
"settings": {
"executionOrder": "v1"
},
"versionId": "749b1732-6fdd-4a5f-927c-46a8dd628b1a",
"connections": {
"If": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
],
[]
]
},
"Filter": {
"main": [
[
{
"node": "Code in JavaScript",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "Filter",
"type": "main",
"index": 0
}
]
]
},
"AI Agent": {
"main": [
[
{
"node": "Code in JavaScript1",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Code in JavaScript": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript1": {
"main": [
[
{
"node": "HTTP Request1",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
openAiApipostmarkApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Recover failed subscription payments automatically with Stripe, Postmark, and AI.
Source: https://n8n.io/workflows/8794/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
⏺ 🚀 How it works
L&D_AgentsAI_ATIVO. Uses httpRequest, agent, googleCalendarTool, toolSerpApi. Webhook trigger; 93 nodes.
CLINICAINTEGRAL_secretary. Uses postgres, mcpClientTool, googleDriveTool, toolWorkflow. Webhook trigger; 89 nodes.
Remi 1.1. Uses lmChatOpenAi, memoryPostgresChat, openAi, postgres. Webhook trigger; 89 nodes.
This n8n workflow orchestrates a powerful suite of AI Agents and automations to manage and optimize various aspects of an e-commerce operation, particularly for platforms like Shopify. It leverages La