Register users and authenticate with magic links using Google Sheets
Pull contacts, verify each address with BillionVerify, and continue to Authentica — only deliverable addresses get through.
Why verify before the send
Sending to invalid, risky, catch-all, or disposable addresses spikes your bounce rate and erodes sender reputation. A verification gate before the Authentica step removes that risk automatically — only deliverable addresses continue, the rest are flagged.
The workflow
BillionVerify — verification sits right before the send.
Node by node
- 1On form registerTrigger· n8n
Starts the workflow — on a schedule, a webhook, or manually while you test.
- 2On form loginTrigger· n8n
Starts the workflow — on a schedule, a webhook, or manually while you test.
- 3AuthSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 4ProfileSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 5Create password and tokenSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 6Check username/passwordSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 7If has tokenLogic· n8n
Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.
- 8Check sid cookies headerSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 9Append or update row in sheetSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 10If login successLogic· n8n
Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.
- 11Search by tokenSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 12Invalid tokenSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 13Search sidSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 14Verify Email (BillionVerify)Verify· billionverify
The BillionVerify node verifies the address — status (valid / invalid / risky / catch-all / role / disposable), is_deliverable, and a confidence score — before anything is sent.
- 15If has token in DBLogic· n8n
Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.
- 16Redirect to login formSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 17If token validLogic· n8n
Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.
- 18If has data with sidLogic· n8n
Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.
- 19IF deliverableLogic· n8n
Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.
- 20Redirect to auth urlSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 21Generate tokenSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 22Prepare sidSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 23Redirect to login form1Source· n8n
Provides or transforms the contact data flowing through the workflow.
- 24Prepare htmlSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 25Redirect to loginSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 26Send username/passwordSend· n8n
Sends only to verified, deliverable addresses. Swap in your own provider node if you send elsewhere.
- 27Add token to DBSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 28Redirect to profile and set cookiesSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 29Update sidSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 30Show profile pageSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 31Notice register successSource· n8n
Provides or transforms the contact data flowing through the workflow.
- 32Redirect to auth with new tokenSource· n8n
Provides or transforms the contact data flowing through the workflow.
Workflow JSON
Copy or download this workflow, then import it in n8n (Workflows → Import from File / Paste). Install the BillionVerify community node first, then add your API key credential.
{
"name": "Register users and authenticate with magic links using Google Sheets + BillionVerify",
"nodes": [
{
"id": "dcaa8a9d-abdd-41a8-a359-868318d7f661",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
160,
608
],
"parameters": {
"color": 7,
"width": 1904,
"height": 384,
"content": "## 1. Login\nValidate username and password against Google Sheets, then reuse or issue a new token and redirect to the auth endpoint."
},
"typeVersion": 1
},
{
"id": "44324518-59de-4068-b49d-da5b9cabcf66",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
160,
224
],
"parameters": {
"color": 7,
"width": 1232,
"height": 288,
"content": "## 0. Register\nCollect an email address via form, generate credentials and a one-time token, save the user to Google Sheets, and send an onboarding email with the login link."
},
"typeVersion": 1
},
{
"id": "2472b7c4-cd6e-4194-8d12-59c4249ea7f3",
"name": "Append or update row in sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
688,
320
],
"parameters": {
"columns": {
"value": {
"role": "Property Manager",
"token": "YOUR_CREDENTIAL_HERE",
"password": "YOUR_CREDENTIAL_HERE",
"username": "={{ $json.email }}"
},
"schema": [
{
"id": "username",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "username",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "role",
"type": "string",
"display": true,
"required": false,
"displayName": "role",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "password",
"type": "string",
"display": true,
"required": false,
"displayName": "password",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "token",
"type": "string",
"display": true,
"required": false,
"displayName": "token",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"username"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Sheet2"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID"
}
},
"typeVersion": 4.7
},
{
"id": "d7f38863-74ee-4bb9-bb4d-2fd445594eea",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
160,
1072
],
"parameters": {
"color": 7,
"width": 1696,
"height": 400,
"content": "## 2. Auth\nValidate the one-time token via Google Sheets, generate a session id (sid), set an HttpOnly cookie, clear the token, and redirect to the profile page."
},
"typeVersion": 1
},
{
"id": "891d65a6-0b6c-474d-b029-d4702c8e4174",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
160,
1552
],
"parameters": {
"color": 7,
"width": 1424,
"height": 416,
"content": "## 3. Profile\nRead the sid cookie, look up the active session in Google Sheets, and render a profile page — or redirect to login if the session is invalid."
},
"typeVersion": 1
},
{
"id": "fee598fe-64d8-469e-be7d-745bd2be40e4",
"name": "Prepare sid",
"type": "n8n-nodes-base.code",
"position": [
1248,
1088
],
"parameters": {
"jsCode": "function makeSid(len = 48) {\n const alphabet = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\n let out = \"\";\n // Mix time + random to reduce collisions\n let seed = Date.now().toString(36) + \"-\" + Math.random().toString(36).slice(2);\n\n out += seed.replace(/[^a-z0-9-]/gi, \"\").slice(0, 12);\n\n while (out.length < len) {\n out += alphabet[Math.floor(Math.random() * alphabet.length)];\n }\n return out.slice(0, len);\n}\n\nconst sid = makeSid(64);\n\nreturn { json: { sid } };\n"
},
"typeVersion": 2
},
{
"id": "f8747e29-893d-4e5d-916c-bb56975eb881",
"name": "Generate token",
"type": "n8n-nodes-base.code",
"position": [
1328,
816
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "function makeToken(len = 32) {\n let out = '';\n while (out.length < len) {\n out += Math.random().toString(36).slice(2); // Strip the leading \"0.\"\n }\n return out.slice(0, len);\n}\nconst token = makeToken()\nreturn {token};"
},
"typeVersion": 2
},
{
"id": "fd48786d-6e97-4361-bce0-40dc80596ba2",
"name": "If has token in DB",
"type": "n8n-nodes-base.if",
"position": [
976,
640
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "df0d0deb-4db8-4ef7-9bd0-c79ab6fb4f35",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $('Check username/password').item.json.token }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "43b3e0a0-c749-4b08-8cc1-0e84fe357bed",
"name": "If has token",
"type": "n8n-nodes-base.if",
"position": [
464,
1200
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "51cff10a-dac2-4480-a177-3b6166e4fbdf",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $('Auth').item.json.query.token }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "18b1b20d-06cb-44c5-b295-2966ff15c84f",
"name": "Search by token",
"type": "n8n-nodes-base.googleSheets",
"position": [
736,
1184
],
"parameters": {
"options": {},
"filtersUI": {
"values": [
{
"lookupValue": "={{ $('Auth').item.json.query.token || '' }}",
"lookupColumn": "token"
}
]
},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Sheet2"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID"
}
},
"typeVersion": 4.7,
"alwaysOutputData": true
},
{
"id": "dc27c8ff-75d6-4448-afb2-512d21cb08bf",
"name": "On form register",
"type": "n8n-nodes-base.formTrigger",
"position": [
224,
320
],
"webhookId": "eff757ba-8869-4106-b386-8c3b21baa912",
"parameters": {
"options": {
"path": "register"
},
"formTitle": "Register with your email",
"formFields": {
"values": [
{
"fieldLabel": "Email",
"requiredField": true
}
]
},
"formDescription": "Password will be sent to your email"
},
"typeVersion": 2.2
},
{
"id": "135c6108-45ec-45e6-b1e2-317e40a9004f",
"name": "Create password and token",
"type": "n8n-nodes-base.code",
"position": [
448,
320
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "function generatePassword(length = 12) {\n const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';\n let out = '';\n for (let i = 0; i < length; i++) {\n const idx = Math.floor(Math.random() * chars.length);\n out += chars[idx];\n }\n return out;\n}\nfunction makeToken(len = 32) {\n let out = '';\n while (out.length < len) {\n out += Math.random().toString(36).slice(2); // Strip the leading \"0.\"\n }\n return out.slice(0, len);\n}\nconst email = $json.Email\nconst password = generatePassword()\nconst token = makeToken()\nreturn {email, password, token};"
},
"typeVersion": 2
},
{
"id": "fec2646b-dd15-4e8c-ad07-9fa7de065c9a",
"name": "Send username/password",
"type": "n8n-nodes-base.gmail",
"position": [
928,
320
],
"webhookId": "f66d080e-d94a-4244-99aa-fc2ebcbbece3",
"parameters": {
"sendTo": "={{ $('Create password and token').item.json.email }}",
"message": "=Link to login: https://YOUR_DOMAIN/webhook/auth?token={{ $('Create password and token').item.json.token }}\n<br/>\nUsername: {{ $('Create password and token').item.json.email }}\n<br/>\nPassword: {{ $('Create password and token').item.json.password }}",
"options": {},
"subject": "=Your login credentials"
},
"typeVersion": 2.1
},
{
"id": "f3241140-939d-4871-8bf3-25dc9c69b653",
"name": "Notice register success",
"type": "n8n-nodes-base.form",
"position": [
1184,
320
],
"webhookId": "9a192624-7617-44d3-a3c2-80e2c1839422",
"parameters": {
"options": {},
"operation": "completion",
"completionTitle": "Registration successful",
"completionMessage": "=<p><strong>✅ Please check email {{ $('Create password and token').item.json.email }} to get your login credentials</strong></p>\n<p>\n After that, you can login here:<br>\n <a href=\"https://YOUR_DOMAIN/form/login\" target=\"_blank\" rel=\"noopener noreferrer\">\n https://YOUR_DOMAIN/form/login\n </a>\n</p>"
},
"typeVersion": 1
},
{
"id": "3da0b92c-edad-43e2-bcf1-e7bca316d7e9",
"name": "On form login",
"type": "n8n-nodes-base.formTrigger",
"position": [
224,
736
],
"webhookId": "f738ecda-fe2c-4892-8fd6-382ec06f45fd",
"parameters": {
"options": {
"path": "login"
},
"formTitle": "Login",
"formFields": {
"values": [
{
"fieldLabel": "username",
"requiredField": true
},
{
"fieldType": "password",
"fieldLabel": "password",
"requiredField": true
}
]
}
},
"typeVersion": 2.2
},
{
"id": "caceffd3-862e-4ffc-817e-ed1300546caa",
"name": "Check username/password",
"type": "n8n-nodes-base.googleSheets",
"position": [
480,
736
],
"parameters": {
"options": {},
"filtersUI": {
"values": [
{
"lookupValue": "={{ $json.username }}",
"lookupColumn": "username"
},
{
"lookupValue": "={{ $json.password }}",
"lookupColumn": "password"
}
]
},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Sheet2"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID"
}
},
"typeVersion": 4.7,
"alwaysOutputData": true
},
{
"id": "91c227fd-ecbb-4d9f-a608-4606453a0e93",
"name": "If login success",
"type": "n8n-nodes-base.if",
"position": [
720,
736
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "455881ad-941f-4257-9427-1e48e7bc2974",
"operator": {
"type": "object",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "cedd4efe-c0e3-447b-a368-f4da51b0e004",
"name": "Redirect to login form",
"type": "n8n-nodes-base.form",
"position": [
992,
816
],
"webhookId": "9a192624-7617-44d3-a3c2-80e2c1839422",
"parameters": {
"options": {},
"operation": "completion",
"redirectUrl": "=https://YOUR_DOMAIN/form/login",
"respondWith": "redirect"
},
"typeVersion": 1
},
{
"id": "6c2905c0-67d1-4c29-8ee5-3676d6ab1255",
"name": "Redirect to auth url",
"type": "n8n-nodes-base.form",
"position": [
1312,
624
],
"webhookId": "9a192624-7617-44d3-a3c2-80e2c1839422",
"parameters": {
"options": {},
"operation": "completion",
"redirectUrl": "=https://YOUR_DOMAIN/webhook/auth?token={{ $json.token }}",
"respondWith": "redirect"
},
"typeVersion": 1
},
{
"id": "52426a88-2b8f-4839-b608-8c28e8cef5b1",
"name": "Add token to DB",
"type": "n8n-nodes-base.googleSheets",
"position": [
1568,
816
],
"parameters": {
"columns": {
"value": {
"token": "YOUR_CREDENTIAL_HERE",
"username": "={{ $('Check username/password').item.json.username }}"
},
"schema": [
{
"id": "username",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "username",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "role",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "role",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "password",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "password",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "token",
"type": "string",
"display": true,
"required": false,
"displayName": "token",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"username"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Sheet2"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID"
}
},
"typeVersion": 4.7
},
{
"id": "1a3c065f-b882-4088-9c34-5ebbb4fb787a",
"name": "Redirect to auth with new token",
"type": "n8n-nodes-base.form",
"position": [
1840,
816
],
"webhookId": "9a192624-7617-44d3-a3c2-80e2c1839422",
"parameters": {
"options": {},
"operation": "completion",
"redirectUrl": "=https://YOUR_DOMAIN/webhook/auth?token={{ $('Generate token').item.json.token }}",
"respondWith": "redirect"
},
"typeVersion": 1
},
{
"id": "b4e2aebf-3f52-4234-ae86-f7c29f2a226d",
"name": "Auth",
"type": "n8n-nodes-base.webhook",
"position": [
208,
1200
],
"webhookId": "6f7a23ce-b823-4dcb-af08-c7e91114292b",
"parameters": {
"path": "auth",
"options": {},
"responseMode": "responseNode"
},
"typeVersion": 2.1
},
{
"id": "340dcc3d-797c-44ca-a5ab-830687fbfffa",
"name": "Invalid token",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
736,
1344
],
"parameters": {
"options": {},
"respondWith": "text",
"responseBody": "Invalid token"
},
"typeVersion": 1.4
},
{
"id": "6119d68b-76d0-40ce-8ac1-df2253ca1a81",
"name": "If token valid",
"type": "n8n-nodes-base.if",
"position": [
976,
1184
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a29fd91f-24e3-4302-9735-25419166511b",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.username }}",
"rightValue": ""
}
]
}
},
"executeOnce": true,
"typeVersion": 2.2
},
{
"id": "d3588bf0-be76-4e85-9ac4-ce11de129b25",
"name": "Redirect to login form1",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1248,
1280
],
"parameters": {
"options": {},
"redirectURL": "={{ 'https://' + $('Auth').item.json.headers.host }}/form/login",
"respondWith": "redirect"
},
"typeVersion": 1.4
},
{
"id": "4d02d506-b001-45b8-afab-32ac76d29896",
"name": "Update sid",
"type": "n8n-nodes-base.googleSheets",
"position": [
1600,
1088
],
"parameters": {
"columns": {
"value": {
"sid": "={{ $json.sid }}",
"token": "",
"username": "={{ $('Search by token').item.json.username }}"
},
"schema": [
{
"id": "username",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "username",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "role",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "role",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "password",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "password",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "token",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "token",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "sid",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "sid",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "number",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"username"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Sheet2"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID"
}
},
"typeVersion": 4.7
},
{
"id": "0960c383-12b3-4176-8501-3b85d9453256",
"name": "Redirect to profile and set cookies",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1600,
1280
],
"parameters": {
"options": {
"responseCode": 302,
"responseHeaders": {
"entries": [
{
"name": "Set-Cookie",
"value": "=sid={{$json.sid}}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=604800"
},
{
"name": "Location",
"value": "/webhook/profile"
}
]
}
},
"respondWith": "text",
"responseBody": "={{ $json.sid }}"
},
"typeVersion": 1.4
},
{
"id": "ed4908f3-50f0-4150-adbc-ca5b706a94e3",
"name": "Profile",
"type": "n8n-nodes-base.webhook",
"position": [
224,
1712
],
"webhookId": "6f7a23ce-b823-4dcb-af08-c7e91114292b",
"parameters": {
"path": "profile",
"options": {},
"responseMode": "responseNode"
},
"typeVersion": 2.1
},
{
"id": "2d514f17-01f8-4410-a759-50289488502f",
"name": "Check sid cookies header",
"type": "n8n-nodes-base.code",
"position": [
448,
1712
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "function getCookie(headers, name) {\n const raw = (headers.cookie || headers.Cookie || \"\");\n if (!raw) return null;\n\n const parts = raw.split(\";\").map(s => s.trim());\n for (const p of parts) {\n const eq = p.indexOf(\"=\");\n if (eq === -1) continue;\n const k = p.slice(0, eq).trim();\n const v = p.slice(eq + 1).trim();\n if (k === name) return decodeURIComponent(v);\n }\n return null;\n}\n\nconst headers = $json.headers || {};\nconst sid = getCookie(headers, \"sid\");\n\nreturn {\n json: {\n sid,\n hasCookieHeader: !!(headers.cookie || headers.Cookie),\n }\n};\n"
},
"typeVersion": 2
},
{
"id": "9d062264-f2c0-4659-b68f-714b03389ac4",
"name": "Search sid",
"type": "n8n-nodes-base.googleSheets",
"position": [
672,
1712
],
"parameters": {
"options": {},
"filtersUI": {
"values": [
{
"lookupValue": "={{ $json.sid || '' }}",
"lookupColumn": "sid"
}
]
},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Sheet2"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID"
}
},
"typeVersion": 4.7,
"alwaysOutputData": true
},
{
"id": "d7dd2d42-a6bf-4adb-9571-1736cdb11703",
"name": "If has data with sid",
"type": "n8n-nodes-base.if",
"position": [
880,
1712
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a29fd91f-24e3-4302-9735-25419166511b",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.username }}",
"rightValue": ""
}
]
}
},
"executeOnce": true,
"typeVersion": 2.2
},
{
"id": "89d72c2d-0f9a-4e63-a1e0-7245cdafad39",
"name": "Redirect to login",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1168,
1808
],
"parameters": {
"options": {},
"redirectURL": "={{ 'https://' + $('Profile').item.json.headers.host }}/form/login",
"respondWith": "redirect"
},
"typeVersion": 1.4
},
{
"id": "e13952a5-dc41-4429-a0ba-bb918e18e41f",
"name": "Prepare html",
"type": "n8n-nodes-base.code",
"position": [
1168,
1568
],
"parameters": {
"jsCode": "const html = `Hello ${$input.first().json.username}`;\n\nreturn { json: { html } };\n"
},
"typeVersion": 2
},
{
"id": "bbb5e14a-e5f1-4eff-acb0-e02203922a70",
"name": "Show profile page",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1392,
1568
],
"parameters": {
"options": {},
"respondWith": "text",
"responseBody": "={{ $json.html }}"
},
"typeVersion": 1.4
},
{
"id": "9496073d-aaa6-4d65-a2d5-a4ee7cd3c85c",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-896,
576
],
"parameters": {
"width": 880,
"height": 720,
"content": "## Overview\nThis workflow provides a lightweight authentication flow for n8n web portals using Google Sheets as a simple user/session store and email delivery for onboarding. It supports register, username/password login, one-click magic link auth, and a cookie-based profile page.\n\n### How it works\n1. User registers with an email address via a form\n2. n8n generates a username, password, and a one-time auth token\n3. User data is stored in Google Sheets\n4. n8n sends an email containing credentials + a one-click login link (`/auth?token=...`)\n5. User can log in with username/password or click the magic link\n6. `/auth` validates the token, creates a session id (sid), sets it as an HttpOnly cookie, and invalidates the token\n7. `/profile` reads the sid cookie, validates the session in Google Sheets, and displays user info to confirm login\n\n### Setup\n- Configure Gmail credentials in the Send email node\n- Create a Google Sheet with columns: username, password, role, token, sid\n- Add your Google Sheets URL (replace YOUR_SPREADSHEET_ID)\n- Replace YOUR_DOMAIN with your n8n instance domain in all nodes\n- Ensure your n8n instance is served over HTTPS so Secure cookies work\n\n### Customization\n- Adjust token expiry (e.g., 10-15 minutes) and one-time use behavior\n- Change session cookie settings (name, Max-Age, SameSite)\n- Customize the registration email content\n- Replace the Profile HTML with your own portal UI\n- Add a /logout endpoint to revoke sessions\n- Add rate-limiting at reverse proxy (Nginx/Traefik/Cloudflare)\n"
},
"typeVersion": 1
},
{
"parameters": {
"operation": "verify",
"email": "={{ $('Create password and token').item.json.email }}",
"additionalOptions": {}
},
"type": "n8n-nodes-billionverify.billionVerify",
"typeVersion": 1,
"position": [
568,
320
],
"name": "Verify Email (BillionVerify)",
"credentials": {
"billionVerifyApi": {
"id": "",
"name": "BillionVerify account"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "is-deliverable",
"leftValue": "={{ $json.is_deliverable }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
]
}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
748,
320
],
"name": "IF deliverable"
}
],
"connections": {
"Auth": {
"main": [
[
{
"node": "If has token",
"type": "main",
"index": 0
}
]
]
},
"Profile": {
"main": [
[
{
"node": "Check sid cookies header",
"type": "main",
"index": 0
}
]
]
},
"Search sid": {
"main": [
[
{
"node": "If has data with sid",
"type": "main",
"index": 0
}
]
]
},
"Prepare sid": {
"main": [
[
{
"node": "Redirect to profile and set cookies",
"type": "main",
"index": 0
},
{
"node": "Update sid",
"type": "main",
"index": 0
}
]
]
},
"If has token": {
"main": [
[
{
"node": "Search by token",
"type": "main",
"index": 0
}
],
[
{
"node": "Invalid token",
"type": "main",
"index": 0
}
]
]
},
"Prepare html": {
"main": [
[
{
"node": "Show profile page",
"type": "main",
"index": 0
}
]
]
},
"On form login": {
"main": [
[
{
"node": "Check username/password",
"type": "main",
"index": 0
}
]
]
},
"Generate token": {
"main": [
[
{
"node": "Add token to DB",
"type": "main",
"index": 0
}
]
]
},
"If token valid": {
"main": [
[
{
"node": "Prepare sid",
"type": "main",
"index": 0
}
],
[
{
"node": "Redirect to login form1",
"type": "main",
"index": 0
}
]
]
},
"Add token to DB": {
"main": [
[
{
"node": "Redirect to auth with new token",
"type": "main",
"index": 0
}
]
]
},
"Search by token": {
"main": [
[
{
"node": "If token valid",
"type": "main",
"index": 0
}
]
]
},
"If login success": {
"main": [
[
{
"node": "If has token in DB",
"type": "main",
"index": 0
}
],
[
{
"node": "Redirect to login form",
"type": "main",
"index": 0
}
]
]
},
"On form register": {
"main": [
[
{
"node": "Create password and token",
"type": "main",
"index": 0
}
]
]
},
"If has token in DB": {
"main": [
[
{
"node": "Redirect to auth url",
"type": "main",
"index": 0
}
],
[
{
"node": "Generate token",
"type": "main",
"index": 0
}
]
]
},
"If has data with sid": {
"main": [
[
{
"node": "Prepare html",
"type": "main",
"index": 0
}
],
[
{
"node": "Redirect to login",
"type": "main",
"index": 0
}
]
]
},
"Send username/password": {
"main": [
[
{
"node": "Notice register success",
"type": "main",
"index": 0
}
]
]
},
"Check username/password": {
"main": [
[
{
"node": "If login success",
"type": "main",
"index": 0
}
]
]
},
"Check sid cookies header": {
"main": [
[
{
"node": "Search sid",
"type": "main",
"index": 0
}
]
]
},
"Create password and token": {
"main": [
[
{
"node": "Append or update row in sheet",
"type": "main",
"index": 0
}
]
]
},
"Append or update row in sheet": {
"main": [
[
{
"node": "Verify Email (BillionVerify)",
"type": "main",
"index": 0
}
]
]
},
"Verify Email (BillionVerify)": {
"main": [
[
{
"node": "IF deliverable",
"type": "main",
"index": 0
}
]
]
},
"IF deliverable": {
"main": [
[
{
"node": "Send username/password",
"type": "main",
"index": 0
}
],
[]
]
}
},
"settings": {
"executionOrder": "v1"
}
}When to use this
- Cleaning a list before a Authentica send or sync.
- Protecting Authentica deliverability and sender reputation.
- Keeping bounce rates low so your sending is never throttled.
FAQ
Why verify before sending in Authentica?
Verifying first keeps your bounce rate low, which protects your sender reputation and your results.
How do I import this workflow?
Download the JSON, then in n8n go to Workflows → Import from File (or paste it). Install the BillionVerify community node and add your API key credential.
What happens to risky or catch-all addresses?
They are routed to the false branch and excluded from the send. You decide whether to retry, review, or drop them.
Add verification to your workflow
Create a free account, grab your API key, and stop bounces before they happen.
Get started free