Talki Academy
Automationn8n32 min read

Email Automation with n8n: 10 Ready-to-Deploy Templates

Email is still the highest-ROI channel for customer communication — but manual sending doesn't scale. This guide gives you 10 production-ready n8n workflows covering welcome sequences, payment reminders, weekly digests, approval flows, and more. Each template includes the full n8n JSON configuration, node-by-node setup, expected outcomes, and a real cost estimate. Self-host n8n and run all 10 for under EUR 20/month.

By Talki Academy·Updated April 27, 2026

Why n8n for Email Automation?

Most email automation tools (Mailchimp, ActiveCampaign, HubSpot) charge per contact and lock you into their platform. n8n takes a different approach: it's open-source, self-hosted, and treats email as one node in a larger workflow — sitting alongside Stripe, CRMs, spreadsheets, and AI APIs.

This matters when your email logic depends on external events: a payment succeeds, a support ticket is closed, a form is submitted, a database row changes. n8n handles these triggers natively. No Zapier glue. No data copies between platforms.

PlatformMonthly cost (10k emails)Self-hosted?AI personalization?
n8n + SES~EUR 15✅ Yes✅ Via Claude API
Mailchimp~$100❌ No⚠️ Limited
ActiveCampaign~$149❌ No⚠️ Basic
HubSpot Marketing~$800❌ No✅ Built-in
Zapier + Mailgun~$50❌ No⚠️ Via OpenAI step

Prerequisites: n8n Setup in 5 Minutes

If you don't have n8n running yet, here's the fastest setup path. Self-hosting on a EUR 5/month VPS (Hetzner, DigitalOcean) is recommended for production.

# docker-compose.yml — paste and run version: "3.8" services: n8n: image: n8nio/n8n:latest restart: always ports: - "5678:5678" environment: - N8N_BASIC_AUTH_ACTIVE=true - N8N_BASIC_AUTH_USER=admin - N8N_BASIC_AUTH_PASSWORD=change_this_password - WEBHOOK_URL=https://your-domain.com - N8N_ENCRYPTION_KEY=your_32_char_key_here volumes: - n8n_data:/home/node/.n8n volumes: n8n_data: # Start: # docker compose up -d # Access: http://localhost:5678 (or your domain)

For email delivery, you'll need credentials from one of:

  • Amazon SES: $1/1,000 emails — best for high volume. Verify your domain, create SMTP credentials, and add them to n8n's Send Email node.
  • SendGrid: Free up to 100/day, then $19.95/month for 50k — good deliverability, easy API. Use the n8n SendGrid node.
  • Gmail OAuth2: Free, up to 2,000 emails/day — fine for internal automation. Set up Google Cloud OAuth2 credentials in n8n.
  • Mailgun: Pay-as-you-go, $0.80/1,000 emails — reliable for transactional. Use the HTTP Request node with the Mailgun API.

All 10 templates below use the Send Email node (SMTP) or the HTTP Request node targeting the SendGrid API. Swap in your own credentials.

Template 1: New Customer Welcome Sequence (3-Email Drip)

Use case

When a new user signs up (via webhook, form, or database insert), send a timed welcome sequence: immediate confirmation, onboarding tips at 24 hours, and a check-in at day 7. Without automation, this requires manual follow-up or an expensive platform. With n8n, you set it up once.

Expected outcome

  • Time saved: 2h/week of manual follow-ups eliminated
  • Conversion lift: 20–35% improvement in activation rate (industry benchmark for welcome sequences)
  • Cost: EUR 0.003 per new user (3 emails via SES)

n8n workflow JSON

{ "name": "Welcome Sequence - 3 Emails", "nodes": [ { "name": "Webhook Trigger", "type": "n8n-nodes-base.webhook", "parameters": { "path": "new-user", "httpMethod": "POST", "responseMode": "onReceived" }, "position": [250, 300] }, { "name": "Email 1 — Welcome (Immediate)", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "hello@yourcompany.com", "toEmail": "={{ $json.email }}", "subject": "Welcome, {{ $json.first_name }}! Here's how to get started", "emailType": "html", "html": "<h1>Welcome, {{ $json.first_name }}!</h1><p>We're glad you're here...</p>" }, "position": [450, 300] }, { "name": "Wait 24h", "type": "n8n-nodes-base.wait", "parameters": { "amount": 24, "unit": "hours" }, "position": [650, 300] }, { "name": "Email 2 — Onboarding Tips", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "hello@yourcompany.com", "toEmail": "={{ $node['Webhook Trigger'].json.email }}", "subject": "3 tips to get the most out of [Product]", "emailType": "html", "html": "<h2>Ready to dive in?</h2><p>Here are your first 3 steps...</p>" }, "position": [850, 300] }, { "name": "Wait 6 Days", "type": "n8n-nodes-base.wait", "parameters": { "amount": 6, "unit": "days" }, "position": [1050, 300] }, { "name": "Email 3 — Day 7 Check-in", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "hello@yourcompany.com", "toEmail": "={{ $node['Webhook Trigger'].json.email }}", "subject": "How's it going, {{ $node['Webhook Trigger'].json.first_name }}?", "emailType": "html", "html": "<p>It's been a week — any questions? Reply to this email.</p>" }, "position": [1250, 300] } ], "connections": { "Webhook Trigger": { "main": [[ { "node": "Email 1 — Welcome (Immediate)", "type": "main", "index": 0 } ]] }, "Email 1 — Welcome (Immediate)": { "main": [[ { "node": "Wait 24h", "type": "main", "index": 0 } ]] }, "Wait 24h": { "main": [[ { "node": "Email 2 — Onboarding Tips", "type": "main", "index": 0 } ]] }, "Email 2 — Onboarding Tips": { "main": [[ { "node": "Wait 6 Days", "type": "main", "index": 0 } ]] }, "Wait 6 Days": { "main": [[ { "node": "Email 3 — Day 7 Check-in", "type": "main", "index": 0 } ]] } } }

How to import: In n8n, click the menu icon → Import from JSON → paste the config above. Update the fromEmail fields and HTML templates to match your brand.

Pro tip: Add a Check Unsubscribe node before each email that queries your suppression list (PostgreSQL or Airtable). This keeps you GDPR/CAN-SPAM compliant automatically.

Template 2: Order Confirmation + Shipping Notification

Use case

Triggered by a Stripe payment success webhook or a WooCommerce order event. Sends an immediate order confirmation, then a second email when the order status changes to "shipped" (polled from your database or e-commerce API every 30 minutes).

Expected outcome

  • Support tickets reduced: 40% fewer "where is my order?" emails (replaces manual shipping updates)
  • Cost: EUR 0.001 per order (2 emails via SES)
  • Setup time: 45 minutes once, runs forever
{ "name": "Order Confirmation + Shipping", "nodes": [ { "name": "Stripe Webhook", "type": "n8n-nodes-base.webhook", "parameters": { "path": "stripe-payment", "httpMethod": "POST" }, "position": [250, 300] }, { "name": "Filter: payment_intent.succeeded", "type": "n8n-nodes-base.if", "parameters": { "conditions": { "string": [{ "value1": "={{ $json.type }}", "operation": "equals", "value2": "payment_intent.succeeded" }] } }, "position": [450, 300] }, { "name": "Order Confirmation Email", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "orders@yourstore.com", "toEmail": "={{ $json.data.object.receipt_email }}", "subject": "Order confirmed — #{{ $json.data.object.metadata.order_id }}", "emailType": "html", "html": "<h1>Thank you for your order!</h1><p>Order #{{ $json.data.object.metadata.order_id }} is confirmed.</p><p>Amount: {{ ($json.data.object.amount / 100).toFixed(2) }} EUR</p><p>We'll email you when it ships.</p>" }, "position": [650, 200] }, { "name": "Poll Order Status Every 30min", "type": "n8n-nodes-base.scheduleTrigger", "parameters": { "rule": { "interval": [{ "field": "minutes", "minutesInterval": 30 }] } }, "position": [250, 500] }, { "name": "Check DB for Shipped Orders", "type": "n8n-nodes-base.postgres", "parameters": { "operation": "executeQuery", "query": "SELECT * FROM orders WHERE status = 'shipped' AND shipping_email_sent = false LIMIT 50" }, "position": [450, 500] }, { "name": "Shipping Notification Email", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "orders@yourstore.com", "toEmail": "={{ $json.customer_email }}", "subject": "Your order #{{ $json.order_id }} is on its way!", "emailType": "html", "html": "<h1>Great news — it's shipped!</h1><p>Tracking: <a href='{{ $json.tracking_url }}'>{{ $json.tracking_number }}</a></p>" }, "position": [650, 500] }, { "name": "Mark Email Sent in DB", "type": "n8n-nodes-base.postgres", "parameters": { "operation": "executeQuery", "query": "UPDATE orders SET shipping_email_sent = true WHERE order_id = '{{ $json.order_id }}'" }, "position": [850, 500] } ] }

Template 3: AI-Personalized Weekly Digest

Use case

Every Monday at 8 AM, pull the week's activity from your database (or Notion, Airtable), run it through Claude API to write a personalized summary for each user, and send it. No two digests are identical — Claude adapts tone and emphasis based on each user's recent activity.

Expected outcome

  • Open rate: 45–55% (vs. 20% for generic newsletters — personalized digests consistently outperform)
  • Cost: EUR 0.002 per user per week (Claude Haiku for summarization + SES)
  • Time saved: 4h/week of manual reporting for teams up to 500 users
{ "name": "Weekly AI Digest", "nodes": [ { "name": "Schedule: Every Monday 8 AM", "type": "n8n-nodes-base.scheduleTrigger", "parameters": { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 8 * * 1" }] } }, "position": [250, 300] }, { "name": "Fetch Active Users", "type": "n8n-nodes-base.postgres", "parameters": { "operation": "executeQuery", "query": "SELECT user_id, email, first_name, weekly_stats FROM users WHERE active = true AND digest_opt_in = true" }, "position": [450, 300] }, { "name": "Loop Over Users", "type": "n8n-nodes-base.splitInBatches", "parameters": { "batchSize": 1 }, "position": [650, 300] }, { "name": "Claude: Generate Personalized Summary", "type": "n8n-nodes-base.httpRequest", "parameters": { "method": "POST", "url": "https://api.anthropic.com/v1/messages", "headers": { "x-api-key": "={{ $env.ANTHROPIC_API_KEY }}", "anthropic-version": "2023-06-01", "content-type": "application/json" }, "body": { "model": "claude-haiku-4-5-20251001", "max_tokens": 300, "messages": [{ "role": "user", "content": "Write a friendly, 3-sentence weekly digest for {{ $json.first_name }}. Their activity this week: {{ JSON.stringify($json.weekly_stats) }}. Focus on progress made, not just numbers. Be encouraging and specific." }] }, "bodyContentType": "json" }, "position": [850, 300] }, { "name": "Send Digest Email", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "digest@yourapp.com", "toEmail": "={{ $node['Loop Over Users'].json.email }}", "subject": "Your week in review, {{ $node['Loop Over Users'].json.first_name }} 📊", "emailType": "html", "html": "<h2>Your week in review</h2><p>{{ $json.content[0].text }}</p><hr/><p><a href='https://yourapp.com/dashboard'>View full stats →</a></p><p style='font-size:12px;color:#888;'><a href='{{ $env.UNSUBSCRIBE_BASE_URL }}?email={{ $node["Loop Over Users"].json.email }}'>Unsubscribe</a></p>" }, "position": [1050, 300] } ] }
Cost breakdown for 500 users/week: Claude Haiku at $0.00025/1K input tokens + $0.00125/1K output tokens. Each summary: ~100 input tokens + ~80 output tokens = $0.000125/user. 500 users = $0.06/week. Add SES ($0.50 per 1,000) = $0.25. Total: under $1/week for a fully AI-personalized digest.

Template 4: Overdue Invoice Reminder Sequence

Use case

Checks your accounting system (Stripe, QuickBooks, or PostgreSQL) daily for overdue invoices. Sends progressively firmer reminders at day 1, day 7, and day 14 overdue. Stops automatically when the invoice is paid. Escalates to a human at day 30.

Expected outcome

  • Recovery rate: 35–60% of overdue invoices paid within 14 days with automated reminders (vs. 15% without)
  • Time saved: 3–5h/week of manual follow-up for businesses with 50+ clients
  • Cost: EUR 0.003 per invoice (3 emails max)
{ "name": "Invoice Reminder Sequence", "nodes": [ { "name": "Daily Check at 9 AM", "type": "n8n-nodes-base.scheduleTrigger", "parameters": { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 9 * * *" }] } }, "position": [250, 300] }, { "name": "Fetch Overdue Invoices", "type": "n8n-nodes-base.postgres", "parameters": { "operation": "executeQuery", "query": "SELECT invoice_id, client_email, client_name, amount, due_date, days_overdue, reminder_count FROM invoices WHERE status = 'unpaid' AND due_date < NOW() AND reminder_count < 3" }, "position": [450, 300] }, { "name": "Route by Days Overdue", "type": "n8n-nodes-base.switch", "parameters": { "dataType": "number", "value1": "={{ $json.days_overdue }}", "rules": { "rules": [ { "operation": "between", "value1": 1, "value2": 6, "output": 0 }, { "operation": "between", "value1": 7, "value2": 13, "output": 1 }, { "operation": "largerEqual", "value1": 14, "output": 2 } ] } }, "position": [650, 300] }, { "name": "Reminder 1 — Gentle (1-6 days)", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "billing@yourcompany.com", "toEmail": "={{ $json.client_email }}", "subject": "Invoice #{{ $json.invoice_id }} — Gentle Reminder", "emailType": "html", "html": "<p>Hi {{ $json.client_name }},</p><p>Just a friendly reminder that invoice #{{ $json.invoice_id }} for {{ $json.amount }} EUR was due on {{ $json.due_date }}.</p><p><a href='{{ $env.PAYMENT_LINK_BASE }}/{{ $json.invoice_id }}'>Pay now →</a></p>" }, "position": [850, 150] }, { "name": "Reminder 2 — Firm (7-13 days)", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "billing@yourcompany.com", "toEmail": "={{ $json.client_email }}", "subject": "Action required: Invoice #{{ $json.invoice_id }} overdue {{ $json.days_overdue }} days", "emailType": "html", "html": "<p>Hi {{ $json.client_name }},</p><p>Invoice #{{ $json.invoice_id }} is now {{ $json.days_overdue }} days overdue. Please settle this as soon as possible to avoid late fees.</p><p><a href='{{ $env.PAYMENT_LINK_BASE }}/{{ $json.invoice_id }}'>Pay {{ $json.amount }} EUR →</a></p>" }, "position": [850, 300] }, { "name": "Reminder 3 — Urgent (14+ days)", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "billing@yourcompany.com", "toEmail": "={{ $json.client_email }}", "subject": "URGENT: Invoice #{{ $json.invoice_id }} — Final notice", "emailType": "html", "html": "<p>Hi {{ $json.client_name }},</p><p>This is a final automated notice. Invoice #{{ $json.invoice_id }} is {{ $json.days_overdue }} days past due. If not settled within 48 hours, this will be escalated to our collections team.</p><p><a href='{{ $env.PAYMENT_LINK_BASE }}/{{ $json.invoice_id }}'>Pay now →</a></p>" }, "position": [850, 450] }, { "name": "Increment Reminder Count", "type": "n8n-nodes-base.postgres", "parameters": { "operation": "executeQuery", "query": "UPDATE invoices SET reminder_count = reminder_count + 1, last_reminder_at = NOW() WHERE invoice_id = '{{ $json.invoice_id }}'" }, "position": [1050, 300] } ] }

Template 5: Abandoned Cart Recovery

Use case

Detects when a cart has been inactive for 1 hour (item added but no purchase). Sends a recovery email with the cart contents and a time-limited discount code. A second email at 24 hours if still not converted.

Expected outcome

  • Recovery rate: 5–15% of abandoned carts recovered (industry average: 8%)
  • Revenue per email: Average EUR 12-18 recovered per sent email
  • Cost: EUR 0.001 per cart (2 emails)
{ "name": "Abandoned Cart Recovery", "nodes": [ { "name": "Hourly Check", "type": "n8n-nodes-base.scheduleTrigger", "parameters": { "rule": { "interval": [{ "field": "hours", "hoursInterval": 1 }] } }, "position": [250, 300] }, { "name": "Find Abandoned Carts", "type": "n8n-nodes-base.postgres", "parameters": { "operation": "executeQuery", "query": "SELECT cart_id, user_email, user_name, cart_items, cart_total FROM carts WHERE status = 'active' AND updated_at < NOW() - INTERVAL '1 hour' AND updated_at > NOW() - INTERVAL '2 hours' AND recovery_email_1_sent = false" }, "position": [450, 300] }, { "name": "Generate Discount Code", "type": "n8n-nodes-base.code", "parameters": { "jsCode": "const code = 'SAVE10-' + Math.random().toString(36).substr(2, 8).toUpperCase(); return [{ ...item.json, discount_code: code }];" }, "position": [650, 300] }, { "name": "Email 1 — Cart Reminder with Discount", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "shop@yourstore.com", "toEmail": "={{ $json.user_email }}", "subject": "You left something behind, {{ $json.user_name }} — 10% off to complete your order", "emailType": "html", "html": "<h2>Your cart is waiting!</h2><p>Here's what you left:</p><pre>{{ JSON.stringify($json.cart_items, null, 2) }}</pre><p>Total: {{ $json.cart_total }} EUR</p><p>Use code <strong>{{ $json.discount_code }}</strong> for 10% off. Expires in 24 hours.</p><p><a href='{{ $env.SHOP_URL }}/cart/{{ $json.cart_id }}'>Complete your order →</a></p>" }, "position": [850, 300] }, { "name": "Save Discount Code to DB", "type": "n8n-nodes-base.postgres", "parameters": { "operation": "executeQuery", "query": "UPDATE carts SET recovery_email_1_sent = true, discount_code = '{{ $json.discount_code }}' WHERE cart_id = '{{ $json.cart_id }}'" }, "position": [1050, 300] } ] }

Template 6: Support Ticket Status Update

Use case

Whenever a support ticket changes status in your helpdesk (Zendesk, Freshdesk, or a custom PostgreSQL table), automatically email the customer with the update. No agent needs to manually copy-paste status messages.

Expected outcome

  • CSAT improvement: +12 points on average (customers hate silence — proactive updates change everything)
  • Agent time saved: 30 min/agent/day eliminated from status update emails
  • Cost: EUR 0.0005 per ticket update (1 email via SES)
# Zendesk Trigger approach # In Zendesk: Settings → Triggers → Create Trigger # Condition: Ticket Status changed # Action: Notify webhook → https://your-n8n.com/webhook/ticket-update { "name": "Support Ticket Status Email", "nodes": [ { "name": "Zendesk Webhook", "type": "n8n-nodes-base.webhook", "parameters": { "path": "ticket-update", "httpMethod": "POST" }, "position": [250, 300] }, { "name": "Map Status to Message", "type": "n8n-nodes-base.code", "parameters": { "jsCode": "const statusMessages = { 'open': 'Your ticket has been opened and assigned to our team.', 'pending': 'We need more information. Please reply to this email.', 'solved': 'Your ticket has been resolved. If the issue persists, reply within 7 days to reopen it.', 'closed': 'This ticket has been closed. Thank you for your patience.' }; return [{ ...item.json, status_message: statusMessages[item.json.ticket_status] || 'Your ticket has been updated.' }];" }, "position": [450, 300] }, { "name": "Send Status Update", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "support@yourcompany.com", "toEmail": "={{ $json.requester_email }}", "subject": "[Ticket #{{ $json.ticket_id }}] Status: {{ $json.ticket_status }}", "emailType": "html", "html": "<p>Hi {{ $json.requester_name }},</p><p>{{ $json.status_message }}</p><p><strong>Ticket #{{ $json.ticket_id }}</strong>: {{ $json.ticket_subject }}</p><p><a href='{{ $json.ticket_url }}'>View ticket →</a></p>" }, "position": [650, 300] } ] }

Template 7: New Lead AI Qualification + Routing

Use case

When a prospect submits a contact form, Claude analyzes their message to assign a lead score (A/B/C), infer their main need, and draft a personalized reply. The reply is sent automatically for C leads; A and B leads get the draft sent to the assigned sales rep for a 1-click approve-and-send workflow.

Expected outcome

  • Response time: From 24h average to under 5 minutes for all leads
  • Qualification time: 15 min per lead → 30 seconds (Claude does the reading)
  • Cost: EUR 0.002 per lead (Claude Haiku classification + reply draft)
{ "name": "Lead Qualification + Auto-Reply", "nodes": [ { "name": "Form Webhook", "type": "n8n-nodes-base.webhook", "parameters": { "path": "contact-form", "httpMethod": "POST" }, "position": [250, 300] }, { "name": "Claude: Qualify Lead", "type": "n8n-nodes-base.httpRequest", "parameters": { "method": "POST", "url": "https://api.anthropic.com/v1/messages", "headers": { "x-api-key": "={{ $env.ANTHROPIC_API_KEY }}", "anthropic-version": "2023-06-01", "content-type": "application/json" }, "body": { "model": "claude-haiku-4-5-20251001", "max_tokens": 500, "messages": [{ "role": "user", "content": "Analyze this lead and respond in JSON. Lead: Name={{ $json.name }}, Company={{ $json.company }}, Message={{ $json.message }}. Respond ONLY with: {"score": "A|B|C", "primary_need": "...", "reply_draft": "..."}. A = high budget + urgent + clear need. B = medium potential. C = vague or not a fit. The reply_draft should be personalized to their specific message (2-3 sentences)." }] }, "bodyContentType": "json" }, "position": [450, 300] }, { "name": "Parse Claude Response", "type": "n8n-nodes-base.code", "parameters": { "jsCode": "const response = JSON.parse(item.json.content[0].text); return [{ ...item.json, score: response.score, primary_need: response.primary_need, reply_draft: response.reply_draft }];" }, "position": [650, 300] }, { "name": "Route by Score", "type": "n8n-nodes-base.switch", "parameters": { "dataType": "string", "value1": "={{ $json.score }}", "rules": { "rules": [ { "value2": "A", "output": 0 }, { "value2": "B", "output": 1 }, { "value2": "C", "output": 2 } ] } }, "position": [850, 300] }, { "name": "A/B: Send to Sales for Approval", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "crm@yourcompany.com", "toEmail": "sales@yourcompany.com", "subject": "[{{ $json.score }} Lead] {{ $json.name }} from {{ $json.company }}", "emailType": "html", "html": "<p><strong>New {{ $json.score }}-score lead:</strong></p><p>Name: {{ $json.name }}<br>Company: {{ $json.company }}<br>Need: {{ $json.primary_need }}</p><p><strong>Suggested reply:</strong></p><blockquote>{{ $json.reply_draft }}</blockquote><p><a href='{{ $env.CRM_URL }}/approve/{{ $json.lead_id }}'>Approve & Send →</a></p>" }, "position": [1050, 150] }, { "name": "C: Auto-Send Reply", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "hello@yourcompany.com", "toEmail": "={{ $json.email }}", "subject": "Re: Your inquiry", "emailType": "html", "html": "<p>Hi {{ $json.name }},</p><p>{{ $json.reply_draft }}</p><p>Best,<br>The Team</p>" }, "position": [1050, 450] } ] }

Template 8: Employee Onboarding Email Sequence

Use case

Triggered when HR adds a new employee to the HRIS (BambooHR, Personio, or a spreadsheet). Sends a pre-boarding welcome email, day-1 logistics, day-2 tools setup guide, week-1 check-in, and 30-day feedback survey request. The sequence stops if the employee is marked inactive.

Expected outcome

  • Time-to-productivity: 20% faster (structured information delivery vs. verbal briefings)
  • HR time saved: 3h per new hire eliminated
  • Cost: EUR 0.005 per employee (5 emails over 30 days)
{ "name": "Employee Onboarding Sequence", "nodes": [ { "name": "HR System Webhook (new hire)", "type": "n8n-nodes-base.webhook", "parameters": { "path": "new-employee", "httpMethod": "POST" }, "position": [250, 300] }, { "name": "Pre-boarding Welcome", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "hr@yourcompany.com", "toEmail": "={{ $json.personal_email }}", "subject": "Welcome to [Company], {{ $json.first_name }}! Your start date: {{ $json.start_date }}", "emailType": "html", "html": "<h1>We can't wait to meet you!</h1><p>Hi {{ $json.first_name }},</p><p>Your start date is {{ $json.start_date }}. Here's what to bring: ID, signed contract (attached).</p><p>Your manager is {{ $json.manager_name }} — they'll be in touch before your first day.</p>" }, "position": [450, 300] }, { "name": "Wait Until Start Date", "type": "n8n-nodes-base.wait", "parameters": { "amount": "={{ Math.max(0, Math.ceil((new Date($json.start_date) - new Date()) / (1000 * 60 * 60 * 24))) }}", "unit": "days" }, "position": [650, 300] }, { "name": "Day 1: Logistics Email", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "hr@yourcompany.com", "toEmail": "={{ $json.work_email }}", "subject": "Day 1 checklist — everything you need for today", "emailType": "html", "html": "<h2>Good morning, {{ $json.first_name }}!</h2><ul><li>Your desk: Floor 3, Desk 14B</li><li>WiFi: CompanyNetwork / password sent separately</li><li>Lunch: Team lunch at 12:30 in the main kitchen</li><li>IT setup: Meeting with IT at 10 AM</li></ul>" }, "position": [850, 300] } ] }

Template 9: Multi-Level Approval Workflow

Use case

A team member submits a request (leave, budget, purchase) via a form or Slack command. n8n routes it to the first approver, waits for their decision (approve/reject link in the email), then routes to the second approver if needed. The requester is notified at each step.

Expected outcome

  • Decision time: From days to hours (no more "who do I email for this?")
  • Audit trail: Every approval stored in PostgreSQL with timestamp and approver ID
  • Cost: EUR 0.001 per request (2-3 emails)
{ "name": "Multi-Level Approval", "nodes": [ { "name": "Request Form Webhook", "type": "n8n-nodes-base.webhook", "parameters": { "path": "approval-request", "httpMethod": "POST" }, "position": [250, 300] }, { "name": "Save Request to DB", "type": "n8n-nodes-base.postgres", "parameters": { "operation": "insert", "table": "approval_requests", "columns": "request_id,requester_email,requester_name,request_type,details,status,created_at", "values": "={{ $json.request_id }},{{ $json.requester_email }},{{ $json.requester_name }},{{ $json.request_type }},{{ JSON.stringify($json.details) }},pending,{{ new Date().toISOString() }}" }, "position": [450, 300] }, { "name": "Email Level 1 Approver", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "approvals@yourcompany.com", "toEmail": "={{ $json.level1_approver_email }}", "subject": "[Action needed] {{ $json.request_type }} request from {{ $json.requester_name }}", "emailType": "html", "html": "<h2>Approval Required</h2><p>{{ $json.requester_name }} has submitted a {{ $json.request_type }} request:</p><blockquote>{{ JSON.stringify($json.details) }}</blockquote><p><a href='{{ $env.APPROVAL_BASE_URL }}/approve/{{ $json.request_id }}?approver={{ $json.level1_approver_id }}' style='background:#16a34a;color:white;padding:10px 20px;text-decoration:none;border-radius:5px;'>✓ Approve</a>&nbsp;&nbsp;<a href='{{ $env.APPROVAL_BASE_URL }}/reject/{{ $json.request_id }}?approver={{ $json.level1_approver_id }}' style='background:#dc2626;color:white;padding:10px 20px;text-decoration:none;border-radius:5px;'>✗ Reject</a></p>" }, "position": [650, 300] }, { "name": "Wait for Approval (webhook)", "type": "n8n-nodes-base.webhook", "parameters": { "path": "approval-decision/{{ $json.request_id }}", "httpMethod": "GET" }, "position": [850, 300] }, { "name": "Route: Approved or Rejected", "type": "n8n-nodes-base.if", "parameters": { "conditions": { "string": [{ "value1": "={{ $json.decision }}", "operation": "equals", "value2": "approve" }] } }, "position": [1050, 300] }, { "name": "Notify Requester: Approved", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "approvals@yourcompany.com", "toEmail": "={{ $json.requester_email }}", "subject": "✓ Your {{ $json.request_type }} request has been approved", "emailType": "html", "html": "<p>Hi {{ $json.requester_name }},</p><p>Your request has been approved by {{ $json.approver_name }}.</p>" }, "position": [1250, 200] }, { "name": "Notify Requester: Rejected", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "approvals@yourcompany.com", "toEmail": "={{ $json.requester_email }}", "subject": "✗ Your {{ $json.request_type }} request was not approved", "emailType": "html", "html": "<p>Hi {{ $json.requester_name }},</p><p>Your request was not approved. Reason: {{ $json.rejection_reason }}. Please contact {{ $json.approver_name }} directly if you have questions.</p>" }, "position": [1250, 400] } ] }

Template 10: Event Registration + Automated Reminders

Use case

When someone registers for a webinar or in-person event (via Stripe, Eventbrite webhook, or a form), send an instant confirmation with calendar invite, a reminder 48 hours before, and a last-call reminder 1 hour before. After the event, send a follow-up with the recording link.

Expected outcome

  • Show-up rate: 60–75% with timed reminders (vs. 40% with confirmation-only)
  • Manual work eliminated: 2h per event (no more copy-pasting attendee lists)
  • Cost: EUR 0.004 per registrant (4 emails)
{ "name": "Event Registration + Reminders", "nodes": [ { "name": "Registration Webhook", "type": "n8n-nodes-base.webhook", "parameters": { "path": "event-registration", "httpMethod": "POST" }, "position": [250, 300] }, { "name": "Confirmation + Calendar Invite", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "events@yourcompany.com", "toEmail": "={{ $json.attendee_email }}", "subject": "You're registered: {{ $json.event_name }} on {{ $json.event_date }}", "emailType": "html", "attachments": "={{ [{ 'filename': 'event.ics', 'content': 'BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nDTSTART:' + $json.event_datetime_ics + '\nDTEND:' + $json.event_end_ics + '\nSUMMARY:' + $json.event_name + '\nDESCRIPTION:Join link: ' + $json.join_url + '\nEND:VEVENT\nEND:VCALENDAR', 'type': 'text/calendar' }] }}", "html": "<h2>You're in! 🎉</h2><p>Hi {{ $json.first_name }},</p><p>You're registered for <strong>{{ $json.event_name }}</strong>.</p><p>📅 {{ $json.event_date }} at {{ $json.event_time }}<br>🔗 Join link: <a href='{{ $json.join_url }}'>{{ $json.join_url }}</a></p><p>Calendar invite attached. See you there!</p>" }, "position": [450, 300] }, { "name": "Calculate Wait Until 48h Before", "type": "n8n-nodes-base.code", "parameters": { "jsCode": "const eventTime = new Date(item.json.event_datetime); const reminderTime = new Date(eventTime - 48 * 60 * 60 * 1000); const now = new Date(); const waitMs = Math.max(0, reminderTime - now); return [{ ...item.json, wait_hours: Math.ceil(waitMs / (1000 * 60 * 60)) }];" }, "position": [650, 300] }, { "name": "Wait Until 48h Before", "type": "n8n-nodes-base.wait", "parameters": { "amount": "={{ $json.wait_hours }}", "unit": "hours" }, "position": [850, 300] }, { "name": "48h Reminder", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "events@yourcompany.com", "toEmail": "={{ $json.attendee_email }}", "subject": "Reminder: {{ $json.event_name }} is in 2 days", "emailType": "html", "html": "<p>Hi {{ $json.first_name }},</p><p>Just a reminder: <strong>{{ $json.event_name }}</strong> is in 2 days on {{ $json.event_date }} at {{ $json.event_time }}.</p><p><a href='{{ $json.join_url }}'>Join link →</a></p>" }, "position": [1050, 300] }, { "name": "Wait Until 1h Before", "type": "n8n-nodes-base.wait", "parameters": { "amount": 47, "unit": "hours" }, "position": [1250, 300] }, { "name": "1h Reminder", "type": "n8n-nodes-base.sendEmail", "parameters": { "fromEmail": "events@yourcompany.com", "toEmail": "={{ $json.attendee_email }}", "subject": "Starting in 1 hour: {{ $json.event_name }}", "emailType": "html", "html": "<p>Hi {{ $json.first_name }}, your event starts in 1 hour!</p><p><a href='{{ $json.join_url }}' style='background:#7c3aed;color:white;padding:12px 24px;text-decoration:none;border-radius:6px;font-weight:bold;'>Join now →</a></p>" }, "position": [1450, 300] } ] }

Deployment Guide: From Local to Production

Step 1: Set environment variables

In n8n, go to Settings → Variables and add:

# Required for all email templates ANTHROPIC_API_KEY=sk-ant-... # Claude API (templates 3, 7) SMTP_HOST=email-smtp.eu-west-1.amazonaws.com SMTP_USER=AKIAIOSFODNN7EXAMPLE SMTP_PASS=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY SMTP_PORT=587 # Template-specific PAYMENT_LINK_BASE=https://pay.yourcompany.com SHOP_URL=https://yourstore.com APPROVAL_BASE_URL=https://yourapp.com/approvals UNSUBSCRIBE_BASE_URL=https://yourapp.com/unsubscribe CRM_URL=https://crm.yourcompany.com

Step 2: Configure error notifications

Every production workflow should have error handling. Add an Error Trigger node that catches any workflow failure and sends you a Slack message or email:

{ "name": "Error Handler", "nodes": [ { "name": "Error Trigger", "type": "n8n-nodes-base.errorTrigger", "position": [250, 300] }, { "name": "Alert via Slack", "type": "n8n-nodes-base.slack", "parameters": { "channel": "#n8n-alerts", "text": "❌ Workflow failed: {{ $json.workflow.name }}\nError: {{ $json.execution.error.message }}\nExecution: {{ $env.N8N_URL }}/workflow/{{ $json.workflow.id }}/executions/{{ $json.execution.id }}" }, "position": [450, 300] } ] }

Step 3: Enable execution data retention

In n8n Settings → Workflow Settings, set execution data pruning to 30 days. This keeps your database from growing unbounded while retaining enough history for debugging.

# In docker-compose.yml, add to n8n environment: - EXECUTIONS_DATA_PRUNE=true - EXECUTIONS_DATA_MAX_AGE=720 # 30 days in hours - EXECUTIONS_DATA_SAVE_ON_ERROR=all - EXECUTIONS_DATA_SAVE_ON_SUCCESS=none # Don't save success data (saves disk) - EXECUTIONS_DATA_SAVE_MANUAL_EXECUTIONS=true

Step 4: Monitor deliverability

After deploying, set up a basic deliverability check:

  • SPF record: Add v=spf1 include:amazonses.com ~all to your domain DNS
  • DKIM: Verify your domain in SES Console → Verified Identities → enable DKIM
  • DMARC: Add v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com
  • Test delivery: Use mail-tester.com to score your first email — aim for 9+/10

Frequently Asked Questions

Does n8n support Gmail, Outlook, and SMTP?

Yes. n8n has native nodes for Gmail (OAuth2), Microsoft Outlook/Microsoft 365 (OAuth2), and a generic SMTP node that works with any provider (SendGrid, Mailgun, Postmark, Amazon SES). For transactional volume, use the SendGrid or Amazon SES nodes — they offer better deliverability and per-message tracking. OAuth2 Gmail works well for low-volume internal automations (<500 emails/day).

How much does it cost to send 10,000 emails/month with n8n?

Infrastructure only: n8n self-hosted on a EUR 5/month VPS (Hetzner CX11). Email delivery: Amazon SES costs $1 per 1,000 emails = $10 for 10k. Total: ~EUR 15/month. Compare to Mailchimp Automations at $100/month for 10k contacts. If you add Claude API for subject line personalization (Haiku model): ~$0.0005 per email = $5 for 10k. Full AI-personalized email automation at scale: ~EUR 20/month.

Can I use n8n for cold outreach?

n8n can technically send bulk emails, but cold outreach has legal and deliverability implications. In the EU, GDPR requires explicit consent for marketing emails. In the US, CAN-SPAM requires opt-out mechanisms. For legitimate outreach to existing leads who consented: use n8n + SendGrid + proper unsubscribe handling. Never use n8n to scrape contacts and mass-email them — this will get your domain blacklisted within days.

What's the difference between n8n email automation and Mailchimp/ActiveCampaign?

Mailchimp and ActiveCampaign are purpose-built email marketing platforms with drag-and-drop campaign builders, built-in analytics, and deliverability management. n8n is a general-purpose automation platform — more flexible but requires more setup. Use n8n when: you need email as part of a larger workflow (e.g., Stripe payment → update CRM → send email), you want full data control, or costs matter. Use Mailchimp when: you mainly need broadcast campaigns, list management, and A/B testing for marketing emails.

How do I handle unsubscribes in n8n email workflows?

Best practice: use an email delivery service (SendGrid, Mailgun, Amazon SES) that handles unsubscribe links and suppression lists automatically. Configure your n8n workflow to check the suppression list before sending by querying the SendGrid API: GET /v3/suppression/unsubscribes. Alternatively, use a dedicated unsubscribe webhook in n8n: when clicked, add the email to your 'do not send' table in PostgreSQL or Airtable, and filter all outgoing email workflows against that list.

Master n8n and AI Automation

Hands-on training for professionals who want to build real automations — not just watch demos.

n8n Training ProgramAll Programs