Skip to main content
Back to PortfolioProfessional Services

Multi-Step Follow-Up Sequence with State Tracking

A multi-day, multi-channel outbound sequence where the hard part is not sending the messages. It is holding state across days, reacting when someone replies mid-sequence, and not double-sending when a step is retried.

Demonstration build. Comparisons and outcomes are illustrative, not measured client results.

Demonstration build
October 2024

Build at a glance

The business problem

Scheduled messages keep going after a customer replies.

Designed outcome

The next step reads the customer record and pauses the sequence.

Workflow walkthrough
  1. Inquiry captured
  2. Response & follow-up scheduled
  3. Reply received → sequence paused

How to verify the behavior

A review checklist for this demonstration, rather than a claim of independently measured results.

  • Start a lead and confirm one record is created.
  • Simulate a reply and confirm the scheduled follow-up pauses.
  • Retry the same lead and confirm no second record or message is added.
Try the interactive follow-up simulation →
01The challenge

The Challenge

Any sequence that runs over days rather than seconds has a state problem. The workflow is not running continuously, so between step three and step four the record can change underneath it: the contact replies, someone updates them by hand, the same person comes in a second time through a different form. Most sequences are built as a chain of timed sends with no memory of what already happened, so they keep sending after a reply, send twice after a retry, and have no way to answer where a given contact actually is.

02The build

The workflow

A sequence built around an explicit state machine rather than a chain of delays. Each contact has one record holding their current step, the time of the last send, and a lock. Every step reads that state before acting, writes the outcome before finishing, and refuses to act if the state has moved since it was scheduled. Replies and manual edits both write to the same record, so the sequence sees them the same way it sees its own sends.

Safeguards

Every send is keyed on contact plus step number, so a retried or redelivered step is a no-op rather than a second message. If a step cannot confirm the send, the state stays where it was and the step is retried rather than advanced past. Contacts whose state has not moved in longer than their step's window are surfaced in a daily stuck-record report, which is how a broken sequence gets found in a day instead of a quarter.

Technologies

GoHighLeveln8nWebhooksScheduled jobsEmail and SMS APIs

About This Build

This is a demonstration build. It exists to show how I handle a specific class of problem: automation that has to stay correct across days, when the world keeps moving between steps.

The visible part of the system is a multi-day outbound sequence with email and SMS. That part is not interesting. Any tool can send a message on a timer. The interesting part is everything that happens when the timer fires and the situation is no longer what it was when the timer was set.

The Actual Problem

A sequence that runs over ten days is not one workflow execution. It is somewhere between five and thirty separate executions, hours or days apart, each of which wakes up with no memory of the others.

Between any two of those executions, the following can happen:

  • The contact replies, and continuing to send is now actively bad
  • Someone on the team updates or closes the record by hand
  • The same person submits a second form and enters the sequence again
  • A step fails partway through and the platform retries it
  • A webhook is redelivered, because webhooks are redelivered
  • The whole thing is paused and resumed, and every pending timer fires at once

Sequences built as a chain of timed sends handle none of these. They have no memory, so they cannot detect any of it. The failure mode is not a crash. It is a contact who replied on day two and kept receiving scheduled messages until day ten.

Solution Architecture

One record, one source of truth

Each contact in the sequence has exactly one state record:

  • Current step number
  • Timestamp of the last confirmed send
  • Timestamp of the last inbound event of any kind
  • A lock field, with a holder and an expiry
  • Status: active, paused, completed, or held

Nothing about the sequence lives anywhere else. There are no implicit timers carrying meaning, because a timer cannot be inspected and cannot be reasoned about after the fact.

Every step reads before it acts

A scheduled step does not send. It does this:

  1. Take the lock, or exit if someone else holds it
  2. Read the state record
  3. Compare the step it was scheduled for against the step the record is on
  4. If they disagree, the world moved. Exit without sending
  5. If they agree, check status. Paused, completed and held all exit
  6. Send
  7. Write the new step and send time
  8. Release the lock

Step three is the whole design. A step scheduled for day four that wakes up to find the record already on step six knows something else advanced it, and does nothing. This is what makes a resume-after-pause safe: every queued timer fires, and all but one of them exit at step three.

Idempotency on contact plus step

Every send carries an idempotency key of contact id plus step number. The send is recorded before the step advances. If a step is retried after the send succeeded but before the write completed, the retry finds the existing record for that key and treats the send as already done.

This is the difference between a retry being safe and a retry being a second message to a real person.

Replies write to the same record

An inbound reply, an SMS response, a manual note and a status change from the CRM all write to the same state record and set status to paused. They do not try to cancel scheduled work, because cancelling scheduled work reliably is harder than it looks and fails silently when it fails.

Instead, the next scheduled step reads the paused status and exits. The pause takes effect on the next read, which is deterministic and inspectable, rather than depending on whether a cancellation reached every queued job.

Duplicate entry merges

A second form submission from an existing contact does not start a parallel sequence. It matches on the identity fields, writes the new information to the existing record, and records the second touch. Two sequences running against one person is the same class of bug as two invoices for one order.

What This Costs You

More moving parts than a chain of timers. A state record per contact, a lock, a send log, and a reconciliation job. That is real work and it is not free.

What you get is a sequence you can answer questions about. Where is this contact. Why did they stop. Did they get step four. Did anyone get step four twice. A timed chain cannot answer any of those, which is usually discovered at the moment someone asks.

How I Approach a Build Like This

  • Write down every event that can happen between two steps, before designing either step
  • Make state explicit and inspectable, never implied by a pending timer
  • Make every side effect idempotent on a key you can reconstruct
  • Prefer a check on the next read over a cancellation that has to reach everything
  • Add a stuck-record report on day one, because the failure mode here is silence
03Designed outcomes

Illustrative comparisons for a demonstration build. Actual time savings depend on your process and have not been measured here.

Before

  • Sequence stateImplicit, in timers
  • Retried stepSends again
  • Reply mid-sequenceKeeps sending
  • Stuck contactFound by accident

After

  • Sequence stateExplicit, one record
  • Retried stepNo-op
  • Reply mid-sequencePauses on next read
  • Stuck contactDaily report

What This System Delivers

One record per contact holds the current step, last send time and lock

Sends are idempotent on contact plus step, so retries cannot duplicate

An inbound reply pauses the sequence on the next step read, not eventually

Duplicate entries from a second form fill merge instead of starting a parallel sequence

Stuck contacts surface in a daily report instead of sitting silently

More builds

Related Builds

E-commerce

E-commerce Order Processing Automation

An end-to-end order processing system that automates everything from order capture to customer notifications. Built to eliminate the 15+ hours per week growing stores typically spend on manual fulfillment work.

View case study

Operations

Invoice Intake with Human Review

The proposed workflow receives an attachment, identifies whether it has already been processed, and extracts a defined set of invoice fields into a draft. Required-field checks and total checks decide whether it is ready for human approval or needs correction. Only an approved version is written to the destination.

View case study

Customer Support

Support Ticket Routing with Customer Context

The proposed workflow captures a request, looks up the customer using an exact identifier where possible, and classifies the issue against a small set of queues. Clear cases are assigned with a concise summary and relevant context. Uncertain customer matches or categories go to a human triage queue.

View case study

Want a System Like This?

Let's map your process on a strategy call and design the version that fits your business.

Discuss your project