Late-accept follow-up: auto-fire, heal-tick backstop, and the May 7 historical backfill
Overview
WarmySender is a 4-pillar outreach platform — Cold Emailing, Email Warmup, LinkedIn Outreach, and Multichannel sequences. This page is part of the LinkedIn Outreach pillar and explains the safety net that catches LinkedIn invitation accepts that arrive after your campaign workflow has already moved past the wait_accept step ("late accepts").
Three layers protect the customer-facing latency from accept to first follow-up message:
- Webhook-time auto-fire (primary, V15+) — when the late-accept webhook lands, our handler immediately enqueues the follow-up via the existing
campaign_send_jobspipeline. - 6-hour heal-tick backstop — a recurring sweeper picks up edge cases the auto-fire missed (account was disconnected at webhook time, campaign was paused, etc.).
- May 7, 2026 historical backfill (V16) — a one-shot script that scans for prospects whose accepts arrived BEFORE the V15 auto-fire wiring landed and enqueues their missing follow-ups via the same safety-gated pipeline.
All three paths route through the same campaign_send_jobs queue, so every per-account safety gate (daily cap, ramp ceiling, cooldown, sending-window clamp) applies to every dispatch regardless of which path triggered it.
Webhook-time auto-fire (primary path, V15+)
When the LinkedIn integration delivers a late-accept webhook, our handler runs three actions in a single request:
- Stamp
late_accept_observed_aton the matched prospect (the SAFE sentinel that NEVER toucheslinkedin_accepted_at,linkedin_status, or the workflow advance). - Call the auto-fire dispatcher, which walks the campaign step graph to find the right post-accept message step and inserts a
campaign_send_jobsrow. - Emit a structured audit event with
metadata.source = 'late_accept_webhook_auto_fire'ANDmetadata.branch_taken = 'post_acceptance_followup_dispatched'(V16 normalized — both fields together are the canonical query for late-accept dispatch volume).
From your perspective, an accept that lands inside your sending window with cap headroom produces a follow-up message within minutes. See Why a late LinkedIn accept may take time to message for the worked-example timeline.
Idempotency is double-protected: the dispatcher's late_accept_followup_sent_at IS NULL guard at enqueue time, plus the partial unique index on campaign_send_jobs at the database level. A re-fire of the same webhook (network retry, dedup race, etc.) is a no-op.
6-hour heal-tick backstop
The recurring heal scheduler runs every 6 hours and catches what the auto-fire couldn't enqueue. Common triggers:
- Account disconnected at webhook time. The dispatcher's pre-flight returns
account_not_connectedand skips. When you reconnect, the next 6-hour tick picks the prospect up and dispatches. - Campaign was paused at webhook time. The dispatcher returns
campaign_paused. When you resume, our resume-sweep runs, and the V15 hourly skip-retry tick (added May 7, 2026, every 60 minutes, hard-cap 100 rows/tick) catches any rows the resume-sweep missed. - Step graph ambiguity. Rare — happens when a campaign's authored step graph has multiple branches the dispatcher can't pick automatically. We log this for ops review.
The heal-tick has a hard cap of 200 rows per 6-hour tick. With backlogs in the hundreds, the backstop alone takes days to drain — which is why the May 7 historical backfill exists as a one-shot.
The May 7, 2026 historical backfill (V16)
Before the V15 auto-fire wiring landed (May 7, 2026), every late accept relied on the 6-hour heal-tick to dispatch its follow-up. That worked, but the rate-cap of 200 rows per 6-hour tick combined with backlogs in the hundreds meant some prospects waited 100+ hours between accept and first follow-up — far longer than any reasonable user expectation.
The backfill scans for prospects with this pattern:
linkedin_accepted_at IS NOT NULL(the accept signal arrived).late_accept_followup_sent_at IS NULL(no follow-up has been sent yet).late_accept_observed_at IS NULL(this row was never observed by the V15 webhook path; it's a true historical pre-fix victim).now() - linkedin_accepted_at > INTERVAL '24 hours'(only stuck rows; rows accepted in the last 24h are still in the normal handling window).linkedin_replied_at IS NULL(sequence-stop guard — don't fire on prospects who already responded).- The campaign has at least one post-accept step (
current_step_index < (steps_count - 1)). - Status not in (
failed,completed,skipped,condition_skipped,stopped,bounced,unsubscribed). - No active pending/processing send job already exists for this prospect at the next step (idempotency check against
campaign_send_jobs).
For each match, the backfill calls the same canonical helper the live engine uses (enqueuePostAcceptanceNextStep) which routes through ensureNextJobExists, which respects every per-account safety gate at job execution time.
Account-safety caps on the backfill
Two hard caps apply, both enforced at the SQL predicate level so the cap cannot be bypassed by a bug in the application code:
- 50 jobs per LinkedIn account per day. Enforced via a
ROW_NUMBER()window function partitioned bylinkedin_account_idordered by oldest accept first. The 51st prospect on any single account waits for the next run; nothing is silently dropped. - 500 jobs per script run. Total per-run cap — even if a single workspace has thousands of stuck prospects, the backfill only enqueues 500 per run. Multiple runs over multiple days drain the cohort gradually without spiking any single account's daily volume.
Both caps preserve the LinkedIn-side safety invariant CLAUDE.md describes: account safety always wins over speed, throughput, or user intent. A banned LinkedIn account is unrecoverable; we err on the side of caution at every layer.
Rows the backfill enqueues still go through ensureNextJobExists at job execution time, so even within the per-day cap of 50, every dispatch is gated by your campaign's sending window, the per-account ramp ceiling (especially conservative on days 1–14 of warmup), and any active cooldown. The backfill cannot cause an over-send.
What users see after the backfill runs
Customer-facing dashboard changes after a backfill run:
- "Sent today" counter increments as queued follow-ups dispatch through the engine over the next hours/days (subject to your sending window + ramp).
- Prospect detail pages show the next scheduled run time for any newly-queued follow-up. You can see exactly when each prospect's follow-up will fire.
- The "Late accepts" tile on your campaign dashboard shows a brief uptick as the backfill processes the cohort, then drops back to baseline once the cohort is drained. Each row links to the prospect's audit timeline.
- Audit events with
metadata.source = 'pre_ll333_backfill_2026_05_07'are written tolinkedin_eventsper dispatched prospect — visible in our internal dashboards and available for trace export on request.
You will NOT see:
- Bursts of sends. The 50/account/day + sending-window + ramp + cooldown gates ensure the cohort drains gradually.
- Any change to
linkedin_accepted_at,linkedin_status, or workflow positions. The backfill operates at the queue-enqueue layer only — it never re-stamps acceptance signals or rewinds workflow state. - Duplicate messages. The dispatcher's idempotency guards plus the partial unique index on
campaign_send_jobsensure exactly one follow-up per prospect.
Why named backfills (and not silent re-stamping)
Every WarmySender backfill is a named, dated, opt-in script with a DRY-RUN default and explicit CONFIRM=yes commit gate. Three reasons:
- Auditability. The script's
metadata.sourcetag (e.g.,pre_ll333_backfill_2026_05_07) on every audit row makes it trivial to query "what did this backfill change?" months later. - Rollback safety. Backfill effects are limited to
campaign_send_jobsenqueues. We never silently re-stamp acceptance timestamps, never rewind workflow, never bypass safety gates. If a backfill misbehaves, the worst outcome is a few extra (gated) sends — not a data corruption. - Customer transparency. Named backfills appear in our internal change log and in customer comms when the cohort is large enough to be visible to the user.
This is the same discipline behind every LinkedIn-side change at WarmySender: explicit, capped, observable, reversible.
Common questions
My follow-up landed within seconds of the accept — was that the auto-fire or the backfill?
The auto-fire (V15+ webhook-time path). The May 7 backfill targets prospects whose accepts arrived BEFORE V15 landed; if your accept arrived after, the auto-fire path handled it. You can tell which by checking the audit event tags: late_accept_webhook_auto_fire = V15+ webhook path; pre_ll333_backfill_2026_05_07 = V16 backfill; late_accept_auto_fire = heal-tick origin.
My prospect accepted weeks ago and just got a follow-up — was something broken?
Yes, but not catastrophically. Pre-V15, late accepts relied on the 6-hour heal-tick (rate-capped 200 rows/tick) which couldn't keep up with backlogs in the hundreds. Some prospects waited 100+ hours between accept and follow-up. The May 7, 2026 V16 backfill picked up your prospect from this stuck cohort and enqueued the missing follow-up via the same safety-gated pipeline the live engine uses. We're sorry for the delay — it was caused by the rate-cap on the backstop heal-tick, not by your campaign configuration.
Will the backfill send messages outside my campaign's sending window?
No. The backfill enqueues jobs into campaign_send_jobs, which the engine then runs through the same safety gates as every other dispatch. If the engine picks up a backfilled job at 23:00 and your sending window is 09:00–17:00, the job is automatically deferred to 09:00 the next morning. Your authored intent always wins.
Will the backfill exceed my LinkedIn daily message cap?
No. Two layers protect this: (1) the backfill's own per-account/day cap of 50 enqueues prevents queueing more than 50 follow-ups against any single account in any given run; (2) at execution time, the engine reads your account's current daily counter and defers any job that would exceed the cap. The combined effect is that even a 1000-prospect backfill on one account drains gradually over many days at most 50/day, never burst.
Why was my account specifically affected?
Pre-V15, every late accept on every account on the platform relied on the 6-hour heal-tick. Accounts with high invite volume + a high late-accept rate (typically because of a longer wait_accept timeout, OR many accepts arriving outside the original window) accumulated the largest backlogs. Sales Nav-heavy accounts and accounts with longer-than-default wait windows were over-represented in the May 7 cohort. The V15 fix removes the bottleneck for everyone going forward; the backfill drains the historical pile-up.
How can I see which of my prospects were touched by the backfill?
Email hello@warmysender.com with your workspace name and we'll send a CSV export of all prospects whose follow-ups were enqueued by the backfill. The data lives in linkedin_events with metadata.source = 'pre_ll333_backfill_2026_05_07' and can be exported per workspace.
Is this related to the May 7 V16 webhook source-tag fix and the auto-resubscribe?
Yes — both are part of the same V16 batch. The source-tag normalization made dashboard queries on late-accept dispatch volume reliable (queries on the canonical source + branch_taken pair now match every event, where pre-V16 about half were missed because the source tag varied). The webhook auto-resubscribe ensures customer accounts stay observable when an upstream webhook source drops — see Why am I still getting re-login emails when my account shows Connected? for the full picture.
If V15 is the fix, why do we need the backfill?
V15 fixes the GOING-FORWARD path — every NEW late accept after May 7 uses the webhook-time auto-fire. But prospects whose accepts arrived BEFORE May 7 are stuck in the rate-capped heal-tick queue. The backfill is the one-shot drainage of that historical cohort. After the backfill completes, the heal-tick reverts to its low-volume backstop role (most ticks find 0 rows) and steady-state dispatch is purely webhook-driven.
Related guides
- Why a late LinkedIn accept may take time to message — V15 webhook-time auto-fire vs heal-tick backstop, worked example
- What is a "late accept"? — Definition + dashboard tile + manual-followup CTA
- Why some accept webhooks are not actioned — Orphan-webhook handler explainer
- Why am I still getting re-login emails when my account shows Connected? — V16 auto-resubscribe
- How cap enforcement works — Per-account daily limits + ramp + cooldown
- How WarmySender handles load — Queue architecture + recurring heals + cache-layer best practices
- LinkedIn rate limits — Per-account daily and weekly limits
- LinkedIn campaign documentation — Schedule, sending windows, ramp, acceptance lag
- Full documentation — All guides
- Support — How to get in touch
Still have questions? Email hello@warmysender.com with your workspace name and we'll dig into the specific prospects and timing.