ooligo
n8n-flow

Demo no-show recovery flow in n8n

Difficulty
beginner
Setup time
45min
For
revops · sdr-leader
RevOps

Stack

A demo no-show is the most expensive event in a B2B sales motion that nobody owns. The AE blocked thirty minutes, the SDR booked the meeting, the buyer expressed real intent at some point, and then nothing happened. Most teams handle this with a Slack reminder to the AE and a vague intention to “follow up.” The follow-up either lands days later when the buying moment has cooled, or never lands at all because the AE is in their next call. This n8n flow makes the recovery happen on the same day, every time, without an SDR doing the work — and exits the moment a human reply makes the automation redundant.

When to use

You have at least 30 demo no-shows per month across the team. Below that volume, the AE can recover them by hand and you don’t need automation; the failure mode of automation (a slightly off email at the wrong moment) becomes worse than the failure mode of manual recovery (forgetting). You’re already on HubSpot for meetings (or Chili Piper writing back to HubSpot) and the AE mailboxes are on Google Workspace. You have an honest no-show signal — meaning HubSpot is actually marking meetings as no_show rather than leaving them blank — and your AEs have working scheduling links.

When NOT to use

Do not turn this on if any of the following are true. Your no-show “detection” is just blank meeting outcomes — you’ll send recovery emails to people who arrived two minutes late and the AE forgot to log it, which is worse than doing nothing. Your AE domain doesn’t have SPF, DKIM, and DMARC properly configured — delegated send from an unauthenticated domain will land in spam, train Gmail to keep landing it in spam, and damage the AE’s actual outbound. You’re sending high-volume cold outbound from the same AE mailboxes (more than ~50 send actions per day per mailbox combined with this) — the recovery emails will compete with cold and both will suffer. You don’t have a clean opt-out path to honor — sending a “soft close” to someone who already replied STOP is illegal in most jurisdictions and ruinous in all of them.

Setup

The full bundle ships at apps/web/public/artifacts/demo-no-show-recovery-n8n/. Two files: demo-no-show-recovery-n8n.json (the n8n export) and _README.md (import procedure, the four credential placeholders, the Postgres schema for the two tables the flow reads and writes, and a nine-step first-run verification that exercises every branch including the eligibility guard, the tone split, the Claude fallback, the cron sweep, and the reply-exit path). Follow the README end-to-end before flipping the workflow to Active. The schema-creation SQL must be run before the first webhook fires or the Postgres — Init Recovery State node will throw and the contact will be left in a half-sequenced state with no row to recover from.

What the flow actually does

The webhook is the source of truth. A HubSpot Workflow watches for meetingOutcome = no_show and POSTs { meetingId, contactId, ownerId } to the hubspot-no-show endpoint. n8n acks 202 immediately (so HubSpot’s retry doesn’t fire if a downstream step is slow) and in parallel pulls the meeting, the contact, and the AE owner record. The eligibility guard is three explicit conditions ANDed together: not opted out, has a real email address, and the meeting start time is at least five minutes in the past. That last condition is the safety against the “they joined late and the AE didn’t notice” failure mode — see Watch-outs below.

The tone branch is decided by a single Postgres lookup against hubspot_meetings_raw: if the contact has any meeting with outcome = 'completed' in the last 90 days, the tone is we_missed_you; otherwise it’s lets_reschedule. This gets passed to Claude along with the contact’s form-fill summary and the AE’s name. Claude returns one sentence — capped at 22 words, peer-to-peer voice — and the prompt has an explicit fallback contract: if the form summary is empty or generic, the model returns the literal string FALLBACK and the JS step in Compose Step 1 Email swaps in a safe template opener instead of shipping an over-personalized line built on nothing. Only the opener is generated. The body, the two pre-picked time slots, and the scheduling-link call-to-action are deterministic. This is the engineering choice that separates this flow from the typical “let an LLM write the whole email” pattern: most of the email is template, the personalization is one sentence, and there’s a hard fallback when personalization isn’t safely possible.

After step 1 sends, a row goes into recovery_state with next_due_at = now() + 2 days. A separate cron node sweeps every 15 minutes during business hours, picks up due rows, and routes step-1 rows to a value-forward email referencing a relevant resource (mapped per tone) and step-2 rows to a soft-close. A third independent trigger watches the AE inbox for any new inbound email, classifies it as opt_out, rescheduled_or_replied, or human_reply based on subject and snippet pattern matching, exits the sequence, and writes the exit reason back to the HubSpot contact via PATCH so reporting can roll exits up alongside meetings.

Cost reality

Per recovered no-show: roughly 1 Claude API call at ~600 input tokens and ~80 output tokens, which on Sonnet is about $0.003. Plus 2–3 Gmail sends (free for the AE mailbox quota). Plus 4 HubSpot API calls (well under any sane org’s daily HubSpot limit; HubSpot Private Apps allow 100 requests per 10 seconds). Plus the n8n self-host cost, which is ~$5/mo on a Hetzner CX22 if you’re not already running n8n. At 200 no-shows per month the marginal cost is about $0.60 in Claude charges and zero in everything else. If even 10% of those recover and convert to opportunity, the unit economics are absurd — the cost story is dominated by the 30-45 minutes of setup, not the run-cost.

What success looks like

Three numbers to watch in HubSpot once the flow has been running for at least 30 days. First, recovered-meeting rate: of contacts that hit the flow, what percentage either book a new meeting (via rescheduled_or_replied exit) or reply to the AE within the seven-day window. Healthy is 25-35%; below 15% means the opener is generic, the form-fill summary isn’t being captured, or the AE domain has deliverability problems. Second, opt-out rate: percentage of contacts that exit via opt_out. Healthy is under 3%; above 5% means the tone is wrong or you’re including segments that shouldn’t be in the recovery flow at all. Third, false-positive rate: percentage of step-1 sends where the AE later confirms the contact actually attended (sample 20 per month manually). This should be under 2%; if it’s higher, the no-show detection is broken upstream and you should pause the flow.

Versus the alternatives

Versus a HubSpot Workflows-only sequence: HubSpot can do the timing and the email send, but HubSpot can’t call Claude for opener personalization, can’t apply a fallback contract on the personalization output, and can’t route replies through code-based classification. The HubSpot-only version sends the same opener to everyone in the segment. That gets you maybe 15% recovery instead of 25-35% — worth it if you don’t have n8n, not worth it if you do.

Versus an outbound platform like Smartlead or Outreach: those tools assume you’re sending cold to a list and want to optimize deliverability across hundreds of mailboxes. They’re overkill for this volume (a few hundred no-shows per month per team) and they don’t read HubSpot meeting outcomes natively, so you’d still need an integration layer. The pricing also doesn’t make sense — $100+/AE/month for what is at most 2-3 sends per AE per day from this flow.

Versus doing nothing: the status quo. Most teams’ no-show recovery rate is in the 5-10% range — the AE remembers to follow up on the high-fit ones and forgets the rest. The delta from 5-10% to 25-35% is the actual ROI of this flow.

Watch-outs

Late arrivals counted as no-shows. The most common false positive: the buyer joins six minutes after the start, the AE has already left or marked the meeting done. The eligibility guard’s third condition (meeting_start_time at least 5 minutes in the past) is the specific guard, but it doesn’t help if HubSpot is marking meetings as no_show immediately when the AE clicks “no-show” without checking. Fix this upstream by requiring the AE to wait a configurable window before they can mark no_show in HubSpot, or by switching to an automatic no-show detector that watches Zoom/Google Meet attendance.

Over-personalized opener. Claude will, given any context at all, attempt to personalize. If the form summary is “Wants demo,” the personalization will be a hallucinated specific. The guard is the explicit FALLBACK contract in the system prompt — Claude returns the literal string when context is insufficient and Compose Step 1 Email substitutes a safe template opener. Watch the used_fallback metric on recovery_state for the first month; if it’s never true, the contract isn’t firing and Claude is hallucinating opener content.

Sender reputation collapse. Delegated send from the AE’s mailbox is the right move for reply rates but a wrong move if the AE’s domain doesn’t authenticate. The guard is operational, not in the flow: before activation, run mail-tester.com against a test send and confirm a 10/10 with SPF, DKIM, and DMARC all green. If any are failing, fix DNS first or ship from a sub-domain (replies.<domain>.com) with its own authentication.

Replied contacts kept in sequence. The reply trigger polls Gmail every minute, but Gmail polling has a non-zero lag and the cron sweep also runs every 15 minutes. There’s a window where a step-2 send could fire after the buyer has already replied. The guard is the status = 'active' filter in Postgres — Pull Due Recoveries plus the Postgres — Exit Sequence UPDATE flipping status the moment a reply lands. Race conditions are still possible at the minute boundary; if this matters for your team, switch the reply trigger to a Gmail push notification (Pub/Sub) instead of polling.

Stack

  • n8n — orchestration, the three independent triggers (webhook, cron, Gmail), and the JS code nodes for personalization context and reply classification.
  • HubSpot — meeting and contact source of truth, owner records, exit-reason write-back via Private App token.
  • Gmail — delegated send from the AE mailbox; reply trigger watching the same inbox.
  • Claude (Sonnet) — opener-line personalization with a hard FALLBACK contract for when context is insufficient.
  • Postgresrecovery_state table for sequence state and hubspot_meetings_raw for the 90-day prior-meeting lookup that drives the tone branch.

Files in this artifact

Download all (.zip)