> ## Documentation Index
> Fetch the complete documentation index at: https://docs.12m.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# No-reply timers

> Send-and-react without running your own scheduler. The primitive that turns 12m into a sequencer backend.

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:

```bash theme={null}
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).

| Input          | Means        |
| -------------- | ------------ |
| `"4h"`         | 4 hours      |
| `"1d"`         | 1 day        |
| `"3 days"`     | 3 days       |
| `"90 minutes"` | 90 minutes   |
| `"1.5h"`       | 90 minutes   |
| `14400000`     | 4 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:

```json theme={null}
{
  "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):

```json theme={null}
{
  "type": "email.no_reply",
  "seq":  44,
  "data": {
    "convId":   "conv_dd6e...",
    "identity":    "alice.acme@inboxbase.ai",
    "afterMessageId": "<msg-id>",
    "waitedMs": 14401234
  }
}
```

A typical handler:

```js theme={null}
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.
