Gmail Email Sequence

Set up the Gmail Multi-Step Email Campaign template — sheet columns, the emailSequence variable, step tracking, and troubleshooting.

The Gmail Multi-Step Email Campaign template sends a drip sequence (intro plus follow-ups) to contacts in a Google Sheet. Follow-ups only go out when the contact hasn't replied to the Gmail thread. This guide covers everything the template needs to work: the sheet layout, the emailSequence variable, workflow variables, and how step tracking behaves.

How it works

Each scheduled run:

  1. Reads all contact rows from your sheet.
  2. Filters out replied / unsubscribed / bounced contacts.
  3. Keeps only rows due today for their current step (based on step and first_emailed).
  4. For follow-ups, checks the Gmail thread — if the contact replied, marks them replied and skips.
  5. Sends the email for the contact's current step, then writes the send date, thread id, status, and the next step back to the sheet.

One run sends one step per contact. The next step goes out on a later run once its day offset is due.

1. Google Sheet setup

Create a sheet with a header row using these exact column names (the workflow matches columns by header, so spelling matters):

ColumnFilled byPurpose
emailYouContact address (required)
nameYouUsed in {{name}} placeholders
companyYouUsed in {{company}} placeholders
first_emailedWorkflowDate of the first send — all follow-up timing is measured from this
thread_idWorkflowGmail thread id, used for reply detection and threading
statusWorkflowactive after first send, replied when a reply is detected
stepWorkflowCurrent sequence index (0-based)

Warning: Use thread_id, not threadId. A mismatched header means the workflow can't find the thread — reply detection silently stops working.

New leads should have step empty or 0, and first_emailed, thread_id, status all blank. The workflow fills them in.

2. Workflow variables

Open the workflow's Variables and set:

VariableExamplePurpose
spreadsheetUrlyour sheet URLUsed by Read Contacts and Update Contact Row
campaignIdoutreach-v1Tag added to each send for tracking
emailSequencesee belowThe sequence copy and timing
contactList[]Runtime scratch state — leave as empty array
scanIndex0Runtime scratch state — leave as 0
sentEmails[]Runtime scratch state — leave as empty array

Warning: Published workflows run the published definition. After editing any variable (including emailSequence), you must Publish again for scheduled runs to pick up the change.

3. The emailSequence array

This variable defines every email in the sequence. Each entry is an object with exactly these properties:

[
  {
    "day": 0,
    "subject": "Nice to meet you",
    "body": "Hi {{name}} — was wondering whether {{company}} would be interested in a quick call?"
  },
  {
    "day": 3,
    "subject": "Re: Nice to meet you",
    "body": "Hi {{name}}, just wanted to check in on this. Let me know if there's any interest!"
  },
  {
    "day": 7,
    "subject": "Re: Nice to meet you",
    "body": "{{name}}, just wanted to give this one last try."
  }
]
PropertyTypeMeaning
daynumberDays after first_emailed when this entry becomes due. The first entry should be 0.
subjectstringEmail subject. Supports {{field}} placeholders from the sheet row.
bodystringEmail body. Supports the same placeholders. Use \n for line breaks in JSON.

Rules that matter:

  • The timing property must be named day. Entries with a step property instead of day fall back to day: 0 and break follow-up timing.
  • day values are anchored to first_emailed, not to the previous send. 0, 3, 7 means day 0, then 3 days after the first email, then 7 days after the first email. Keep them increasing.
  • Add as many entries as you like. The sequence is generic: a contact advances one entry per send and drops out automatically after the last entry (step reaches the array length).
  • Placeholders ({{name}}, {{company}}, {{email}}, or any sheet column) are replaced per contact.

4. Step and status lifecycle

stepstatusfirst_emailedWhat happens
0 / blankblankblankIntro (entry 0) sends once
0activesetSkipped — intro already sent, never resent
1+activesetFollow-up sends when its day offset is due
anyrepliedSkipped forever (reply check also sets this)

After every successful send, Update Contact Row writes back:

Sheet columnWritten value
first_emailedSend date (kept from the first send onward)
thread_idGmail thread id from the send
statusactive
stepnextStep — the step just sent plus one

Warning: If you customize Update Contact Row, the step column must use the pick node's nextStep output (e.g. {{context.pick-next-contact.output.contact.nextStep}}), not step or sequenceStep. Writing the current step back causes the same email to send on every run and the sequence never finishes.

5. Advance step rules (safety net)

The Due for Sequence and Scan Sequence Contact nodes include an Advance step rules config:

[
  {
    "atStep": 0,
    "toStep": 1,
    "whenAnyFieldSet": ["first_emailed"],
    "whenStatusEquals": [{ "field": "status", "value": "active" }]
  }
]

This handles rows where the intro clearly went out (first_emailed set or status is active) but the sheet step is still 0 — the row is treated as step 1 instead of being stuck or double-sent. Keep toStep at 1; jumping further skips sequence entries.

6. Testing the sequence

  1. Set every entry's day to 0 so all steps are due immediately (restore real offsets afterwards).
  2. Reset your test row: step0, clear first_emailed, thread_id, and status.
  3. Publish (required after variable edits).
  4. Run the workflow once per step. Each run should send the next entry and increment step by 1.

With real day offsets, remember: the third email in a 0 / 3 / 7 sequence cannot send until 7 days after first_emailed, no matter how many times the workflow runs.

Troubleshooting

SymptomCauseFix
Follow-ups never sendemailSequence entries use step instead of dayRename the property to day
Same email sends on every run; step doesn't advanceUpdate Contact Row writes step instead of nextStepPoint the step column at contact.nextStep
first_emailed stays blankUpdate Contact Row's first value references a field the pick node doesn't outputUse contact.firstEmailedForUpdate from the Scan Sequence Contact node
Reply detection never triggersSheet header is threadId (or anything other than the configured thread_id)Rename the header to match threadIdField
Variable edits have no effectWorkflow not republishedPublish after every variable change
A step is skippedtoStep in advance rules set too highSet toStep: 1 for the atStep: 0 rule
Contact stuck, nothing sendsRow's status is replied/unsubscribed/bounced, or the next entry's day isn't due yetCheck status and first_emailed date math