PosthasteDocsGet an API key

Reference

Live tail

The message log answers what happened. This answers what is happening — the same append-only events, delivered as they are written, usually within a tenth of a second of the send being accepted.

The endpoint

GET/v1/messages/tailmessages:read

A text/event-stream response that stays open. It carries the same events the audit log holds and the waybill shows, so nothing here is a second source of truth — it is the one record, sooner.

Nothing polls, on either side. The connection is idle until an event lands, at which point the database wakes it. You are not being charged a request per second for a tab left open, and neither are we.

Filters

The same three the message log takes, meaning the same three things. Everything else is deliberately absent: a tail is for watching a slice of live traffic, and a search box on a stream that has not happened yet would be a search over nothing.

ParameterEffect
tagRepeatable, and ANDed?tag=a&tag=b means both, exactly as on the message log. Up to ten.
streamOne stream, by slug. Mail on any other stream is not on this tail at all.
statusThe message’s status as of the moment the event is read, not at the moment it happened — a message that later bounces has all of its events read as bounced. Worth knowing, because a status is a property of a message and not of an event.
replayHow much recent history to open with, up to 100. Defaults to 20 so the screen is not blank. Ignored on a resume — see below.
afterResume from this seq, for callers that cannot set a header. Last-Event-ID wins when both are present.

What arrives

EventMeaning
readyThe stream is open. Carries the filters as we understood them, the heartbeat interval and the idle timeout. Show a connected state on this.
messageOne event on one message. Carries seq, the event type, when it happened, the message with its current status, stream and tags, and the event payload as detail. The SSE id: is the seq.
heartbeatEvery 15 seconds, whether or not anything is happening. Its absence is the signal — see the note below.
byeWe closed the stream, and the reason says why: idle, error or unavailable. Reconnect and resume.

Only message carries an id:, so a heartbeat never moves your resume position.

Watch for the heartbeat stopping, not just for an error. A TCP connection can be dead for minutes without either end being told, and a tail that has silently died looks exactly like a tail with nothing to report. If two heartbeats do not arrive, treat the stream as gone and reconnect — the resume below means it costs you nothing to be wrong about that.

A dropped connection loses nothing

Every message event carries your account’s seq as its SSE id. Reconnect with that value in Last-Event-ID — which a browser does for you — and everything after it is replayed, in order, before the stream goes on following.

Move your cursor after you have handled an event, never before. That is the one rule, and it is what turns a reconnect from a hope into a guarantee: at worst you see an event twice, which you can spot by seq, instead of never seeing it at all.

# Every message event carries its account seq as the SSE id.
# Remember the last one you handled, and hand it back.

curl -N \
  -H "Authorization: Bearer $POSTHASTE_API_KEY" \
  -H "Last-Event-ID: 4187" \
  "https://api.posthastemail.dev/v1/messages/tail"

# Or, for anything that finds setting a header awkward:
curl -N \
  -H "Authorization: Bearer $POSTHASTE_API_KEY" \
  "https://api.posthastemail.dev/v1/messages/tail?after=4187"

# Both mean the same thing: everything after 4187, in order, then follow.

Limits

LimitValueWhat happens
Concurrent tails5 per accountA sixth is refused with 429 too_many_streams, which names the limit. Waiting does not fix it — closing a tail does, immediately.
Idle timeout15 minutesWith no message event in that window the stream closes with bye {"reason":"idle"}. Reconnect and resume; heartbeats do not reset it, deliberately.
Reconnect delay3 secondsSent as retry: at the top of the stream. Honour it — every reconnect costs an authentication and a replay.

Account-level events with no message — anything the audit log holds that is not about a specific send — are not on this tail. It streams mail moving, so an event with no recipient, status, stream or tags would be a blank line no filter could ever match. The audit log has all of them.

Examples

curl -N \
  -H "Authorization: Bearer $POSTHASTE_API_KEY" \
  "https://api.posthastemail.dev/v1/messages/tail?replay=5"

# -N matters. Without it curl buffers the response and you see nothing
# until the connection ends, which for a stream is never.

retry: 3000

event: ready
data: {"resumedFrom":null,"filters":{"status":null,"stream":null,"tags":[]},"heartbeatSeconds":15,"idleTimeoutSeconds":900}

id: 4187
event: message
data: {"seq":4187,"id":"evt_AZLm3kQ8T2Sf9pXbNc7HrQ","type":"accepted","at":"2026-08-26T09:14:02.418Z","message":{"id":"msg_9Kd2XbQ1T4Rf8pLmNc3HrA","to":"[email protected]","subject":"Your password reset","status":"queued","stream":"transactional","tags":["password-reset"]},"detail":{}}

event: heartbeat
data: {"at":"2026-08-26T09:14:17.002Z"}

NextDelivery analyticsDelivery rate and time to inbox, p50 and p95, for every receiving provider and for each of your own tags — with nothing invented where there is nothing to measure.