← All Newsletter & ESP Workflows
BillionVerifyOccasion

Send personalized occasion wishes with AI, Google Sheets and Gmail

Pull contacts, verify each address with BillionVerify, and continue to Occasion β€” only deliverable addresses get through.

Why verify before the send

Sending to invalid, risky, catch-all, or disposable addresses spikes your bounce rate and erodes sender reputation. A verification gate before the Occasion step removes that risk automatically β€” only deliverable addresses continue, the rest are flagged.

The workflow

BillionVerify β€” verification sits right before the send.

+6
n8n steps
+2
n8n steps

Node by node

  1. 1
    Every Day at 8 AMTriggerΒ· n8n

    Starts the workflow β€” on a schedule, a webhook, or manually while you test.

  2. 2
    Get row(s) in sheet in Google SheetsSourceΒ· n8n

    Provides or transforms the contact data flowing through the workflow.

  3. 3
    OpenAI Chat ModelSourceΒ· n8n

    Provides or transforms the contact data flowing through the workflow.

  4. 4
    Date & TimeSourceΒ· n8n

    Provides or transforms the contact data flowing through the workflow.

  5. 5
    AI AgentSourceΒ· n8n

    Provides or transforms the contact data flowing through the workflow.

  6. 6
    Code in JavaScriptSourceΒ· n8n

    Provides or transforms the contact data flowing through the workflow.

  7. 7
    Verify Email (BillionVerify)VerifyΒ· billionverify

    The BillionVerify node verifies the address β€” status (valid / invalid / risky / catch-all / role / disposable), is_deliverable, and a confidence score β€” before anything is sent.

  8. 8
    IF deliverableLogicΒ· n8n

    Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.

  9. 9
    Send a messageSendΒ· n8n

    Sends only to verified, deliverable addresses. Swap in your own provider node if you send elsewhere.

Workflow JSON

Copy or download this workflow, then import it in n8n (Workflows β†’ Import from File / Paste). Install the BillionVerify community node first, then add your API key credential.

verify-emails-in-occasion.json
{
  "name": "Send personalized occasion wishes with AI, Google Sheets and Gmail + BillionVerify",
  "nodes": [
    {
      "id": "0f023a71-85b7-4f0e-a02f-cbfaa9d89100",
      "name": "Every Day at 8 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -1120,
        -64
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 24
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "419cacda-a902-427a-8b7c-769534544a21",
      "name": "Date & Time",
      "type": "n8n-nodes-base.dateTime",
      "position": [
        -928,
        -64
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 2
    },
    {
      "id": "8f48ee04-443c-4d66-8e80-4d7f478bbef1",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -656,
        -64
      ],
      "parameters": {
        "text": "=Today's date is: {{ $json.currentDate }}\n\nPlease check the Google Sheet resource for any occasions matching today's date (month and day only).\n\nOUTPUT FORMAT:\nWhen occasion is found, return ONLY valid format like this (no other text):\n[{\n  \"hasOccasion\": true,\n  \"email\": \"sarah@email.com\",\n  \"subject\": \"Happy 30th Anniversary Sarah! πŸŒΉβ˜•\",\n  \"message\": \"Happy 30th Anniversary, my love!...\"\n}]\n\nWhen no occasion is found, return ONLY:\n[{\n  \"hasOccasion\": false\n}]\n\nProceed now.",
        "options": {
          "systemMessage": "=You are my personal assistant specialized in relationship management and special occasion tracking.\n\nYOUR IDENTITY:\n- Thoughtful and detail-oriented\n- You help me maintain strong relationships by ensuring I never miss important dates\n- You have access to a Google Sheet containing all my friends' and family members' special occasions\n\nYOUR CAPABILITIES:\n- You can read and analyze dates from the Google Sheet resource attached\n- You understand different types of occasions: birthdays, anniversaries, graduations, engagements, etc.\n- You can calculate ages and anniversary years accurately\n- You generate personalized, heartfelt messages appropriate for different relationships\n\nYOUR RESPONSIBILITIES:\n1. Check if today matches any occasion in the sheet (compare month and day only, ignore year)\n2. If occasions are found today:\n   - Extract all relevant details (Name, Email, Occasion_Type, Relationship, Personal_Note, Occasion_Date)\n   - Calculate years passed (current year minus occasion year)\n   - Use correct ordinal suffix (1st, 2nd, 3rd, 4th, 21st, 22nd, 23rd, etc.)\n   - Generate warm, personalized messages that reflect the relationship type\n3. If no occasions are found today:\n   - Simply respond: \"No occasions today.\"\n\nMESSAGE GUIDELINES:\n- Adapt tone based on relationship:\n  * Spouse/Partner: Romantic, intimate, loving\n  * Parents: Respectful, warm, appreciative\n  * Siblings: Casual, playful, supportive\n  * Friends: Friendly, fun, genuine\n  * Professional contacts: Formal, respectful, professional\n- Reference the Personal_Note to add authentic, specific touches\n- Keep messages 3-5 sentences\n- Start birthday/anniversary messages with \"Happy [number][suffix] [occasion]!\"\n- Be genuine and heartfelt, never generic or robotic\n- Each message should feel unique and personal\n\nIMPORTANT RULES:\n- Match dates by MM-DD only (ignore the year in comparison)\n- Always calculate the correct age/years for context\n- Never send wishes if the date doesn't match\n- Process each person separately if multiple occasions exist today\n- Maintain privacy and handle personal information carefully\n"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 2.2
    },
    {
      "id": "1ea6f0a1-0017-4b76-9e93-00903bcdd0a0",
      "name": "Get row(s) in sheet in Google Sheets",
      "type": "n8n-nodes-base.googleSheetsTool",
      "position": [
        -496,
        128
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/18i8WW1ytW7Zq255Djtj9Xg_uKIsAu9_Oqs452IkRiGQ/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "18i8WW1ytW7Zq255Djtj9Xg_uKIsAu9_Oqs452IkRiGQ",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/18i8WW1ytW7Zq255Djtj9Xg_uKIsAu9_Oqs452IkRiGQ/edit?usp=drivesdk",
          "cachedResultName": "data"
        },
        "descriptionType": "manual",
        "toolDescription": "Get row(s) in sheet in Google Sheets from the sheet named data."
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "credential-id",
          "name": "googleSheetsOAuth2Api Credential"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "94e4ccb3-9794-44f8-872b-e710197ffdec",
      "name": "Send a message",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -96,
        -64
      ],
      "webhookId": "4ff5e570-eb90-4576-aee6-6f33f097ee86",
      "parameters": {
        "sendTo": "={{ $json.output.email }}",
        "message": "={{ $json.output.message }}",
        "options": {},
        "subject": "={{ $json.output.subject }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "778195ab-b64b-4c06-96e9-db0901d7c63b",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -656,
        112
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "id": "credential-id",
          "name": "openAiApi Credential"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "bdeccaf3-6199-4e8a-ab59-3a7d11000589",
      "name": "Code in JavaScript",
      "type": "n8n-nodes-base.code",
      "position": [
        -256,
        -64
      ],
      "parameters": {
        "jsCode": "// Get the string returned by the AI agent\nlet raw = items[0].json.output;\n\n// Sometimes output may already be parsed, handle both cases\nlet parsed;\n\ntry {\n  // Try to parse directly\n  parsed = JSON.parse(raw);\n} catch (e) {\n  // If output contains escaped JSON inside JSON, parse twice\n  try {\n    parsed = JSON.parse(JSON.parse(raw));\n  } catch (e2) {\n    throw new Error(\"AI output is not valid JSON array: \" + raw);\n  }\n}\n\n// Ensure we always get an array\nif (!Array.isArray(parsed)) {\n  parsed = [parsed];\n}\n\n// If no occasions found β†’ [{ hasOccasion: false }]\nif (parsed.length === 1 && parsed[0].hasOccasion === false) {\n  // Return zero items β†’ nothing is sent\n  return [];\n}\n\n// Convert each event into its own n8n item\nconst newItems = parsed.map(ev => ({\n  json: {\n    hasOccasion: ev.hasOccasion,\n    email: ev.email,\n    subject: ev.subject,\n    message: ev.message\n  }\n}));\n\nreturn newItems;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "186dc502-4b99-49cf-969f-63412a6938b4",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1776,
        -176
      ],
      "parameters": {
        "width": 528,
        "height": 480,
        "content": "## Overview: Automated Occasion Wisher \n**How it works** \n- Runs daily to check if today matches any birthday, anniversary, or special occasion in your Google Sheet\n- AI Agent reads the sheet and returns list of users having special occasion to wish with details and personalized wishing message\n- If there is no one to wish, no email is sent. If there are multiple people to wish, multiple personalized emails are sent\n\n**Setup steps** \n\n- Connect your Google Sheet containing columns as: Name, Occasion_Date, Email,\tOccasion_Type, Relationship, Personal_Note\n- Insert the AI prompt ensuring strict JSON output (list format only)\n- Configure the Email node for sending the final message\n\n**Customization**\n\n- Edit AI prompt to change message tone, length, or emojis\n- Add support for multiple reminder styles (e.g., early notification)\n- Extend with logging, Slack alerts, or saving sent-email history\n"
      },
      "typeVersion": 1
    },
    {
      "id": "1bd4a7de-1b07-4d63-bbb8-2da1c4881dca",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1152,
        -176
      ],
      "parameters": {
        "color": 7,
        "width": 352,
        "height": 288,
        "content": "## 1. Trigger Everyday and fetch current date "
      },
      "typeVersion": 1
    },
    {
      "id": "cd9810d0-dac7-4280-8dda-8521d3edae99",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -720,
        -176
      ],
      "parameters": {
        "color": 7,
        "width": 352,
        "height": 480,
        "content": "## 2. AI Agent with Google Sheets resource"
      },
      "typeVersion": 1
    },
    {
      "id": "bcd7d0f9-3e97-426e-8344-2c5ad7ac2552",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -304,
        -176
      ],
      "parameters": {
        "color": 7,
        "width": 352,
        "height": 288,
        "content": "## 3. Format output from AI Agent and send email if found any event "
      },
      "typeVersion": 1
    },
    {
      "parameters": {
        "operation": "verify",
        "email": "={{ $json.output.email }}",
        "additionalOptions": {}
      },
      "type": "n8n-nodes-billionverify.billionVerify",
      "typeVersion": 1,
      "position": [
        -456,
        -64
      ],
      "name": "Verify Email (BillionVerify)",
      "credentials": {
        "billionVerifyApi": {
          "id": "",
          "name": "BillionVerify account"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "is-deliverable",
              "leftValue": "={{ $json.is_deliverable }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ]
        }
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -276,
        -64
      ],
      "name": "IF deliverable"
    }
  ],
  "connections": {
    "AI Agent": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Date & Time": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every Day at 8 AM": {
      "main": [
        [
          {
            "node": "Date & Time",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript": {
      "main": [
        [
          {
            "node": "Verify Email (BillionVerify)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get row(s) in sheet in Google Sheets": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Verify Email (BillionVerify)": {
      "main": [
        [
          {
            "node": "IF deliverable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF deliverable": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}

When to use this

  • Cleaning a list before a Occasion send or sync.
  • Protecting Occasion deliverability and sender reputation.
  • Keeping bounce rates low so your sending is never throttled.

FAQ

Why verify before sending in Occasion?

Verifying first keeps your bounce rate low, which protects your sender reputation and your results.

How do I import this workflow?

Download the JSON, then in n8n go to Workflows β†’ Import from File (or paste it). Install the BillionVerify community node and add your API key credential.

What happens to risky or catch-all addresses?

They are routed to the false branch and excluded from the send. You decide whether to retry, review, or drop them.

Add verification to your workflow

Create a free account, grab your API key, and stop bounces before they happen.

Get started free