n8n Lead Generation Template Pack — 5 B2B Outbound Workflows (CAN-SPAM and GDPR Fields Built In)
Five import-ready n8n workflows (42 nodes in total, 5 of them setup notes) that run a complete B2B outbound motion inside your own n8n: import and consent-stamp leads, send a three-step sequence with the CAN-SPAM footer attached automatically, stop on reply or objection, capture unsubscribes on a webhook, and answer GDPR access and erasure requests inside the one-month deadline of Art. 12(3). Bought once and run on n8n, which is free when self-hosted — the per-seat cold-outbound SaaS this replaces starts above $29 per sending seat per month (lemlist, Instantly and Smartlead all bill per seat, per month).
Free workflow — copy & import
{
"name": "Unsubscribe Webhook to Suppression List (CAN-SPAM 10-Business-Day Rule)",
"nodes": [
{
"parameters": {
"path": "unsubscribe",
"options": {}
},
"id": "b3e8aaf7-ba08-b5e5-b3e8-aaf7ba08b5e5",
"name": "Receive the Unsubscribe Click",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
0,
300
]
},
{
"parameters": {
"mode": "manual",
"assignments": {
"assignments": [
{
"id": "a1",
"name": "campaign",
"value": "={{ $json.query.campaign || 'unknown' }}",
"type": "string"
},
{
"id": "a1",
"name": "email",
"value": "={{ $json.query.email }}",
"type": "string"
},
{
"id": "a1",
"name": "source",
"value": "unsubscribe_link",
"type": "string"
}
]
},
"options": {}
},
"id": "b3e8aaf7-ba08-b5e6-b3e8-aaf7ba08b5e6",
"name": "Read the Email and Campaign from the Link",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
220,
300
]
},
{
"parameters": {
"jsCode": "return items.map(item => {\n const raw = String(item.json.email || '').trim().toLowerCase();\n const valid = /^[^@\\s]+@[^@\\s.]+\\.[^@\\s]+$/.test(raw);\n const now = new Date();\n return { json: {\n email: raw,\n valid,\n domain: valid ? raw.split('@')[1] : '',\n campaign: item.json.campaign,\n opted_out_at_utc: now.toISOString(),\n honour_by_note: 'CAN-SPAM 15 U.S.C. 7704(a)(4)(A): opt-out must be honoured within 10 business days',\n request_type: 'opt_out'\n } };\n});"
},
"id": "b3e8aaf7-ba08-b5e7-b3e8-aaf7ba08b5e7",
"name": "Normalise the Address and Stamp the Opt-Out Time in UTC",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
440,
300
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"version": 2
},
"conditions": [],
"combinator": "and"
}
},
"id": "b3e8aaf7-ba08-b5e8-b3e8-aaf7ba08b5e8",
"name": "Keep Only Addresses That Parse",
"type": "n8n-nodes-base.filter",
"typeVersion": 2,
"position": [
660,
300
]
},
{
"parameters": {
"operation": "append",
"options": {}
},
"id": "b3e8aaf7-ba08-b5e9-b3e8-aaf7ba08b5e9",
"name": "Append the Opt-Out to Your Suppression Sheet",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
880,
300
]
},
{
"parameters": {
"subject": "You have been unsubscribed",
"text": "You will not receive any further messages from us.\n\nYour address was removed on {{ $json.opted_out_at_utc }} (UTC). If this was a mistake, reply to this message and we will restore it.\n\n[Your company name]\n[Your physical postal address]",
"toEmail": "={{ $json.email }}",
"fromEmail": "={{ $env.FROM_EMAIL }}",
"options": {}
},
"id": "b3e8aaf7-ba08-b5ea-b3e8-aaf7ba08b5ea",
"name": "Confirm the Opt-Out to the Recipient",
"type": "n8n-nodes-base.emailSend",
"typeVersion": 2.1,
"position": [
1100,
300
]
},
{
"parameters": {
"content": "## Setup — 3 steps, about 5 minutes\n\n**1. Credentials.** Open *Append the Opt-Out to Your Suppression Sheet* and pick your own Google Sheets credential. Open *Confirm the Opt-Out to the Recipient* and pick your own SMTP or mail-provider credential. Nothing is pre-filled: no key, token or password ships in this file.\n\n**2. Your sheet.** Create a Google Sheet with a tab named `suppression` and these five header columns in row 1: `email`, `domain`, `campaign`, `opted_out_at_utc`, `request_type`. Select that document and tab on the Sheets node.\n\n**3. Your link.** Activate the workflow, copy the Production URL from *Receive the Unsubscribe Click*, and use it as the unsubscribe link in your emails, in this shape:\n`https://YOUR-N8N-HOST/webhook/unsubscribe?email={{recipient}}&campaign={{campaign}}`\n\n**Fill in your own text.** The confirmation email ends with `[Your company name]` and `[Your physical postal address]`. Replace both. CAN-SPAM (15 U.S.C. 7704(a)(5)) requires a valid physical postal address in commercial email, and 15 U.S.C. 7704(a)(4)(A) requires the opt-out to be honoured within 10 business days — appending it here the moment the link is clicked is what keeps you inside that window.\n\n**Test it.** Open the Production URL in a browser with your own address in the query string. A row should appear in `suppression` within a second.\n\nQuestions: the support address on the listing page.",
"height": 220,
"width": 320
},
"id": "b3e8aaf7-ba08-b5eb-b3e8-aaf7ba08b5eb",
"name": "Setup",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
1320,
100
]
}
],
"connections": {
"Receive the Unsubscribe Click": {
"main": [
[
{
"node": "Read the Email and Campaign from the Link",
"type": "main",
"index": 0
}
]
]
},
"Read the Email and Campaign from the Link": {
"main": [
[
{
"node": "Normalise the Address and Stamp the Opt-Out Time in UTC",
"type": "main",
"index": 0
}
]
]
},
"Normalise the Address and Stamp the Opt-Out Time in UTC": {
"main": [
[
{
"node": "Keep Only Addresses That Parse",
"type": "main",
"index": 0
}
]
]
},
"Keep Only Addresses That Parse": {
"main": [
[
{
"node": "Append the Opt-Out to Your Suppression Sheet",
"type": "main",
"index": 0
}
]
]
},
"Append the Opt-Out to Your Suppression Sheet": {
"main": [
[
{
"node": "Confirm the Opt-Out to the Recipient",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"pinData": {},
"meta": {
"templateId": "n8n-lead-gen-b2b-outbound-pack"
}
}