The first message a new user gets sets the tone for the whole relationship — and for most teams it is the weakest thing they send all week. "Welcome! Here are five tips" reads like a robot, because the only alternative is fifteen to twenty minutes of research per signup, and nobody on a growing team has that.
So you pick one. Either the welcome is personal and doesn't scale, or it scales and isn't personal. Most teams quietly choose "isn't personal," and good signups cool off before anyone reaches out.
This workflow refuses the trade-off. Every new signup gets a researched, on-brand welcome drafted in seconds — waiting in Gmail and Slack for one click of approval. A genuinely great first touch, at scale, without anyone hand-researching and without an AI sending in your name.
The first-touch problem
A signup is the warmest a lead will ever be. They just raised their hand. Yet the moment that matters most is usually the one teams automate worst:
- Generic autoresponders treat a serious buyer the same as a tire-kicker — and read like it.
- Hand-written welcomes are great, but at any real volume they fall to the bottom of someone's list.
- Mail-merge
"Hi {{first_name}}"fools no one and signals the rest of the email is templated too.
The cost is invisible but real: a curious signup who would have replied to something thoughtful gets a form letter instead, and the conversation never starts.
What you get
A signup comes in. Twain researches the person in real time — who they are, the company they're from, and an angle worth opening with — then writes a welcome in your brand voice. It lands as a draft, ready for you to glance over and send.
- Researched, not templated. Each welcome is built on a real signal about that specific person, not a merged first name.
- On-brand at scale. It sounds like your best rep wrote it, every time, no matter the volume.
- Drafted for approval. Nothing goes out until you read it and hit send. You stay the author.
- Delivered where you work. A draft in Gmail and a summary in Slack — no new dashboard to babysit.
The warmth of a hand-written welcome, with the effort of clicking send.
The outcome in numbers
The pitch is simple: the slow, expensive part of a great welcome is already done before you've finished your coffee.
- ~15–20 minutes → ~20 seconds. A researched, personalized welcome that would take a human a quarter-hour is drafted in about twenty seconds.
- Research per signup, automatically. Every lead gets a real-time pass on who they are, their company, and a relevant opening angle — not a name plugged into a template.
- 100% coverage, 0 manual research. Every qualifying signup gets the same considered first touch, whether you get one a day or a hundred.
- About one credit per welcome. A trivial cost against the price of a lead that goes cold.
Do the math on a busy week: a hundred signups at fifteen minutes each is twenty-five hours of research nobody was ever going to do. This does it in the background and hands you drafts.
Why it lifts activation
First impressions compound. A signup who gets a message that clearly understands their world is far more likely to reply, book the call, and actually try the product — the difference between a name in a database and an activated user.
A generic autoresponder asks the user to do all the work of seeing why the product is for them. A researched welcome does that work for them, in the first thirty seconds of the relationship, when their intent is at its peak. That is where activation is won or lost.
You stay in control
The biggest reason teams hesitate to put AI near the inbox is brand risk — a hallucinated detail or an off-key line going out under your name. This workflow is built so that never happens.
- Draft-first, always. The message is created as a draft, never auto-sent. You approve every word.
- No off-brand surprises. The voice comes from an Agent and campaign you define once, so drafts sound like you, not like a generic model.
- Honest about the edge cases. If it can't personalize a signup, or if a draft can't be saved, it tells you plainly instead of pretending — so you never trust a draft that isn't really there.
You get AI's speed on the research and the writing, and keep a human's judgment on what actually ships.
Where it shows up
It meets your team where they already are. The welcome lands as a draft in Gmail — open Drafts, read it, send it. A Slack card posts the same moment with who signed up, the subject, the exact draft, and a link into Twain, so the whole team sees the first touch without leaving the channel they live in.
No new tool to learn, no tab to keep open. The work appears inside the inbox and the channel you already check all day.
Who this is for
This earns its place whenever the first human touch is worth getting right:
- Founder-led and product-led teams turning curious signups into conversations — without paying the research tax on every one.
- Demand-gen and growth who want top-of-funnel volume to feel personal instead of automated.
- RevOps standardizing a high-quality, on-brand first touch across every signup, not just the ones a rep happens to notice.
If you have ever looked at a new signup and thought "I should send them something good," this makes that the easy, automatic option — one you still approve, every time.
Import the template
Prefer to import rather than rebuild? Copy the sanitized JSON below — secrets removed, live IDs replaced with placeholders. Attach your own Twain, Gmail, and Slack connections after import, then point REPLACE_WITH_WELCOME_CAMPAIGN_ID at your welcome campaign.
{
"name": "Twain — Personalized Welcome Draft",
"settings": {
"executionOrder": "v1",
"availableInMCP": true,
"binaryMode": "separate"
},
"nodes": [
{
"id": "a9ecce14-53cd-4ab9-b3ef-aa1a22dc55b2",
"name": "Signup Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [240, 350],
"parameters": {
"httpMethod": "POST",
"path": "welcome-signup",
"authentication": "headerAuth",
"options": {}
}
},
{
"id": "dac1b12b-f538-4383-a42d-02b0f80b50a3",
"name": "Normalize Signup",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1000, 520],
"parameters": {
"assignments": {
"assignments": [
{
"id": "uid",
"name": "uid",
"value": "={{ $json.body?.uid ?? $json.uid }}",
"type": "string"
},
{
"id": "email",
"name": "email",
"value": "={{ ($json.body?.email ?? $json.email ?? \"\").toLowerCase().trim() }}",
"type": "string"
},
{
"id": "displayName",
"name": "displayName",
"value": "={{ $json.body?.displayName ?? $json.displayName ?? \"\" }}",
"type": "string"
},
{
"id": "signup_ts",
"name": "signup_ts",
"value": "={{ $json.body?.signup_ts ?? $json.signup_ts ?? $now.toISO() }}",
"type": "string"
},
{
"id": "campaign_id",
"name": "campaign_id",
"value": "REPLACE_WITH_WELCOME_CAMPAIGN_ID",
"type": "string"
},
{
"id": "linkedin_url",
"name": "linkedin_url",
"value": "={{ ($json.body?.linkedin_url ?? $json.body?.linkedin_profile_url ?? $json.linkedin_url ?? $json.linkedin_profile_url ?? \"\").trim() }}",
"type": "string"
},
{
"id": "email_domain",
"name": "email_domain",
"value": "={{ ((($json.body?.email ?? $json.email ?? \"\").toLowerCase().trim()).split(\"@\")[1] ?? \"\").toLowerCase() }}",
"type": "string"
},
{
"id": "is_personal_email",
"name": "is_personal_email",
"value": "={{ [\"gmail.com\",\"googlemail.com\",\"outlook.com\",\"hotmail.com\",\"live.com\",\"msn.com\",\"yahoo.com\",\"ymail.com\",\"icloud.com\",\"me.com\",\"mac.com\",\"proton.me\",\"protonmail.com\",\"aol.com\",\"gmx.com\",\"gmx.net\",\"zoho.com\",\"pm.me\",\"mail.com\",\"yandex.com\"].includes(((($json.body?.email ?? $json.email ?? \"\").toLowerCase().trim()).split(\"@\")[1] ?? \"\").toLowerCase()) }}",
"type": "boolean"
},
{
"id": "has_linkedin_url",
"name": "has_linkedin_url",
"value": "={{ Boolean(($json.body?.linkedin_url ?? $json.body?.linkedin_profile_url ?? $json.linkedin_url ?? $json.linkedin_profile_url ?? \"\").trim()) }}",
"type": "boolean"
},
{
"id": "has_work_email",
"name": "has_work_email",
"value": "={{ Boolean((($json.body?.email ?? $json.email ?? \"\").toLowerCase().trim())) && ![\"gmail.com\",\"googlemail.com\",\"outlook.com\",\"hotmail.com\",\"live.com\",\"msn.com\",\"yahoo.com\",\"ymail.com\",\"icloud.com\",\"me.com\",\"mac.com\",\"proton.me\",\"protonmail.com\",\"aol.com\",\"gmx.com\",\"gmx.net\",\"zoho.com\",\"pm.me\",\"mail.com\",\"yandex.com\"].includes((((($json.body?.email ?? $json.email ?? \"\").toLowerCase().trim()).split(\"@\")[1] ?? \"\").toLowerCase())) }}",
"type": "boolean"
}
]
},
"options": {}
}
},
{
"id": "e7710068-3b23-41ac-b67c-a63a38895fae",
"name": "Dedupe New Signups",
"type": "n8n-nodes-base.removeDuplicates",
"typeVersion": 2,
"position": [1760, 520],
"parameters": {
"operation": "removeItemsSeenInPreviousExecutions",
"dedupeValue": "={{ $json.uid }}",
"options": {
"scope": "node",
"historySize": 100000
}
}
},
{
"id": "6520ceb0-3c8a-4f0e-9eac-c790cb9554af",
"name": "Has usable contact?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [3660, 520],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 3
},
"conditions": [
{
"id": "usable-1",
"leftValue": "={{ Boolean($json.has_work_email) || Boolean($json.has_linkedin_url) }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {}
}
},
{
"id": "54a956e5-ecbe-4d5b-b5be-dc1c4bae97f3",
"name": "Slack: Skipped (cannot personalize)",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.5,
"position": [4040, 740],
"onError": "continueRegularOutput",
"parameters": {
"authentication": "accessToken",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "n8n-workflows"
},
"text": "=:no_entry_sign: *Twain — Personalized Welcome Draft — skipped*\n\n*{{ $('Normalize Signup').item.json.displayName || $('Normalize Signup').item.json.email || 'Unknown signup' }}*\n:email: {{ $('Normalize Signup').item.json.email || 'No email provided' }}\n\nNo work email, and the LinkedIn lookup didn't return a profile URL for this signup — so there's no one to personalize a welcome for. Enable a LinkedIn Lookup provider (or add a work email / LinkedIn URL upstream) to catch leads like this.\n\n<https://www.twain.ai/w|Open Twain workspace>",
"otherOptions": {
"includeLinkToWorkflow": false,
"unfurl_links": false,
"unfurl_media": false
}
}
},
{
"id": "7ebf6cbb-cb80-429f-97d6-46c2e609f28e",
"name": "Twain Generate Sequence",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [4040, 520],
"onError": "continueErrorOutput",
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 5000,
"parameters": {
"method": "POST",
"url": "https://public.api.twain.ai/v2/Generate/Sequence",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ {\n campaign_id: $('Normalize Signup').item.json.campaign_id,\n contact: {\n ...($('Normalize Signup').item.json.has_work_email ? { work_email: $('Normalize Signup').item.json.email } : {}),\n ...($json.has_linkedin_url ? { linkedin_profile_url: $json.linkedin_url } : {}),\n },\n add_contact_to_campaign: true,\n} }}",
"options": {
"timeout": 180000
}
}
},
{
"id": "9d4ccb2d-633f-4a4e-ae7a-90fcab334f5e",
"name": "Build Welcome Draft",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [4420, 520],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const api = $json;\nconst norm = $(\"Normalize Signup\").item.json;\nconst cfg = $(\"Channel Settings\").item.json;\nconst messages = Array.isArray(api.messages) ? api.messages : [];\n\n// --- Parse configurable step->channel split (1-based indices) ---\nconst parseSteps = (s) =>\n String(s ?? \"\")\n .split(\",\")\n .map((x) => parseInt(String(x).trim(), 10))\n .filter((n) => Number.isInteger(n) && n >= 1);\nlet emailSteps = parseSteps(cfg.email_steps);\nconst linkedinSteps = parseSteps(cfg.linkedin_steps);\n// Fall back to step 1 for email if none configured.\nif (emailSteps.length === 0) emailSteps = [1];\n\nconst stepMsg = (i1) => messages[i1 - 1] || {}; // i1 is 1-based\n\n// --- Email step(s) ---\nconst primaryEmailIdx = emailSteps[0]; // 1-based\nconst primary = stepMsg(primaryEmailIdx);\nconst esc = (s) =>\n String(s ?? \"\")\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\nconst toHtml = (s) => (s ? esc(s).split(\"\\n\").join(\"<br>\") : \"\");\n\nconst primarySubject = String(\n primary.subject_line ||\n api.subject_line ||\n \"Welcome to Twain, \" + (norm.displayName || \"there\"),\n);\nconst primaryBody = String(primary.message || \"\");\n\nconst email_message = {\n step: primaryEmailIdx,\n subject: primarySubject,\n body_text: primaryBody,\n body_html: toHtml(primaryBody),\n};\n\n// Preview of ALL email steps (for Slack), escape-safe single string.\nconst email_preview =\n emailSteps\n .map((i1) => {\n const m = stepMsg(i1);\n const subj = m.subject_line ? \"Subject: \" + m.subject_line + \"\\n\" : \"\";\n return subj + (m.message || \"\");\n })\n .filter(Boolean)\n .join(\"\\n\\n--------\\n\\n\") || \"No email steps.\";\n\n// --- LinkedIn step(s) ---\nconst linkedin_messages = linkedinSteps.map((i1) => ({\n step: i1,\n message: String(stepMsg(i1).message || \"\"),\n}));\nconst has_linkedin_steps = linkedin_messages.length > 0;\nconst linkedin_preview =\n linkedin_messages.map((m) => m.message).join(\"\\n\\n--------\\n\\n\") ||\n \"No LinkedIn steps.\";\n\n// --- Full sequence preview (unchanged, all steps) ---\nconst sequencePreview =\n messages\n .map((m, i) => {\n const subj = m.subject_line ? \"Subject: \" + m.subject_line + \"\\n\" : \"\";\n return subj + (m.message || \"\");\n })\n .join(\"\\n\\n--------\\n\\n\") || \"No messages returned.\";\n\n// --- Research / contact context ---\nconst research = api.research || {};\nconst person = research.person || {};\nconst company = research.company || {};\nconst warnings = api.warnings || {};\nconst categories = Array.isArray(warnings.categories) ? warnings.categories : [];\nconst linkedin_url = (person && person.linkedin_profile_url) || norm.linkedin_url || \"\";\nconst linkedin_line = linkedin_url ? \":link: <\" + linkedin_url + \"|LinkedIn profile>\" : \"\";\n// Prebuilt escape-safe warnings line (single token for Slack cards; empty when no warnings).\nconst warning_line =\n warnings && warnings.has_warnings\n ? \":warning: *Warnings:* \" +\n ((categories || []).join(\", \") || \"flagged\") +\n (warnings.summary ? \" — \" + warnings.summary : \"\")\n : \"\";\n\nreturn {\n to_email: norm.email || \"\",\n has_work_email: norm.has_work_email === true,\n // Primary email step (drives the Gmail draft + email Slack card)\n subject: email_message.subject,\n body_html: email_message.body_html,\n body_text: email_message.body_text,\n email_message: email_message,\n email_preview: email_preview,\n email_steps: emailSteps,\n // LinkedIn steps (drive HeyReach + LinkedIn Slack card)\n linkedin_messages: linkedin_messages,\n linkedin_preview: linkedin_preview,\n has_linkedin_steps: has_linkedin_steps,\n linkedin_steps: linkedinSteps,\n heyreach_campaign_id: cfg.heyreach_campaign_id || \"\",\n // Shared\n sequence_preview: sequencePreview,\n step_count: messages.length,\n person_name: person.name || norm.displayName || norm.email || \"Unknown\",\n person_title: person.title || \"\",\n company_name: company.name || \"\",\n linkedin_url: linkedin_url,\n linkedin_line: linkedin_line,\n link: api.link || \"\",\n warnings_line: categories.length ? categories.join(\", \") : \"\",\n warnings_summary: warnings.summary || \"\",\n warning_line: warning_line,\n}\n;\n"
}
},
{
"id": "a5b3a83d-78a7-46c6-8105-4fc3616e3a54",
"name": "Has Work Email?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [4800, 520],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 3
},
"conditions": [
{
"leftValue": "={{ $json.has_work_email }}",
"operator": {
"type": "boolean",
"operation": "true"
},
"rightValue": "",
"id": "hwe-1"
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {}
}
},
{
"id": "c30a33d9-1653-42ea-b6da-22e5361466c9",
"name": "Gmail: Create Welcome Draft",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.2,
"position": [5180, 520],
"onError": "continueErrorOutput",
"parameters": {
"resource": "draft",
"subject": "={{ $('Build Welcome Draft').item.json.subject }}",
"emailType": "html",
"message": "={{ $('Build Welcome Draft').item.json.body_html }}",
"options": {
"sendTo": "={{ $('Build Welcome Draft').item.json.to_email }}"
}
}
},
{
"id": "dd200fcf-04e3-4890-b5b3-12d68ce2837c",
"name": "Slack: Email draft ready",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.5,
"position": [5560, 740],
"onError": "continueRegularOutput",
"parameters": {
"authentication": "accessToken",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "n8n-workflows"
},
"text": "=:love_letter: *Welcome draft ready to review*\n\n*{{ $('Build Welcome Draft').item.json.person_name }}*{{ [$('Build Welcome Draft').item.json.person_title, $('Build Welcome Draft').item.json.company_name].filter(Boolean).length ? ' · ' + [$('Build Welcome Draft').item.json.person_title, $('Build Welcome Draft').item.json.company_name].filter(Boolean).join(' @ ') : '' }}\n\n:email: {{ $('Build Welcome Draft').item.json.to_email }}\n{{ $('Build Welcome Draft').item.json.linkedin_line }}\n{{ $('Build Welcome Draft').item.json.warning_line }}\n\n:memo: *_{{ $('Build Welcome Draft').item.json.subject }}_*\n```\n{{ ($('Build Welcome Draft').item.json.body_text || '').slice(0, 700) }}{{ ($('Build Welcome Draft').item.json.body_text || '').length > 700 ? '…' : '' }}\n```\n\n:inbox_tray: Saved as a Gmail draft — open *Drafts* to review and send.\n<{{ $('Build Welcome Draft').item.json.link }}|Open lead in Twain>",
"otherOptions": {
"includeLinkToWorkflow": false,
"unfurl_links": false,
"unfurl_media": false
}
}
},
{
"id": "e468da70-63cf-4712-9545-abdb76e032ac",
"name": "Slack: LinkedIn welcome ready",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.5,
"position": [5180, 740],
"onError": "continueRegularOutput",
"parameters": {
"authentication": "accessToken",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "n8n-workflows"
},
"text": "=:wave: *LinkedIn welcome ready — send manually*\n\n*{{ $('Build Welcome Draft').item.json.person_name }}*{{ [$('Build Welcome Draft').item.json.person_title, $('Build Welcome Draft').item.json.company_name].filter(Boolean).length ? ' · ' + [$('Build Welcome Draft').item.json.person_title, $('Build Welcome Draft').item.json.company_name].filter(Boolean).join(' @ ') : '' }}\n\n{{ $('Build Welcome Draft').item.json.linkedin_line }}\n{{ $('Build Welcome Draft').item.json.warning_line }}\n\nNo work email on file — send this via LinkedIn.\n\n:speech_balloon: *_Sequence · {{ $('Build Welcome Draft').item.json.step_count }} step{{ $('Build Welcome Draft').item.json.step_count == 1 ? '' : 's' }}_*\n```\n{{ ($('Build Welcome Draft').item.json.sequence_preview || '').slice(0, 1400) }}{{ ($('Build Welcome Draft').item.json.sequence_preview || '').length > 1400 ? '…' : '' }}\n```\n\n<{{ $('Build Welcome Draft').item.json.link }}|Open lead in Twain>",
"otherOptions": {
"includeLinkToWorkflow": false,
"unfurl_links": false,
"unfurl_media": false
}
}
},
{
"id": "40aa933a-b458-494b-81cc-9a1af2c7c52e",
"name": "Slack: Twain error",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.5,
"position": [4420, 740],
"onError": "continueRegularOutput",
"parameters": {
"authentication": "accessToken",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "n8n-workflows"
},
"text": "=:rotating_light: *Twain Sequence generation failed*\n\n*{{ $('Normalize Signup').item.json.displayName || $('Normalize Signup').item.json.email || 'Unknown signup' }}*\n:email: {{ $('Normalize Signup').item.json.email || 'No email provided' }}\n\n*Error:*\n{{ String($json.error?.message ?? $json.message ?? 'Unknown error').replace(/^\\s*\\d{3}\\s*-\\s*/, '').trim() }}\n\n<https://www.twain.ai/w|Open Twain workspace>",
"otherOptions": {
"includeLinkToWorkflow": false,
"unfurl_links": false,
"unfurl_media": false
}
}
},
{
"id": "5ad9d860-deb0-4c08-805f-4b7c33c95aec",
"name": "Schedule: BigQuery backfill",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.3,
"position": [240, 550],
"disabled": true,
"parameters": {
"rule": {
"interval": [
{
"field": "hours"
}
]
}
}
},
{
"id": "e3b85d9e-526e-41fc-b999-470587894488",
"name": "BigQuery: New signups",
"type": "n8n-nodes-base.googleBigQuery",
"typeVersion": 2.1,
"position": [620, 410],
"disabled": true,
"parameters": {
"authentication": "serviceAccount",
"projectId": {
"__rl": true,
"mode": "id",
"value": "REPLACE_WITH_GCP_PROJECT_ID"
},
"sqlQuery": "SELECT uid, email, display_name AS displayName, signup_ts\nFROM `your_dataset.signups`\nWHERE signup_ts > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)\nORDER BY signup_ts",
"options": {}
}
},
{
"id": "db1f67e1-e636-43dc-8514-1e34a56719e3",
"name": "Schedule: Firestore poll",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.3,
"position": [240, 750],
"disabled": true,
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 15
}
]
}
}
},
{
"id": "9c83ff7c-10e2-483b-b610-f519f1822843",
"name": "Firestore: New customers",
"type": "n8n-nodes-base.googleFirebaseCloudFirestore",
"typeVersion": 1.1,
"position": [620, 630],
"disabled": true,
"parameters": {
"authentication": "serviceAccount",
"operation": "query",
"projectId": "REPLACE_WITH_FIREBASE_PROJECT_ID",
"query": "{\n \"from\": [{ \"collectionId\": \"customers\" }],\n \"orderBy\": [{ \"field\": { \"fieldPath\": \"created_at\" }, \"direction\": \"DESCENDING\" }],\n \"limit\": 50\n}"
}
},
{
"id": "0f12cd20-0910-464a-a17f-0bfe6cbe8410",
"name": "Slack: Draft not created",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.5,
"position": [5560, 960],
"onError": "continueRegularOutput",
"parameters": {
"authentication": "accessToken",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "n8n-workflows"
},
"text": "=:warning: *Welcome ready — no Gmail draft created*\n\n*{{ $('Build Welcome Draft').item.json.person_name }}*{{ [$('Build Welcome Draft').item.json.person_title, $('Build Welcome Draft').item.json.company_name].filter(Boolean).length ? ' · ' + [$('Build Welcome Draft').item.json.person_title, $('Build Welcome Draft').item.json.company_name].filter(Boolean).join(' @ ') : '' }}\n\n:email: {{ $('Build Welcome Draft').item.json.to_email }}\n{{ $('Build Welcome Draft').item.json.linkedin_line }}\n{{ $('Build Welcome Draft').item.json.warning_line }}\n\nGmail isn't connected (or the draft step failed), so nothing was saved to Drafts. Connect Gmail to auto-draft, or copy the message below to send manually.\n\n:memo: *_{{ $('Build Welcome Draft').item.json.subject }}_*\n```\n{{ ($('Build Welcome Draft').item.json.body_text || '').slice(0, 900) }}{{ ($('Build Welcome Draft').item.json.body_text || '').length > 900 ? '…' : '' }}\n```\n\n<{{ $('Build Welcome Draft').item.json.link }}|Open lead in Twain>",
"otherOptions": {
"includeLinkToWorkflow": false,
"unfurl_links": false,
"unfurl_media": false
}
}
},
{
"id": "c6a473a8-39a1-457d-a14e-4a912dc1695a",
"name": "Needs LinkedIn Lookup?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [2140, 520],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 3
},
"conditions": [
{
"id": "needs-li-1",
"leftValue": "={{ !$('Normalize Signup').item.json.has_linkedin_url && Boolean($('Normalize Signup').item.json.email) }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {}
}
},
{
"id": "5919a455-0d50-4f31-9723-8fc6e5459004",
"name": "LinkedIn Lookup — Findymail",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [2520, 300],
"onError": "continueRegularOutput",
"parameters": {
"method": "POST",
"url": "https://app.findymail.com/api/search/reverse-email",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { email: $('Normalize Signup').item.json.email, with_profile: true } }}",
"options": {
"timeout": 30000
}
}
},
{
"id": "7897dcd7-fab8-4170-b2fa-799efdc8e6ba",
"name": "Apply Resolved LinkedIn",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [2900, 520],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Resolve LinkedIn URL from the provider response.\n// Adjust the field below to match YOUR provider's response shape.\nconst resp = $json || {};\nconst linkedin_url = resp.linkedin_url ?? resp.linkedin_profile_url ?? resp.url ?? resp.contact?.linkedin_url ?? resp.profile?.linkedin_url ?? resp.person?.linkedin_url ?? resp.data?.linkedin_url ?? resp.response?.linkedin_url ?? \"\";\n\n// Carry forward all normalized fields, overriding only the LinkedIn ones.\nconst n = $('Normalize Signup').item.json;\nreturn {\n ...n,\n linkedin_url: linkedin_url,\n has_linkedin_url: Boolean(linkedin_url)\n};\n"
}
},
{
"id": "5ce75f13-8405-4ca7-b7d3-5d297bd4b281",
"name": "Resolved Contact",
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [3280, 520],
"parameters": {}
},
{
"id": "1449224c-d6c4-4592-89c8-03632ecbda41",
"name": "Channel Settings",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1380, 520],
"parameters": {
"assignments": {
"assignments": [
{
"id": "email_steps",
"name": "email_steps",
"value": "1",
"type": "string"
},
{
"id": "linkedin_steps",
"name": "linkedin_steps",
"value": "",
"type": "string"
},
{
"id": "heyreach_campaign_id",
"name": "heyreach_campaign_id",
"value": "REPLACE_WITH_HEYREACH_CAMPAIGN_ID",
"type": "string"
}
]
},
"includeOtherFields": true,
"options": {}
}
},
{
"id": "d5683f20-f3e4-4869-9126-3815e1d1fd58",
"name": "Has LinkedIn steps?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [5940, 740],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "has-li-steps-1",
"leftValue": "={{ $('Build Welcome Draft').item.json.has_linkedin_steps }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {}
}
},
{
"id": "aaed263d-83ea-4521-9cf6-029f660f6156",
"name": "HeyReach: Add to Campaign",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [6320, 740],
"disabled": true,
"onError": "continueRegularOutput",
"parameters": {
"method": "POST",
"url": "https://api.heyreach.io/api/public/campaign/AddLeadsToCampaignV2",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ {\n campaignId: Number($('Build Welcome Draft').item.json.heyreach_campaign_id) || $('Build Welcome Draft').item.json.heyreach_campaign_id,\n accountLeadPairs: [{\n lead: {\n profileUrl: $('Build Welcome Draft').item.json.linkedin_url,\n firstName: ($('Build Welcome Draft').item.json.person_name || \"\").trim().split(\" \")[0] || \"\",\n lastName: ($('Build Welcome Draft').item.json.person_name || \"\").trim().split(\" \").slice(1).join(\" \") || \"\",\n companyName: $('Build Welcome Draft').item.json.company_name || \"\",\n emailAddress: $('Build Welcome Draft').item.json.to_email || \"\",\n customUserFields: [{ name: \"twain_message\", value: $('Build Welcome Draft').item.json.linkedin_preview || \"\" }]\n }\n }]\n} }}",
"options": {
"timeout": 30000
}
}
},
{
"id": "1cf2c115-5471-4b75-85fd-69d0bd961521",
"name": "Slack: LinkedIn steps queued (HeyReach)",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.5,
"position": [6700, 740],
"parameters": {
"authentication": "accessToken",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "n8n-workflows"
},
"text": "=:link: *LinkedIn step(s) queued — HeyReach*\n\n*{{ $('Build Welcome Draft').item.json.person_name }}*{{ [$('Build Welcome Draft').item.json.person_title, $('Build Welcome Draft').item.json.company_name].filter(Boolean).length ? ' · ' + [$('Build Welcome Draft').item.json.person_title, $('Build Welcome Draft').item.json.company_name].filter(Boolean).join(' @ ') : '' }}\n\n{{ $('Build Welcome Draft').item.json.linkedin_line }}\n{{ $('Build Welcome Draft').item.json.warning_line }}\n\n:speech_balloon: *_LinkedIn message_*\n```\n{{ ($('Build Welcome Draft').item.json.linkedin_preview || '').slice(0, 1400) }}{{ ($('Build Welcome Draft').item.json.linkedin_preview || '').length > 1400 ? '…' : '' }}\n```\n\n{{ $('HeyReach: Add to Campaign').isExecuted ? ':white_check_mark: Pushed to HeyReach campaign `' + ($('Build Welcome Draft').item.json.heyreach_campaign_id || '') + '`.' : ':wrench: *Configure HeyReach to enable* — add the `X-API-KEY` credential + `heyreach_campaign_id`, then enable `HeyReach: Add to Campaign`. Until then, send these manually.' }}\n<{{ $('Build Welcome Draft').item.json.link }}|Open lead in Twain>",
"otherOptions": {
"includeLinkToWorkflow": false,
"unfurl_links": false,
"unfurl_media": false,
"thread_ts": {
"replyValues": {
"thread_ts": "={{ $('Slack: Email draft ready').isExecuted ? $('Slack: Email draft ready').item.json.message_timestamp : ($('Slack: Draft not created').isExecuted ? $('Slack: Draft not created').item.json.message_timestamp : $('Slack: LinkedIn welcome ready').item.json.message_timestamp) }}"
}
}
}
}
},
{
"id": "fabef85e-7d22-44ef-a7ce-0e405de52044",
"name": "LinkedIn Lookup — Apollo",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [2520, 80],
"disabled": true,
"onError": "continueRegularOutput",
"parameters": {
"method": "POST",
"url": "https://api.apollo.io/api/v1/people/match",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { email: $('Normalize Signup').item.json.email } }}",
"options": {
"timeout": 30000
}
}
},
{
"id": "3bd76e34-b31b-4930-b6b6-45be11a056f9",
"name": "LinkedIn Lookup — People Data Labs",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [2520, -140],
"disabled": true,
"onError": "continueRegularOutput",
"parameters": {
"url": "=https://api.peopledatalabs.com/v5/person/enrich?email={{ encodeURIComponent($('Normalize Signup').item.json.email) }}",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"options": {
"timeout": 30000
}
}
},
{
"id": "78642433-b136-4e9a-bb8f-e86878ec6af8",
"name": "LinkedIn Lookup — Proxycurl",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [2520, -360],
"disabled": true,
"onError": "continueRegularOutput",
"parameters": {
"url": "=https://nubela.co/proxycurl/api/linkedin/profile/resolve/email?email={{ encodeURIComponent($('Normalize Signup').item.json.email) }}",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"options": {
"timeout": 30000
}
}
}
],
"connections": {
"Signup Webhook": {
"main": [
[
{
"node": "Normalize Signup",
"type": "main",
"index": 0
}
]
]
},
"Twain Generate Sequence": {
"main": [
[
{
"node": "Build Welcome Draft",
"type": "main",
"index": 0
}
],
[
{
"node": "Slack: Twain error",
"type": "main",
"index": 0
}
]
]
},
"Build Welcome Draft": {
"main": [
[
{
"node": "Has Work Email?",
"type": "main",
"index": 0
}
]
]
},
"Has Work Email?": {
"main": [
[
{
"node": "Gmail: Create Welcome Draft",
"type": "main",
"index": 0
}
],
[
{
"node": "Slack: LinkedIn welcome ready",
"type": "main",
"index": 0
}
]
]
},
"Gmail: Create Welcome Draft": {
"main": [
[
{
"node": "Slack: Email draft ready",
"type": "main",
"index": 0
}
],
[
{
"node": "Slack: Draft not created",
"type": "main",
"index": 0
}
]
]
},
"Schedule: BigQuery backfill": {
"main": [
[
{
"node": "BigQuery: New signups",
"type": "main",
"index": 0
}
]
]
},
"BigQuery: New signups": {
"main": [
[
{
"node": "Normalize Signup",
"type": "main",
"index": 0
}
]
]
},
"Schedule: Firestore poll": {
"main": [
[
{
"node": "Firestore: New customers",
"type": "main",
"index": 0
}
]
]
},
"Firestore: New customers": {
"main": [
[
{
"node": "Normalize Signup",
"type": "main",
"index": 0
}
]
]
},
"Needs LinkedIn Lookup?": {
"main": [
[
{
"node": "LinkedIn Lookup — Findymail",
"type": "main",
"index": 0
},
{
"node": "LinkedIn Lookup — Apollo",
"type": "main",
"index": 0
},
{
"node": "LinkedIn Lookup — People Data Labs",
"type": "main",
"index": 0
},
{
"node": "LinkedIn Lookup — Proxycurl",
"type": "main",
"index": 0
}
],
[
{
"node": "Resolved Contact",
"type": "main",
"index": 1
}
]
]
},
"Apply Resolved LinkedIn": {
"main": [
[
{
"node": "Resolved Contact",
"type": "main",
"index": 0
}
]
]
},
"Normalize Signup": {
"main": [
[
{
"node": "Channel Settings",
"type": "main",
"index": 0
}
]
]
},
"Channel Settings": {
"main": [
[
{
"node": "Dedupe New Signups",
"type": "main",
"index": 0
}
]
]
},
"Has LinkedIn steps?": {
"main": [
[
{
"node": "HeyReach: Add to Campaign",
"type": "main",
"index": 0
}
]
]
},
"HeyReach: Add to Campaign": {
"main": [
[
{
"node": "Slack: LinkedIn steps queued (HeyReach)",
"type": "main",
"index": 0
}
]
]
},
"LinkedIn Lookup — Findymail": {
"main": [
[
{
"node": "Apply Resolved LinkedIn",
"type": "main",
"index": 0
}
]
]
},
"LinkedIn Lookup — Apollo": {
"main": [
[
{
"node": "Apply Resolved LinkedIn",
"type": "main",
"index": 0
}
]
]
},
"LinkedIn Lookup — People Data Labs": {
"main": [
[
{
"node": "Apply Resolved LinkedIn",
"type": "main",
"index": 0
}
]
]
},
"LinkedIn Lookup — Proxycurl": {
"main": [
[
{
"node": "Apply Resolved LinkedIn",
"type": "main",
"index": 0
}
]
]
},
"Slack: Email draft ready": {
"main": [
[
{
"node": "Has LinkedIn steps?",
"type": "main",
"index": 0
}
]
]
},
"Slack: Draft not created": {
"main": [
[
{
"node": "Has LinkedIn steps?",
"type": "main",
"index": 0
}
]
]
},
"Slack: LinkedIn welcome ready": {
"main": [
[
{
"node": "Has LinkedIn steps?",
"type": "main",
"index": 0
}
]
]
},
"Dedupe New Signups": {
"main": [
[
{
"node": "Needs LinkedIn Lookup?",
"type": "main",
"index": 0
}
]
]
},
"Resolved Contact": {
"main": [
[
{
"node": "Has usable contact?",
"type": "main",
"index": 0
}
]
]
},
"Has usable contact?": {
"main": [
[
{
"node": "Twain Generate Sequence",
"type": "main",
"index": 0
}
],
[
{
"node": "Slack: Skipped (cannot personalize)",
"type": "main",
"index": 0
}
]
]
}
}
}