Skip to main content
Every send arms a timer. If the recipient doesn’t reply within the configured window, we fire an email.no_reply event and add a no_reply_expired entry to the conversation timeline. You opt in by passing noReplyEventAfter on the send:
curl https://api.inboxbase.ai/v1/identities/alice.acme@inboxbase.ai/send \
  -H "Authorization: Bearer sk_live_..." \
  -d '{
    "to":                "morgan@northwindrobotics.com",
    "subject":           "Following up",
    "text":              "...",
    "noReplyEventAfter": "4h"
  }'
That’s the entire interface. We schedule the alarm; you handle the event when it fires.

Why we built it

Every cold-outbound system on Earth has reinvented this clock. You send an email, you write down (leadId, sendId, fireAt) somewhere, you run a worker that scans for due rows every minute, you cancel the row when a reply lands, you wonder why the worker drifted by an hour, you discover your scheduler doesn’t survive a deploy. We already own the conversation. We already see the inbound. The natural home for the timer is next to the conversation, not in your DB. So we moved it. You stop running a scheduler.

The window

noReplyEventAfter accepts duration strings (LLM- and human-friendly) or a raw number (interpreted as milliseconds).
InputMeans
"4h"4 hours
"1d"1 day
"3 days"3 days
"90 minutes"90 minutes
"1.5h"90 minutes
144000004 hours (ms)
Default is 1d if you omit the field. Floor is 1 minute — anything shorter rounds up. Below 60 seconds the alarm churn isn’t useful (no human replies to email in 30 seconds anyway).

Cancel and reset

The timer’s lifecycle is a small state machine and the rules are worth knowing:
  • A new outbound resets the timer. If you reply to your own thread before the recipient does (a “bumping” follow-up), the clock starts over from that send’s window.
  • An inbound reply cancels the timer. No email.no_reply event fires; the conversation is now in a “they replied” state.
  • Each outbound-without-reply fires the event once. The event isn’t a recurring schedule. If you want repeated nudges, send another message — the next timer arms automatically.
The conversation’s state shows you whether a timer is currently armed:
{
  "noReplyAt":      1777286400000,
  "lastNoReplyAt":  null
}
noReplyAt is the future timestamp the alarm will fire at, or null if no timer is currently armed. lastNoReplyAt is when the most recent no_reply_expired event happened (or null if it never has).

When the event fires

The event payload is the standard shape with a data.waitedMs field that tells you the actual elapsed time (which can be slightly longer than the requested window because alarms aren’t preempted to the millisecond):
{
  "type": "email.no_reply",
  "seq":  44,
  "data": {
    "convId":   "conv_dd6e...",
    "identity":    "alice.acme@inboxbase.ai",
    "afterMessageId": "<msg-id>",
    "waitedMs": 14401234
  }
}
A typical handler:
case "email.no_reply": {
  const lead = leads.findByConvId(ev.data.convId);
  if (lead.currentStep + 1 < sequence.steps.length) {
    lead.currentStep += 1;
    fireStep(lead);                  // POST /send with {convId, text}
  } else {
    lead.status = "exhausted";
  }
  save(lead);
  break;
}
That’s it. No scheduler, no cron, no worker watching a queue. The send arms; we wait; the event arrives; you decide what to do next.

Picking windows

A few rules of thumb that hold up across most outbound playbooks:
  • First follow-up: 2–4 days. Short enough that the original send is still in their head, long enough that they don’t feel rushed.
  • Subsequent follow-ups: longer. Each step in a sequence should wait longer than the last. 3 → 7 → 14 days is a reasonable curve.
  • Don’t go below an hour for outbound to humans. It looks desperate. The 1-minute floor exists for testing and for transactional use cases (like an OTP that didn’t land).
  • Match the timezone roughly. A 1d timer crosses one day boundary. If you sent at 11pm Friday recipient-local, a 1d timer fires Saturday night and the follow-up lands when nobody’s reading. Pick 2d or 3d for evening sends if you don’t want the next message to land on the weekend.
These are heuristics, not constraints we enforce.