AutomationFlowsEmail & Gmail › Draft Personalized Welcome Emails for New Signups with Twain, Gmail, and Slack

Draft Personalized Welcome Emails for New Signups with Twain, Gmail, and Slack

ByMohamed Yahya Chahin @motwain on n8n.io

This workflow receives new signup events, enriches each contact with Twain (and optionally a LinkedIn lookup), drafts a personalized welcome email in Gmail, and posts review-ready previews to Slack, with optional LinkedIn-step handoff to HeyReach. Receives a new signup via a…

Webhook trigger★★★★★ complexity35 nodesSlackHTTP RequestGmailGoogle BigQueryGoogle Firebase Cloud Firestore
Email & Gmail Trigger: Webhook Nodes: 35 Complexity: ★★★★★ Added:
Draft Personalized Welcome Emails for New Signups with Twain, Gmail, and Slack — n8n workflow card showing Slack, HTTP Request, Gmail integration

This workflow corresponds to n8n.io template #17102 — we link there as the canonical source.

This workflow follows the Gmail → HTTP Request recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "k9fWvQnQXpPQ4CBU",
  "meta": {
    "builderVariant": "mcp",
    "aiBuilderAssisted": true
  },
  "name": "Draft personalized welcome emails for new signups with Twain",
  "tags": [
    {
      "id": "wzeP6rYkNyfX1gKe",
      "name": "Welcome message",
      "createdAt": "2026-07-13T09:44:49.851Z",
      "updatedAt": "2026-07-13T09:44:49.851Z"
    }
  ],
  "nodes": [
    {
      "id": "a9ecce14-53cd-4ab9-b3ef-aa1a22dc55b2",
      "name": "Signup Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        240,
        352
      ],
      "parameters": {
        "path": "welcome-signup",
        "options": {},
        "httpMethod": "POST",
        "authentication": "headerAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "dac1b12b-f538-4383-a42d-02b0f80b50a3",
      "name": "Normalize Signup",
      "type": "n8n-nodes-base.set",
      "position": [
        1008,
        528
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "uid",
              "name": "uid",
              "type": "string",
              "value": "={{ $json.body?.uid ?? $json.uid }}"
            },
            {
              "id": "email",
              "name": "email",
              "type": "string",
              "value": "={{ ($json.body?.email ?? $json.email ?? \"\").toLowerCase().trim() }}"
            },
            {
              "id": "displayName",
              "name": "displayName",
              "type": "string",
              "value": "={{ $json.body?.displayName ?? $json.displayName ?? \"\" }}"
            },
            {
              "id": "signup_ts",
              "name": "signup_ts",
              "type": "string",
              "value": "={{ $json.body?.signup_ts ?? $json.signup_ts ?? $now.toISO() }}"
            },
            {
              "id": "campaign_id",
              "name": "campaign_id",
              "type": "string",
              "value": "REPLACE_WITH_WELCOME_CAMPAIGN_ID"
            },
            {
              "id": "linkedin_url",
              "name": "linkedin_url",
              "type": "string",
              "value": "={{ ($json.body?.linkedin_url ?? $json.body?.linkedin_profile_url ?? $json.linkedin_url ?? $json.linkedin_profile_url ?? \"\").trim() }}"
            },
            {
              "id": "email_domain",
              "name": "email_domain",
              "type": "string",
              "value": "={{ ((($json.body?.email ?? $json.email ?? \"\").toLowerCase().trim()).split(\"@\")[1] ?? \"\").toLowerCase() }}"
            },
            {
              "id": "is_personal_email",
              "name": "is_personal_email",
              "type": "boolean",
              "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()) }}"
            },
            {
              "id": "has_linkedin_url",
              "name": "has_linkedin_url",
              "type": "boolean",
              "value": "={{ Boolean(($json.body?.linkedin_url ?? $json.body?.linkedin_profile_url ?? $json.linkedin_url ?? $json.linkedin_profile_url ?? \"\").trim()) }}"
            },
            {
              "id": "has_work_email",
              "name": "has_work_email",
              "type": "boolean",
              "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())) }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "e7710068-3b23-41ac-b67c-a63a38895fae",
      "name": "Dedupe New Signups",
      "type": "n8n-nodes-base.removeDuplicates",
      "position": [
        1760,
        528
      ],
      "parameters": {
        "options": {
          "scope": "node",
          "historySize": 100000
        },
        "operation": "removeItemsSeenInPreviousExecutions",
        "dedupeValue": "={{ $json.uid }}"
      },
      "typeVersion": 2
    },
    {
      "id": "6520ceb0-3c8a-4f0e-9eac-c790cb9554af",
      "name": "Has usable contact?",
      "type": "n8n-nodes-base.if",
      "position": [
        3664,
        528
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "usable-1",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ Boolean($json.has_work_email) || Boolean($json.has_linkedin_url) }}",
              "rightValue": ""
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.3
    },
    {
      "id": "54a956e5-ecbe-4d5b-b5be-dc1c4bae97f3",
      "name": "Slack: Skipped (cannot personalize)",
      "type": "n8n-nodes-base.slack",
      "onError": "continueRegularOutput",
      "position": [
        4048,
        752
      ],
      "parameters": {
        "text": "=:no_entry_sign:  *Twain \u2014 Personalized Welcome Draft \u2014 skipped*\n*{{ $('Normalize Signup').item.json.displayName || $('Normalize Signup').item.json.email || 'Unknown signup' }}*\n{{ $('Normalize Signup').item.json.email || 'No email provided' }}\nNo work email, and the LinkedIn lookup didn't return a profile URL for this signup \u2014 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<https://www.twain.ai/w|Open Twain workspace>",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Channel Settings').first().json.slack_channel }}"
        },
        "otherOptions": {
          "unfurl_links": false,
          "unfurl_media": false,
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.5
    },
    {
      "id": "7ebf6cbb-cb80-429f-97d6-46c2e609f28e",
      "name": "Twain Generate Sequence",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueErrorOutput",
      "maxTries": 3,
      "position": [
        4048,
        528
      ],
      "parameters": {
        "url": "https://public.api.twain.ai/v2/Generate/Sequence",
        "method": "POST",
        "options": {
          "timeout": 180000
        },
        "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} }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.4,
      "waitBetweenTries": 5000
    },
    {
      "id": "9d4ccb2d-633f-4a4e-ae7a-90fcab334f5e",
      "name": "Build Welcome Draft",
      "type": "n8n-nodes-base.code",
      "position": [
        4432,
        528
      ],
      "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, \"&amp;\")\n    .replace(/</g, \"&lt;\")\n    .replace(/>/g, \"&gt;\");\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 ? \"  \u2014  \" + 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"
      },
      "typeVersion": 2
    },
    {
      "id": "a5b3a83d-78a7-46c6-8105-4fc3616e3a54",
      "name": "Has Work Email?",
      "type": "n8n-nodes-base.if",
      "position": [
        4800,
        528
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "hwe-1",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.has_work_email }}",
              "rightValue": ""
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.3
    },
    {
      "id": "c30a33d9-1653-42ea-b6da-22e5361466c9",
      "name": "Gmail: Create Welcome Draft",
      "type": "n8n-nodes-base.gmail",
      "onError": "continueErrorOutput",
      "position": [
        5184,
        528
      ],
      "parameters": {
        "message": "={{ $('Build Welcome Draft').item.json.body_html }}",
        "options": {
          "sendTo": "={{ $('Build Welcome Draft').item.json.to_email }}"
        },
        "subject": "={{ $('Build Welcome Draft').item.json.subject }}",
        "resource": "draft",
        "emailType": "html"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "dd200fcf-04e3-4890-b5b3-12d68ce2837c",
      "name": "Slack: Email draft ready",
      "type": "n8n-nodes-base.slack",
      "onError": "continueRegularOutput",
      "position": [
        5568,
        608
      ],
      "parameters": {
        "text": "=*Welcome draft ready to review*\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 ? '  \u00b7  ' + [$('Build Welcome Draft').item.json.person_title, $('Build Welcome Draft').item.json.company_name].filter(Boolean).join(' @ ') : '' }}\n{{ $('Build Welcome Draft').item.json.to_email }}\n{{ $('Build Welcome Draft').item.json.linkedin_line }}\n{{ $('Build Welcome Draft').item.json.warning_line }}\n*_{{ $('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 ? '\u2026' : '' }}\n```\nSaved as a Gmail draft \u2014 open *Drafts* to review and send.\n<{{ $('Build Welcome Draft').item.json.link }}|Open lead in Twain>",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Channel Settings').first().json.slack_channel }}"
        },
        "otherOptions": {
          "unfurl_links": false,
          "unfurl_media": false,
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.5
    },
    {
      "id": "e468da70-63cf-4712-9545-abdb76e032ac",
      "name": "Slack: LinkedIn welcome ready",
      "type": "n8n-nodes-base.slack",
      "onError": "continueRegularOutput",
      "position": [
        5184,
        752
      ],
      "parameters": {
        "text": "=*LinkedIn welcome ready \u2014 send manually*\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 ? '  \u00b7  ' + [$('Build Welcome Draft').item.json.person_title, $('Build Welcome Draft').item.json.company_name].filter(Boolean).join(' @ ') : '' }}\n{{ $('Build Welcome Draft').item.json.linkedin_line }}\n{{ $('Build Welcome Draft').item.json.warning_line }}\nNo work email on file \u2014 send this via LinkedIn.\n*_Sequence \u00b7 {{ $('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 ? '\u2026' : '' }}\n```\n<{{ $('Build Welcome Draft').item.json.link }}|Open lead in Twain>",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Channel Settings').first().json.slack_channel }}"
        },
        "otherOptions": {
          "unfurl_links": false,
          "unfurl_media": false,
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.5
    },
    {
      "id": "40aa933a-b458-494b-81cc-9a1af2c7c52e",
      "name": "Slack: Twain error",
      "type": "n8n-nodes-base.slack",
      "onError": "continueRegularOutput",
      "position": [
        4432,
        752
      ],
      "parameters": {
        "text": "=:rotating_light:  *Twain Sequence generation failed*\n*{{ $('Normalize Signup').item.json.displayName || $('Normalize Signup').item.json.email || 'Unknown signup' }}*\n{{ $('Normalize Signup').item.json.email || 'No email provided' }}\n*Error:*\n{{ String($json.error?.message ?? $json.message ?? 'Unknown error').replace(/^\\s*\\d{3}\\s*-\\s*/, '').trim() }}\n<https://www.twain.ai/w|Open Twain workspace>",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Channel Settings').first().json.slack_channel }}"
        },
        "otherOptions": {
          "unfurl_links": false,
          "unfurl_media": false,
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.5
    },
    {
      "id": "5ad9d860-deb0-4c08-805f-4b7c33c95aec",
      "name": "Schedule: BigQuery backfill",
      "type": "n8n-nodes-base.scheduleTrigger",
      "disabled": true,
      "position": [
        240,
        560
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours"
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "e3b85d9e-526e-41fc-b999-470587894488",
      "name": "BigQuery: New signups",
      "type": "n8n-nodes-base.googleBigQuery",
      "disabled": true,
      "position": [
        624,
        416
      ],
      "parameters": {
        "options": {},
        "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",
        "projectId": {
          "__rl": true,
          "mode": "id",
          "value": "REPLACE_WITH_GCP_PROJECT_ID"
        },
        "authentication": "serviceAccount"
      },
      "typeVersion": 2.1
    },
    {
      "id": "db1f67e1-e636-43dc-8514-1e34a56719e3",
      "name": "Schedule: Firestore poll",
      "type": "n8n-nodes-base.scheduleTrigger",
      "disabled": true,
      "position": [
        240,
        752
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 15
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "9c83ff7c-10e2-483b-b610-f519f1822843",
      "name": "Firestore: New customers",
      "type": "n8n-nodes-base.googleFirebaseCloudFirestore",
      "disabled": true,
      "position": [
        624,
        640
      ],
      "parameters": {
        "query": "{\n  \"from\": [{ \"collectionId\": \"customers\" }],\n  \"orderBy\": [{ \"field\": { \"fieldPath\": \"created_at\" }, \"direction\": \"DESCENDING\" }],\n  \"limit\": 50\n}",
        "operation": "query",
        "projectId": "REPLACE_WITH_FIREBASE_PROJECT_ID",
        "authentication": "serviceAccount"
      },
      "typeVersion": 1.1
    },
    {
      "id": "d664e839-8753-466c-bbac-7a5cf4993683",
      "name": "Sticky: Twain onboarding",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        144,
        -192
      ],
      "parameters": {
        "color": 7,
        "width": 368,
        "height": 304,
        "content": "## Twain onboarding (first time)\n1. Create an account & workspace at **https://www.twain.ai**\n2. Build a **new-user-welcome campaign** \u2014 its Agent decides the welcome copy + research depth (`research_rules`).\n3. Copy that **campaign_id** into *Normalize Signup* (replace `REPLACE_WITH_WELCOME_CAMPAIGN_ID`).\n4. Get an **API key** from workspace settings for the Header Auth credential.\n\nGenerate/Sequence costs 1 credit per signup.\n\n\ud83d\udcd6 **Full walkthrough:** https://www.twain.ai/blog/n8n-welcome-draft"
      },
      "typeVersion": 1
    },
    {
      "id": "f3581017-c5eb-4085-8529-cc76eae91154",
      "name": "Sticky: Credentials",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        528,
        -192
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 304,
        "content": "## Credentials to wire\n- **Twain Generate Sequence** \u2192 Header Auth credential: Name `X-Api-Key`, Value your `twain-\u2026` key.\n- **Gmail: Create Welcome Draft** \u2192 Gmail OAuth2 (the founder mailbox where drafts land).\n- **Slack** nodes \u2192 Slack OAuth2 (auto-binds if one exists).\n- **Webhook** \u2192 enable Header Auth before going live (set a shared secret matched by your caller)."
      },
      "typeVersion": 1
    },
    {
      "id": "0f12cd20-0910-464a-a17f-0bfe6cbe8410",
      "name": "Slack: Draft not created",
      "type": "n8n-nodes-base.slack",
      "onError": "continueRegularOutput",
      "position": [
        5568,
        832
      ],
      "parameters": {
        "text": "=*Welcome ready \u2014 no Gmail draft created*\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 ? '  \u00b7  ' + [$('Build Welcome Draft').item.json.person_title, $('Build Welcome Draft').item.json.company_name].filter(Boolean).join(' @ ') : '' }}\n{{ $('Build Welcome Draft').item.json.to_email }}\n{{ $('Build Welcome Draft').item.json.linkedin_line }}\n{{ $('Build Welcome Draft').item.json.warning_line }}\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*_{{ $('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 ? '\u2026' : '' }}\n```\n<{{ $('Build Welcome Draft').item.json.link }}|Open lead in Twain>",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Channel Settings').first().json.slack_channel }}"
        },
        "otherOptions": {
          "unfurl_links": false,
          "unfurl_media": false,
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.5
    },
    {
      "id": "c6a473a8-39a1-457d-a14e-4a912dc1695a",
      "name": "Needs LinkedIn Lookup?",
      "type": "n8n-nodes-base.if",
      "position": [
        2144,
        528
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "needs-li-1",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ !$('Normalize Signup').item.json.has_linkedin_url && Boolean($('Normalize Signup').item.json.email) }}",
              "rightValue": ""
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.3
    },
    {
      "id": "5919a455-0d50-4f31-9723-8fc6e5459004",
      "name": "LinkedIn Lookup \u2014 Findymail",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        2528,
        304
      ],
      "parameters": {
        "url": "https://app.findymail.com/api/search/reverse-email",
        "method": "POST",
        "options": {
          "timeout": 30000
        },
        "jsonBody": "={{ { email: $('Normalize Signup').item.json.email, with_profile: true } }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth"
      },
      "credentials": {
        "httpBearerAuth": {
          "name": "<your credential>"
        },
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "7897dcd7-fab8-4170-b2fa-799efdc8e6ba",
      "name": "Apply Resolved LinkedIn",
      "type": "n8n-nodes-base.code",
      "position": [
        2912,
        528
      ],
      "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"
      },
      "typeVersion": 2
    },
    {
      "id": "5ce75f13-8405-4ca7-b7d3-5d297bd4b281",
      "name": "Resolved Contact",
      "type": "n8n-nodes-base.merge",
      "position": [
        3280,
        528
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "1449224c-d6c4-4592-89c8-03632ecbda41",
      "name": "Channel Settings",
      "type": "n8n-nodes-base.set",
      "position": [
        1392,
        528
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "email_steps",
              "name": "email_steps",
              "type": "string",
              "value": "1"
            },
            {
              "id": "linkedin_steps",
              "name": "linkedin_steps",
              "type": "string",
              "value": ""
            },
            {
              "id": "heyreach_campaign_id",
              "name": "heyreach_campaign_id",
              "type": "string",
              "value": "REPLACE_WITH_HEYREACH_CAMPAIGN_ID"
            },
            {
              "id": "slack_channel",
              "name": "slack_channel",
              "type": "string",
              "value": "welcome-signups"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "d5683f20-f3e4-4869-9126-3815e1d1fd58",
      "name": "Has LinkedIn steps?",
      "type": "n8n-nodes-base.if",
      "position": [
        5952,
        752
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "has-li-steps-1",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $('Build Welcome Draft').item.json.has_linkedin_steps }}",
              "rightValue": ""
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.3
    },
    {
      "id": "aaed263d-83ea-4521-9cf6-029f660f6156",
      "name": "HeyReach: Add to Campaign",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "disabled": true,
      "position": [
        6320,
        752
      ],
      "parameters": {
        "url": "https://api.heyreach.io/api/public/campaign/AddLeadsToCampaignV2",
        "method": "POST",
        "options": {
          "timeout": 30000
        },
        "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} }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "typeVersion": 4.4
    },
    {
      "id": "1cf2c115-5471-4b75-85fd-69d0bd961521",
      "name": "Slack: LinkedIn steps queued (HeyReach)",
      "type": "n8n-nodes-base.slack",
      "position": [
        6704,
        752
      ],
      "parameters": {
        "text": "=*LinkedIn step(s) queued \u2014 HeyReach*\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 ? '  \u00b7  ' + [$('Build Welcome Draft').item.json.person_title, $('Build Welcome Draft').item.json.company_name].filter(Boolean).join(' @ ') : '' }}\n{{ $('Build Welcome Draft').item.json.linkedin_line }}\n{{ $('Build Welcome Draft').item.json.warning_line }}\n*_LinkedIn message_*\n```\n{{ ($('Build Welcome Draft').item.json.linkedin_preview || '').slice(0, 1400) }}{{ ($('Build Welcome Draft').item.json.linkedin_preview || '').length > 1400 ? '\u2026' : '' }}\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* \u2014 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>",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Channel Settings').first().json.slack_channel }}"
        },
        "otherOptions": {
          "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) }}"
            }
          },
          "unfurl_links": false,
          "unfurl_media": false,
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.5
    },
    {
      "id": "fabef85e-7d22-44ef-a7ce-0e405de52044",
      "name": "LinkedIn Lookup \u2014 Apollo",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "disabled": true,
      "position": [
        2528,
        80
      ],
      "parameters": {
        "url": "https://api.apollo.io/api/v1/people/match",
        "method": "POST",
        "options": {
          "timeout": 30000
        },
        "jsonBody": "={{ { email: $('Normalize Signup').item.json.email } }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "typeVersion": 4.4
    },
    {
      "id": "3bd76e34-b31b-4930-b6b6-45be11a056f9",
      "name": "LinkedIn Lookup \u2014 People Data Labs",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "disabled": true,
      "position": [
        2528,
        -144
      ],
      "parameters": {
        "url": "=https://api.peopledatalabs.com/v5/person/enrich?email={{ encodeURIComponent($('Normalize Signup').item.json.email) }}",
        "options": {
          "timeout": 30000
        },
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "typeVersion": 4.4
    },
    {
      "id": "78642433-b136-4e9a-bb8f-e86878ec6af8",
      "name": "LinkedIn Lookup \u2014 Proxycurl",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "disabled": true,
      "position": [
        2528,
        -368
      ],
      "parameters": {
        "url": "=https://nubela.co/proxycurl/api/linkedin/profile/resolve/email?email={{ encodeURIComponent($('Normalize Signup').item.json.email) }}",
        "options": {
          "timeout": 30000
        },
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "typeVersion": 4.4
    },
    {
      "id": "fa726d21-affd-4edb-b081-0a69a16ae3aa",
      "name": "Sticky: Try it out",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -816,
        -192
      ],
      "parameters": {
        "width": 920,
        "height": 1218,
        "content": "## Try It Out!\n\n**The moment someone signs up, this workflow automatically drafts a personalized welcome message for them** \u2014 researched and written by Twain, ready for a human to review and send. No more generic \"thanks for signing up.\"\n\nIt normalizes the signup, researches the person with **Twain** (resolving a LinkedIn URL first if needed), generates a welcome sequence, saves the email as a **Gmail draft**, and posts it to Slack for approval. LinkedIn-only signups get the message to send manually; optional LinkedIn steps can be queued in HeyReach. **Nothing is sent automatically.**\n\n### Who's it for\n\nFounder-led and PLG / sales teams who want every new signup to get a warm, personalized welcome touch \u2014 with zero manual writing.\n\n### How it works\n\n- **Signup comes in** \u2014 via webhook (or an optional BigQuery / Firestore backfill), normalized and de-duplicated by `uid`.\n- **Resolve contact** \u2014 personal-email signups get a LinkedIn / work-email lookup so there's a real person to research.\n- **Research + write** \u2014 Twain researches the person and generates a personalized welcome sequence.\n- **Draft & notify** \u2014 saves a Gmail draft (work email) or posts the message to Slack (LinkedIn-only); optional LinkedIn steps \u2192 HeyReach; everything posted to Slack for review.\n\n### How to set up\n\n- **Set `campaign_id`** in **Normalize Signup** to your Twain welcome campaign; set `slack_channel`, `email_steps` / `linkedin_steps`, and `heyreach_campaign_id` in **Channel Settings**.\n- **Add credentials:** Twain (HTTP Header Auth, header `X-Api-Key`), Gmail, and Slack \u2014 plus, if used, a LinkedIn-lookup provider and HeyReach.\n- **Trigger:** POST signups to the **Signup Webhook** as `{ email, displayName, linkedin_url, uid, signup_ts }`, or enable a backfill trigger.\n\n### Requirements\n\nA **Twain** account + API key with a welcome campaign; **Gmail** and **Slack**. Optional: a LinkedIn-lookup provider and **HeyReach** for LinkedIn steps.\n\n### How to customize\n\n- Route sequence steps to email vs LinkedIn with `email_steps` / `linkedin_steps`.\n- Swap the lookup provider, or enable a BigQuery / Firestore backfill trigger.\n\nBuilt with Twain \u2014 https://www.twain.ai"
      },
      "typeVersion": 1
    },
    {
      "id": "90ea72c4-85de-499b-8f14-4e344b99ec3b",
      "name": "Sticky: Section 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        144,
        128
      ],
      "parameters": {
        "color": 7,
        "width": 1860,
        "height": 900,
        "content": "### 1 \u00b7 Intake & dedupe\n\nA new signup arrives via the webhook (or an optional BigQuery / Firestore backfill), is normalized to a common shape, and de-duplicated by `uid`."
      },
      "typeVersion": 1
    },
    {
      "id": "06655a66-9588-4e31-887b-970c2413b580",
      "name": "Sticky: Section 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2048,
        -528
      ],
      "parameters": {
        "color": 7,
        "width": 1800,
        "height": 1556,
        "content": "### 2 \u00b7 Resolve the contact\n\nWhen a signup has no LinkedIn URL, resolve one from the email so Twain has a real person to research. **Findymail** is the default provider; **Apollo, People Data Labs, and Proxycurl** are pre-built disabled alternatives \u2014 enable one and add its API-key credential."
      },
      "typeVersion": 1
    },
    {
      "id": "3f9bdf77-9ad8-4a8f-bc86-d0024e1400b4",
      "name": "Sticky: Section 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3888,
        128
      ],
      "parameters": {
        "color": 7,
        "width": 1120,
        "height": 900,
        "content": "### 3 \u00b7 Research & write\n\nTwain researches the person and generates the personalized welcome sequence; skipped signups and errors are reported to Slack."
      },
      "typeVersion": 1
    },
    {
      "id": "d410c035-abf6-424a-81e7-be233d401405",
      "name": "Sticky: Section 4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5040,
        128
      ],
      "parameters": {
        "color": 7,
        "width": 1980,
        "height": 900,
        "content": "### 4 \u00b7 Deliver & route\n\nWork email \u2192 Gmail draft; LinkedIn-only \u2192 Slack message to send manually; optional LinkedIn steps \u2192 HeyReach. Everything posted to Slack for approval \u2014 nothing auto-sends."
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": true,
    "executionOrder": "v1"
  },
  "versionId": "88db415c-dbd5-44bd-a587-779cf4c361b0",
  "nodeGroups": [],
  "connections": {
    "Signup Webhook": {
      "main": [
        [
          {
            "node": "Normalize Signup",
            "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
          }
        ]
      ]
    },
    "Channel Settings": {
      "main": [
        [
          {
            "node": "Dedupe New Signups",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Signup": {
      "main": [
        [
          {
            "node": "Channel Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Resolved Contact": {
      "main": [
        [
          {
            "node": "Has usable contact?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dedupe New Signups": {
      "main": [
        [
          {
            "node": "Needs LinkedIn Lookup?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Welcome Draft": {
      "main": [
        [
          {
            "node": "Has Work Email?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has LinkedIn steps?": {
      "main": [
        [
          {
            "node": "HeyReach: Add to Campaign",
            "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
          }
        ]
      ]
    },
    "BigQuery: New signups": {
      "main": [
        [
          {
            "node": "Normalize Signup",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Needs LinkedIn Lookup?": {
      "main": [
        [
          {
            "node": "LinkedIn Lookup \u2014 Findymail",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Resolved Contact",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Apply Resolved LinkedIn": {
      "main": [
        [
          {
            "node": "Resolved Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Twain Generate Sequence": {
      "main": [
        [
          {
            "node": "Build Welcome Draft",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack: Twain error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Firestore: New customers": {
      "main": [
        [
          {
            "node": "Normalize Signup",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule: Firestore poll": {
      "main": [
        [
          {
            "node": "Firestore: New customers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack: Draft not created": {
      "main": [
        [
          {
            "node": "Has LinkedIn steps?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack: Email draft ready": {
      "main": [
        [
          {
            "node": "Has LinkedIn steps?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HeyReach: Add to Campaign": {
      "main": [
        [
          {
            "node": "Slack: LinkedIn steps queued (HeyReach)",
            "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
          }
        ]
      ]
    },
    "LinkedIn Lookup \u2014 Findymail": {
      "main": [
        [
          {
            "node": "Apply Resolved LinkedIn",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack: LinkedIn welcome ready": {
      "main": [
        [
          {
            "node": "Has LinkedIn steps?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow receives new signup events, enriches each contact with Twain (and optionally a LinkedIn lookup), drafts a personalized welcome email in Gmail, and posts review-ready previews to Slack, with optional LinkedIn-step handoff to HeyReach. Receives a new signup via a…

Source: https://n8n.io/workflows/17102/ — original creator credit. Request a take-down →

More Email & Gmail workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Email & Gmail

Receive orders via webhook with automatic payment verification, stock checking, inventory reservation, shipping label generation, confirmation emails, and Slack notifications.

HTTP Request, Gmail, Slack +1
Email & Gmail

Transform your visitor management process with this fully automated, enterprise-grade workflow. The Verified Visitor Pass Generator eliminates manual data entry, prevents fake registrations through em

N8N Nodes Htmlcsstoimage, Gmail, Slack +3
Email & Gmail

Automated email verification and welcome email workflow that validates new user signups, prevents fake emails, and creates a seamless onboarding experience with real-time team notifications.

N8N Nodes Verifiemail, Slack, Google Sheets +2
Email & Gmail

Automate press credential verification and badge generation for journalists covering your events

Stop And Error, Gmail, Slack +3
Email & Gmail

This workflow automates HR onboarding by capturing new hires via a webhook, saving them to Google Sheets, emailing a Slack invite via Gmail, and notifying managers in Slack, then listening for Slack t

Google Sheets, Gmail, Slack +1