AutomationFlowsMarketing & Ads › Run A/b-tested Email Campaigns Using Gmail, Google Sheets, and Slack

Run A/b-tested Email Campaigns Using Gmail, Google Sheets, and Slack

ByManu @manu on n8n.io

Run professional email campaigns with A/B testing, Google Sheets tracking, and Slack analytics. FEATURES:

Event trigger★★★★★ complexity35 nodesGoogle SheetsSlackGmailError Trigger
Marketing & Ads Trigger: Event Nodes: 35 Complexity: ★★★★★ Added:

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

This workflow follows the Error Trigger → Gmail 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
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "a44f48be-93ff-41cc-afdf-2b43e6605334",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -1456,
        -1136
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "04e236ab-786c-47d8-8bb6-6765e78e2a79",
      "name": "Scheduled Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -1456,
        -928
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * 1"
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "f0d78564-b3c3-4739-9313-285fbbb019a0",
      "name": "Webhook Trigger",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -1456,
        -736
      ],
      "parameters": {
        "path": "email-campaign",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 2
    },
    {
      "id": "8a5d6585-c5a0-4bbb-98c5-c988744356dc",
      "name": "Configure Campaign",
      "type": "n8n-nodes-base.code",
      "position": [
        -1120,
        -944
      ],
      "parameters": {
        "jsCode": "// CONFIG - Edit these values for your campaign\nconst CONFIG = {\n  // Company info\n  companyName: 'Your Company',\n  senderEmail: 'user@example.com',\n  primaryColor: '#667eea',\n  secondaryColor: '#764ba2',\n  logoUrl: '', // Optional: Your logo URL\n  \n  // Send settings\n  batchSize: 10, // Emails per batch\n  delayBetweenEmails: 2, // Seconds between emails\n  delayBetweenBatches: 10, // Seconds between batches\n  maxRetries: 3, // Retries per failed email\n  \n  // Current campaign\n  campaignName: 'December 2024 Newsletter',\n  emailSubject: 'December Updates - {{name}}',\n  alternativeSubject: 'Something special for you, {{name}}', // For A/B testing\n  enableABTest: true, // 50% receives each subject\n  \n  // Segmentation (filter by 'segment' column in Sheet)\n  targetSegment: '', // Empty for all, or 'premium', 'new', etc.\n  \n  // Tracking\n  trackOpens: true,\n  trackClicks: true\n};\n\n// Generate unique campaign ID\nconst campaignId = `CAMP-${Date.now()}-${Math.random().toString(36).substr(2, 6).toUpperCase()}`;\n\nreturn {\n  json: {\n    ...CONFIG,\n    campaignId: campaignId,\n    startedAt: new Date().toISOString(),\n    startedBy: $input.item.json.body?.user || 'manual'\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "e525ffa4-ed51-439a-aff5-79f1909656af",
      "name": "Read Contacts",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -880,
        -944
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "Contacts"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_DOCUMENT_ID"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "745aa533-3a91-4f2d-9bec-42e717b31cd7",
      "name": "Filter and Validate",
      "type": "n8n-nodes-base.code",
      "position": [
        -640,
        -944
      ],
      "parameters": {
        "jsCode": "const config = $('Configure Campaign').first().json;\nconst contacts = $input.all();\n\n// Email validation regex\nconst emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\nconst results = {\n  valid: [],\n  invalid: [],\n  alreadySent: [],\n  unsubscribed: [],\n  otherSegment: []\n};\n\nfor (const item of contacts) {\n  const c = item.json;\n  const email = (c.email || '').trim().toLowerCase();\n  const sent = (c.sent || '').toLowerCase();\n  const unsub = (c.unsubscribed || '').toLowerCase();\n  const segment = (c.segment || '').toLowerCase();\n  \n  // Validations\n  if (!emailRegex.test(email)) {\n    results.invalid.push({ ...c, reason: 'Invalid email' });\n  } else if (sent === 'yes' || sent === 'true') {\n    results.alreadySent.push(c);\n  } else if (unsub === 'yes' || unsub === 'true') {\n    results.unsubscribed.push(c);\n  } else if (config.targetSegment && segment !== config.targetSegment.toLowerCase()) {\n    results.otherSegment.push(c);\n  } else {\n    // Assign A/B variant\n    const variant = config.enableABTest ? (Math.random() < 0.5 ? 'A' : 'B') : 'A';\n    results.valid.push({\n      ...c,\n      email: email,\n      abVariant: variant,\n      subject: variant === 'A' ? config.emailSubject : config.alternativeSubject\n    });\n  }\n}\n\n// Return stats and valid contacts\nreturn {\n  json: {\n    campaignId: config.campaignId,\n    stats: {\n      totalContacts: contacts.length,\n      valid: results.valid.length,\n      invalid: results.invalid.length,\n      alreadySent: results.alreadySent.length,\n      unsubscribed: results.unsubscribed.length,\n      otherSegment: results.otherSegment.length,\n      variantA: results.valid.filter(c => c.abVariant === 'A').length,\n      variantB: results.valid.filter(c => c.abVariant === 'B').length\n    },\n    validContacts: results.valid,\n    invalidContacts: results.invalid\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "18947fc1-e4f3-40c1-ac25-6a23b52950bb",
      "name": "Log Campaign Start",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -320,
        -1072
      ],
      "parameters": {
        "columns": {
          "value": {
            "Name": "={{ $('Configure Campaign').item.json.campaignName }}",
            "Valid": "={{ $json.stats.valid }}",
            "Status": "In Progress",
            "Invalid": "={{ $json.stats.invalid }}",
            "Variant A": "={{ $json.stats.variantA }}",
            "Variant B": "={{ $json.stats.variantB }}",
            "Start Date": "={{ $('Configure Campaign').item.json.startedAt }}",
            "Campaign ID": "={{ $('Configure Campaign').item.json.campaignId }}",
            "Already Sent": "={{ $json.stats.alreadySent }}",
            "Unsubscribed": "={{ $json.stats.unsubscribed }}",
            "Total Contacts": "={{ $json.stats.totalContacts }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "Campaigns"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_DOCUMENT_ID"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "0475ab01-0a9c-44c7-ad91-d56f13e5b3cc",
      "name": "Slack - Campaign Started",
      "type": "n8n-nodes-base.slack",
      "position": [
        -320,
        -864
      ],
      "parameters": {
        "text": ":rocket: *Email campaign started*",
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "typeVersion": 2
    },
    {
      "id": "5a4cecc7-024d-4816-8e0c-8c6b442d84e8",
      "name": "Has Contacts?",
      "type": "n8n-nodes-base.if",
      "position": [
        0,
        -912
      ],
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{ $json.stats.valid }}",
              "operation": "larger"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "9ce81cbd-f277-4456-9d53-3ca04871b7b6",
      "name": "Extract Contacts",
      "type": "n8n-nodes-base.code",
      "position": [
        304,
        -1104
      ],
      "parameters": {
        "jsCode": "const data = $('Filter and Validate').first().json;\nreturn data.validContacts.map(c => ({ json: c }));"
      },
      "typeVersion": 2
    },
    {
      "id": "45b1abf9-44e9-4555-beb6-3af669c66f44",
      "name": "Process in Batches",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        544,
        -1104
      ],
      "parameters": {
        "options": {},
        "batchSize": "={{ $('Configure Campaign').first().json.batchSize }}"
      },
      "typeVersion": 3
    },
    {
      "id": "e37f2133-00ab-4b35-a35c-313d7b7d25a7",
      "name": "Prepare Email",
      "type": "n8n-nodes-base.code",
      "position": [
        784,
        -1104
      ],
      "parameters": {
        "jsCode": "const config = $('Configure Campaign').first().json;\nconst contact = $input.item.json;\n\n// Variables to replace\nconst variables = {\n  '{{name}}': contact.name || 'User',\n  '{{email}}': contact.email,\n  '{{company}}': config.companyName,\n  '{{date}}': new Date().toLocaleDateString('en-US'),\n  '{{campaignId}}': config.campaignId\n};\n\n// Replace variables in subject\nlet subject = contact.subject;\nfor (const [key, value] of Object.entries(variables)) {\n  subject = subject.replace(new RegExp(key.replace(/[{}]/g, '\\\\$&'), 'g'), value);\n}\n\n// EMAIL CONTENT - Customize here\nlet content = `\n  <h2 style=\"color:#2d3748;margin:0 0 20px;\">Hi {{name}}!</h2>\n  <p style=\"color:#4a5568;font-size:16px;line-height:1.8;\">\n    We hope you're having a great day. We're reaching out from <strong>{{company}}</strong> \n    to share our latest updates with you.\n  </p>\n  \n  <div style=\"background:linear-gradient(135deg, ${config.primaryColor}15, ${config.secondaryColor}15);border-radius:12px;padding:25px;margin:25px 0;\">\n    <h3 style=\"color:${config.primaryColor};margin:0 0 15px;\">Highlights</h3>\n    <ul style=\"color:#4a5568;font-size:15px;line-height:1.8;margin:0;padding-left:20px;\">\n      <li>New features available</li>\n      <li>Performance improvements</li>\n      <li>Exclusive content for you</li>\n    </ul>\n  </div>\n  \n  <div style=\"text-align:center;margin:30px 0;\">\n    <a href=\"https://yourcompany.com?utm_campaign={{campaignId}}&utm_source=email\" \n       style=\"display:inline-block;background:linear-gradient(135deg, ${config.primaryColor}, ${config.secondaryColor});color:#fff;text-decoration:none;padding:16px 40px;border-radius:10px;font-size:16px;font-weight:600;box-shadow:0 4px 15px ${config.primaryColor}40;\">\n      Learn More\n    </a>\n  </div>\n  \n  <p style=\"color:#4a5568;font-size:16px;line-height:1.8;\">\n    If you have any questions, just reply to this email. We're here to help!\n  </p>\n  \n  <p style=\"color:#4a5568;font-size:16px;line-height:1.8;margin-top:25px;\">\n    Best regards,<br>\n    <strong>The {{company}} Team</strong>\n  </p>\n`;\n\n// Replace variables in content\nfor (const [key, value] of Object.entries(variables)) {\n  content = content.replace(new RegExp(key.replace(/[{}]/g, '\\\\$&'), 'g'), value);\n}\n\n// Complete HTML template\nconst emailHtml = `\n<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n</head>\n<body style=\"margin:0;padding:0;font-family:'Segoe UI',Roboto,Arial,sans-serif;background:#f4f7fa;\">\n  <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#f4f7fa;padding:40px 20px;\">\n    <tr>\n      <td align=\"center\">\n        <table width=\"600\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#ffffff;border-radius:16px;box-shadow:0 4px 20px rgba(0,0,0,0.08);overflow:hidden;\">\n          \n          <!-- Header -->\n          <tr>\n            <td style=\"background:linear-gradient(135deg, ${config.primaryColor} 0%, ${config.secondaryColor} 100%);padding:40px 30px;text-align:center;\">\n              ${config.logoUrl ? `<img src=\"${config.logoUrl}\" alt=\"Logo\" style=\"max-height:50px;margin-bottom:15px;\">` : ''}\n              <h1 style=\"margin:0;color:#ffffff;font-size:26px;font-weight:600;\">${config.companyName}</h1>\n            </td>\n          </tr>\n          \n          <!-- Content -->\n          <tr>\n            <td style=\"padding:40px 30px;\">\n              ${content}\n            </td>\n          </tr>\n          \n          <!-- Footer -->\n          <tr>\n            <td style=\"background:#f7fafc;padding:30px;text-align:center;border-top:1px solid #e2e8f0;\">\n              <p style=\"margin:0 0 15px;color:#718096;font-size:13px;\">You received this email because you're subscribed to our newsletter.</p>\n              <p style=\"margin:0 0 15px;\">\n                <a href=\"https://yourcompany.com/unsubscribe?email={{email}}&campaign={{campaignId}}\" style=\"color:#e53e3e;font-size:12px;text-decoration:none;\">Unsubscribe</a>\n                <span style=\"color:#cbd5e0;margin:0 10px;\">|</span>\n                <a href=\"https://yourcompany.com/preferences?email={{email}}\" style=\"color:${config.primaryColor};font-size:12px;text-decoration:none;\">Preferences</a>\n              </p>\n              <p style=\"margin:0;color:#a0aec0;font-size:11px;\">${new Date().getFullYear()} ${config.companyName}. All rights reserved.</p>\n            </td>\n          </tr>\n          \n        </table>\n      </td>\n    </tr>\n  </table>\n</body>\n</html>\n`.replace(/\\{\\{email\\}\\}/g, contact.email).replace(/\\{\\{campaignId\\}\\}/g, config.campaignId);\n\nreturn {\n  json: {\n    // Contact info\n    rowNumber: contact.row_number,\n    to: contact.email,\n    name: contact.name,\n    abVariant: contact.abVariant,\n    \n    // Email\n    subject: subject,\n    htmlContent: emailHtml,\n    \n    // Tracking\n    campaignId: config.campaignId,\n    sentAt: new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "3f827ce6-859a-4e38-93d3-3c6eeba08678",
      "name": "Send Email",
      "type": "n8n-nodes-base.gmail",
      "onError": "continueErrorOutput",
      "position": [
        1024,
        -1104
      ],
      "parameters": {
        "sendTo": "={{ $json.to }}",
        "message": "={{ $json.htmlContent }}",
        "options": {
          "appendAttribution": false
        },
        "subject": "={{ $json.subject }}"
      },
      "typeVersion": 2
    },
    {
      "id": "c56b051b-1983-4cfd-8df0-456617318e3e",
      "name": "Mark as Sent",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1328,
        -1120
      ],
      "parameters": {
        "columns": {
          "value": {
            "sent": "yes",
            "variant": "={{ $json.abVariant }}",
            "campaign": "={{ $json.campaignId }}",
            "sent_date": "={{ $json.sentAt }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "Contacts"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_DOCUMENT_ID"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "edbd1dbb-af35-49bd-90b9-f69078271eb8",
      "name": "Mark as Error",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1328,
        -912
      ],
      "parameters": {
        "columns": {
          "value": {
            "sent": "error",
            "error_msg": "={{ $json.error?.message || 'Send error' }}",
            "sent_date": "={{ new Date().toISOString() }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "Contacts"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_DOCUMENT_ID"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "713d5010-4403-4697-88d6-7d8eeeeb5a69",
      "name": "Log Success",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1568,
        -1120
      ],
      "parameters": {
        "columns": {
          "value": {
            "Name": "={{ $json.name }}",
            "Email": "={{ $json.to }}",
            "Status": "Sent",
            "Subject": "={{ $json.subject }}",
            "Variant": "={{ $json.abVariant }}",
            "Timestamp": "={{ $json.sentAt }}",
            "Campaign ID": "={{ $json.campaignId }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "Send Log"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_DOCUMENT_ID"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "da20478c-52fc-4fcc-a9cc-a41a82fafe9d",
      "name": "Log Error",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1568,
        -912
      ],
      "parameters": {
        "columns": {
          "value": {
            "Name": "={{ $json.name }}",
            "Email": "={{ $json.to }}",
            "Status": "Error",
            "Subject": "={{ $json.subject }}",
            "Variant": "={{ $json.abVariant }}",
            "Timestamp": "={{ new Date().toISOString() }}",
            "Campaign ID": "={{ $('Configure Campaign').item.json.campaignId }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "Send Log"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_DOCUMENT_ID"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "80c70256-8229-4859-ac47-64500c69fa1a",
      "name": "Anti-Spam Delay",
      "type": "n8n-nodes-base.wait",
      "position": [
        1808,
        -1008
      ],
      "parameters": {
        "amount": "={{ $('Configure Campaign').first().json.delayBetweenEmails }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "59fee3b4-a166-4c1a-b412-7d9ff4fc7e3e",
      "name": "Calculate Results",
      "type": "n8n-nodes-base.code",
      "position": [
        320,
        -528
      ],
      "parameters": {
        "jsCode": "// Runs when there are no valid contacts\nconst stats = $('Filter and Validate').first().json.stats;\n\nreturn {\n  json: {\n    message: 'No valid contacts to send',\n    stats: stats\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "1ff96564-189f-4d05-9109-2ffb14637013",
      "name": "Final Summary",
      "type": "n8n-nodes-base.code",
      "position": [
        528,
        -528
      ],
      "parameters": {
        "jsCode": "const config = $('Configure Campaign').first().json;\nconst initialStats = $('Filter and Validate').first().json.stats;\n\n// Calculate duration\nconst start = new Date(config.startedAt);\nconst end = new Date();\nconst durationMs = end - start;\nconst durationMin = Math.round(durationMs / 60000);\n\nreturn {\n  json: {\n    campaignId: config.campaignId,\n    campaignName: config.campaignName,\n    \n    // Times\n    startedAt: config.startedAt,\n    finishedAt: end.toISOString(),\n    duration: durationMin < 1 ? 'Less than 1 minute' : `${durationMin} minutes`,\n    \n    // Statistics\n    totalProcessed: initialStats.valid,\n    variantA: initialStats.variantA,\n    variantB: initialStats.variantB,\n    \n    // Summary text\n    summary: `Campaign \"${config.campaignName}\" completed. ${initialStats.valid} emails processed in ${durationMin < 1 ? '<1' : durationMin} min.`\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "10643128-6bc0-4f07-b46d-e1c828b0c022",
      "name": "Update Campaign",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        768,
        -528
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "Completed",
            "Duration": "={{ $json.duration }}",
            "End Date": "={{ $json.finishedAt }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "Campaigns"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_DOCUMENT_ID"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "d5d81e23-862d-49da-8125-c7ca2c3b5e41",
      "name": "Slack - Campaign Completed",
      "type": "n8n-nodes-base.slack",
      "position": [
        1008,
        -528
      ],
      "parameters": {
        "text": ":white_check_mark: *Campaign completed*",
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "typeVersion": 2
    },
    {
      "id": "735a8545-39b3-40ac-b802-053df70c389d",
      "name": "Error Trigger",
      "type": "n8n-nodes-base.errorTrigger",
      "position": [
        -1424,
        -320
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "2d208af5-d723-43ee-9638-1aa983e2ad02",
      "name": "Format Error",
      "type": "n8n-nodes-base.code",
      "position": [
        -1184,
        -320
      ],
      "parameters": {
        "jsCode": "const error = $input.item.json;\nreturn {\n  json: {\n    errorTime: new Date().toISOString(),\n    errorMessage: error.message || 'Unknown error',\n    errorNode: error.node?.name || 'Unknown',\n    workflow: 'Email Marketing PRO'\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "b2b9b3a5-31ef-4ed6-87a9-811ce1fa0bdb",
      "name": "Slack - Error Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        -944,
        -320
      ],
      "parameters": {
        "text": ":rotating_light: *Workflow Error*",
        "otherOptions": {
          "includeLinkToWorkflow": true
        }
      },
      "typeVersion": 2
    },
    {
      "id": "ae4a28d6-c710-4ed7-83ef-cf88684f371d",
      "name": "Done",
      "type": "n8n-nodes-base.noOp",
      "position": [
        1248,
        -528
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "dcc3f379-be58-475e-8468-29c2b65b1e72",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1936,
        -1232
      ],
      "parameters": {
        "width": 400,
        "height": 632,
        "content": "## Send email campaigns with A/B testing using Gmail and Google Sheets\n\nEmail marketing with contact management, delivery tracking, and analytics.\n\n### How it works\n\n1. **Trigger** - Start manually, scheduled, or via webhook\n2. **Load** - Reads contacts from Google Sheets\n3. **Validate** - Filters invalid emails and duplicates\n4. **Send** - Delivers in batches with anti-spam delay\n5. **Track** - Logs successes and errors to Sheets\n6. **Report** - Sends Slack summary with stats\n\n### Setup steps\n\n1. **Connect:** Gmail, Google Sheets, Slack\n2. **Create Sheet:** Tabs for Contacts, Campaigns, Logs\n3. **Update IDs:** Replace YOUR_DOCUMENT_ID\n4. **Slack:** #marketing and #errors channels\n5. **Test:** Run with test contacts first"
      },
      "typeVersion": 1
    },
    {
      "id": "07b54d1e-133f-4af1-b58e-c65cd1fa1a35",
      "name": "Triggers",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1504,
        -1248
      ],
      "parameters": {
        "color": 2,
        "width": 260,
        "height": 664,
        "content": "## Campaign triggers\n\nManual, scheduled, or webhook."
      },
      "typeVersion": 1
    },
    {
      "id": "6a461723-6ef2-471b-bc20-7f8cd3b56732",
      "name": "Setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1152,
        -1248
      ],
      "parameters": {
        "color": 5,
        "width": 720,
        "height": 664,
        "content": "## Campaign setup\n\nLoads and validates contacts."
      },
      "typeVersion": 1
    },
    {
      "id": "2a7db96c-c091-46de-ade1-4aad06de0a85",
      "name": "Logging",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -384,
        -1232
      ],
      "parameters": {
        "color": 4,
        "width": 280,
        "height": 650,
        "content": "## Logging\n\nLogs start to Sheets and Slack."
      },
      "typeVersion": 1
    },
    {
      "id": "2b841651-80d5-43b4-9c3e-01253da48310",
      "name": "Validation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -48,
        -1232
      ],
      "parameters": {
        "color": 6,
        "width": 260,
        "height": 648,
        "content": "## Validation\n\nChecks for valid contacts."
      },
      "typeVersion": 1
    },
    {
      "id": "a64e4fe2-f8fb-4cd9-af8e-e2d905b26cb5",
      "name": "Delivery",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        288,
        -1232
      ],
      "parameters": {
        "color": 7,
        "width": 920,
        "height": 488,
        "content": "## Email delivery\n\nSends in batches with delays."
      },
      "typeVersion": 1
    },
    {
      "id": "1c5eb0ea-2eff-4947-a03a-6871c423d6c9",
      "name": "Tracking",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1296,
        -1232
      ],
      "parameters": {
        "color": 4,
        "width": 776,
        "height": 490,
        "content": "## Send tracking\n\nTracks results and handles delays."
      },
      "typeVersion": 1
    },
    {
      "id": "b11a7e14-f45c-429b-aaf4-9a1644a4d828",
      "name": "Completion",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        288,
        -720
      ],
      "parameters": {
        "color": 3,
        "width": 1240,
        "height": 400,
        "content": "## Campaign completion\n\nCalculates stats and notifies team."
      },
      "typeVersion": 1
    },
    {
      "id": "2035cf03-f7df-4580-89a4-3c3412f2683d",
      "name": "Errors",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1456,
        -432
      ],
      "parameters": {
        "color": 2,
        "width": 744,
        "height": 264,
        "content": "## Error handling\n\nAlerts team on Slack."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Log Error": {
      "main": [
        [
          {
            "node": "Anti-Spam Delay",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Email": {
      "main": [
        [
          {
            "node": "Mark as Sent",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Mark as Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Success": {
      "main": [
        [
          {
            "node": "Anti-Spam Delay",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Error": {
      "main": [
        [
          {
            "node": "Slack - Error Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark as Sent": {
      "main": [
        [
          {
            "node": "Log Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error Trigger": {
      "main": [
        [
          {
            "node": "Format Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Final Summary": {
      "main": [
        [
          {
            "node": "Update Campaign",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Contacts?": {
      "main": [
        [
          {
            "node": "Extract Contacts",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Calculate Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark as Error": {
      "main": [
        [
          {
            "node": "Log Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Email": {
      "main": [
        [
          {
            "node": "Send Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Contacts": {
      "main": [
        [
          {
            "node": "Filter and Validate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Configure Campaign",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Anti-Spam Delay": {
      "main": [
        [
          {
            "node": "Process in Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Campaign": {
      "main": [
        [
          {
            "node": "Slack - Campaign Completed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook Trigger": {
      "main": [
        [
          {
            "node": "Configure Campaign",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Contacts": {
      "main": [
        [
          {
            "node": "Process in Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Results": {
      "main": [
        [
          {
            "node": "Final Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scheduled Trigger": {
      "main": [
        [
          {
            "node": "Configure Campaign",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configure Campaign": {
      "main": [
        [
          {
            "node": "Read Contacts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Campaign Start": {
      "main": [
        [
          {
            "node": "Slack - Campaign Started",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process in Batches": {
      "main": [
        [
          {
            "node": "Prepare Email",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Final Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter and Validate": {
      "main": [
        [
          {
            "node": "Log Campaign Start",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack - Campaign Started": {
      "main": [
        [
          {
            "node": "Has Contacts?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack - Campaign Completed": {
      "main": [
        [
          {
            "node": "Done",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

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

About this workflow

Run professional email campaigns with A/B testing, Google Sheets tracking, and Slack analytics. FEATURES:

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

More Marketing & Ads workflows → · Browse all categories →

Related workflows

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

Marketing & Ads

This workflow automates sales contact follow-ups and engagement tracking by integrating HighLevel CRM, Gmail, Slack, and Google Sheets. It fetches all contacts from HighLevel, filters inactive contact

High Level, Google Sheets, Gmail +1
Marketing & Ads

AI Lead Qualification & Follow-Up. Uses httpRequest, slack, googleSheets, gmail. Webhook trigger; 18 nodes.

HTTP Request, Slack, Google Sheets +2
Marketing & Ads

This workflow allows you to send multi-step email campaigns using n8n, Gmail and Google Sheets.

Google Sheets, Gmail, Execute Workflow Trigger
Marketing & Ads

Watch target companies for C-level and VP hiring signals, then send AI-personalized outreach emails when leadership roles are posted.

Google Sheets, @Predictleads/N8N Nodes Predictleads, Slack +2
Marketing & Ads

Monitor customers for competitor tech adoption via PredictLeads and alert CSMs to prevent churn.

Google Sheets, @Predictleads/N8N Nodes Predictleads, Slack +1