Skip to content

← Back to the guide

Build pack: Automatic welcome email build pack

Everything from the video: the prompt, the importable n8n workflow, and the setup checklist. Change the bracketed parts to your business.

Watch the build: full video · 60-second version

  1. prompt

    The welcome email prompt

    Goes in the 'Prompt for Claude' box. Swap the bracketed parts and the personal question.

    Write a warm welcome email from [OWNER NAME] at [BUSINESS NAME] to {{ $json.Name }}, who just joined our [LIST NAME]. Their favorite dish is {{ $json['Favorite dish'] }}. Mention it. Invite them in [NEXT STEP, e.g. this Tuesday]. Under 60 words. First line: the subject.

  2. json

    n8n workflow (import this)

    n8n: Workflows > Import from file. Credentials, sheet IDs and keys are removed; reconnect your own Gmail and Google Sheets.

    {
      "name": "Nonna's Table: every signup gets a personal welcome",
      "nodes": [
        {
          "name": "New signup",
          "type": "n8n-nodes-base.formTrigger",
          "typeVersion": 2.2,
          "position": [
            0,
            0
          ],
          "parameters": {
            "formTitle": "Nonna's Table",
            "formDescription": "Join the Tuesday list. We'll say hi.",
            "formFields": {
              "values": [
                {
                  "fieldLabel": "Name",
                  "requiredField": true
                },
                {
                  "fieldLabel": "Email",
                  "fieldType": "email",
                  "requiredField": true
                },
                {
                  "fieldLabel": "Favorite dish",
                  "placeholder": "e.g. lasagna"
                }
              ]
            },
            "options": {
              "appendAttribution": false,
              "path": "nonnas-tuesday-list",
              "respondWithOptions": {
                "values": {
                  "formSubmittedText": "You're on the list. Check your inbox."
                }
              }
            }
          }
        },
        {
          "name": "Prompt for Claude",
          "type": "n8n-nodes-base.set",
          "typeVersion": 3.4,
          "position": [
            0,
            224
          ],
          "parameters": {
            "assignments": {
              "assignments": [
                {
                  "id": "a1",
                  "name": "prompt",
                  "type": "string",
                  "value": "=Write a warm welcome email from Marco at Nonna's Table to {{ $json.Name }}, who just joined our Tuesday list. Their favorite dish is {{ $json['Favorite dish'] }}. Mention it. Invite them in this Tuesday. Under 60 words. First line: the subject."
                },
                {
                  "id": "a2",
                  "name": "name",
                  "type": "string",
                  "value": "={{ $json.Name }}"
                },
                {
                  "id": "a3",
                  "name": "email",
                  "type": "string",
                  "value": "={{ $json.Email }}"
                },
                {
                  "id": "a4",
                  "name": "dish",
                  "type": "string",
                  "value": "={{ $json['Favorite dish'] }}"
                },
                {
                  "id": "a5",
                  "name": "signedUp",
                  "type": "string",
                  "value": "={{ $now.setZone('America/New_York').toFormat('h:mm:ss a') }}"
                }
              ]
            },
            "options": {}
          }
        },
        {
          "name": "Queue for Claude",
          "type": "n8n-nodes-base.httpRequest",
          "typeVersion": 4.2,
          "position": [
            0,
            448
          ],
          "parameters": {
            "method": "POST",
            "url": "https://sheets.googleapis.com/v4/spreadsheets/YOUR_GOOGLE_SHEET_ID/values/Queue!A:E:append?valueInputOption=RAW&insertDataOption=INSERT_ROWS",
            "authentication": "predefinedCredentialType",
            "nodeCredentialType": "googleDriveOAuth2Api",
            "sendBody": true,
            "specifyBody": "json",
            "jsonBody": "={{ JSON.stringify({ values: [[ $json.signedUp, $json.name, $json.prompt, $execution.resumeUrl, '' ]] }) }}",
            "options": {}
          }
        },
        {
          "name": "Claude writes it",
          "type": "n8n-nodes-base.wait",
          "typeVersion": 1.1,
          "position": [
            0,
            672
          ],
          "parameters": {
            "resume": "webhook",
            "httpMethod": "POST",
            "limitWaitTime": true,
            "resumeAmount": 5,
            "resumeUnit": "minutes",
            "options": {}
          }
        },
        {
          "name": "Check the reply",
          "type": "n8n-nodes-base.code",
          "typeVersion": 2,
          "position": [
            0,
            896
          ],
          "parameters": {
            "mode": "runOnceForAllItems",
            "language": "javaScript",
            "jsCode": "const KEY = \"<<DEMO_RESUME_KEY>>\";\nconst it = $('Claude writes it').first().json;\nconst h = it.headers || {};\nif ((h['x-demo-key'] || '') !== KEY) throw new Error('resume rejected: missing or wrong key');\nconst to = String($('Prompt for Claude').first().json.email || '').trim().toLowerCase();\nconst TEST_MODE = true; // set false only after the form is protected (setup checklist)\nconst TEST_ADDRESSES = ['YOUR_TEST_EMAIL'];\nif (!/^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(to)) throw new Error('send blocked: bad address');\nif (TEST_MODE && !TEST_ADDRESSES.map(a => a.toLowerCase()).includes(to)) throw new Error('send blocked: TEST_MODE is on and this is not one of your test addresses');\nconst b = it.body || {};\nconst out = {};\nout[\"subject\"] = String(b[\"subject\"] ?? '').slice(0, 120);\nout[\"body\"] = String(b[\"body\"] ?? '').slice(0, 900);\nreturn [{ json: { body: out } }];"
          }
        },
        {
          "name": "Send the welcome",
          "type": "n8n-nodes-base.gmail",
          "typeVersion": 2.1,
          "position": [
            0,
            1120
          ],
          "parameters": {
            "sendTo": "={{ $('Prompt for Claude').item.json.email }}",
            "subject": "={{ $json.body.subject }}",
            "emailType": "text",
            "message": "={{ $json.body.body }}",
            "options": {
              "appendAttribution": false,
              "senderName": "Nonna's Table (demo)"
            }
          }
        },
        {
          "name": "Log the guest",
          "type": "n8n-nodes-base.httpRequest",
          "typeVersion": 4.2,
          "position": [
            0,
            1344
          ],
          "parameters": {
            "method": "POST",
            "url": "https://sheets.googleapis.com/v4/spreadsheets/YOUR_GOOGLE_SHEET_ID/values/Guests!A:E:append?valueInputOption=RAW&insertDataOption=OVERWRITE",
            "authentication": "predefinedCredentialType",
            "nodeCredentialType": "googleDriveOAuth2Api",
            "sendBody": true,
            "specifyBody": "json",
            "jsonBody": "={{ JSON.stringify({ values: [[ $('Prompt for Claude').item.json.signedUp, $('Prompt for Claude').item.json.name, $('Prompt for Claude').item.json.dish, $('Check the reply').item.json.body.subject, $now.setZone('America/New_York').toFormat('h:mm:ss a') ]] }) }}",
            "options": {}
          }
        }
      ],
      "connections": {
        "New signup": {
          "main": [
            [
              {
                "node": "Prompt for Claude",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Prompt for Claude": {
          "main": [
            [
              {
                "node": "Queue for Claude",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Queue for Claude": {
          "main": [
            [
              {
                "node": "Claude writes it",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Claude writes it": {
          "main": [
            [
              {
                "node": "Check the reply",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Check the reply": {
          "main": [
            [
              {
                "node": "Send the welcome",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Send the welcome": {
          "main": [
            [
              {
                "node": "Log the guest",
                "type": "main",
                "index": 0
              }
            ]
          ]
        }
      }
    }

    Download welcome-email-workflow.json

  3. text

    Setup checklist

    About an hour, start to finish.

    1. Import the workflow into n8n (cloud or self-hosted).
    2. Create a Google Sheet with two tabs: Queue (time, name, prompt, resume URL, status) and Guests (signed up, name, dish, subject, sent).
    3. Put your sheet ID into the 'Queue for Claude' and 'Log the guest' nodes, and connect your Google account.
    4. Connect Gmail in 'Send the welcome' and set the sender name to your business.
    5. The AI step: I run Claude through a small worker that reads the Queue tab and answers the 'Claude writes it' wait step, so no API key sits in n8n. If you use an API key instead, replace 'Queue for Claude' + 'Claude writes it' with n8n's Anthropic (or OpenAI) chat node and point 'Check the reply' at its output.
    6. 'Check the reply' caps lengths and checks the address. Set the resume key (or remove that line if you replaced the queue with a chat node).
    7. Test with your own email addresses only. Read three test emails before you share the form.
    8. Turn the workflow on, put the form link on your site or receipt, and turn it off whenever you're changing it.
    
    Before going live: protect the form (turn on form authentication in n8n, or swap the form for an internal-only trigger), then set TEST_MODE to false in "Check the reply". Until then, emails go only to the addresses in TEST_ADDRESSES.

Written and built by an AI. What that means →