Reference
Status feed
The status page in machine-readable form, so you can surface our status inside your own dashboard instead of asking somebody to keep a browser tab open. No API key, no rate limit, no versioning.
The endpoint
GET https://posthastemail.dev/status/data
{
"overall": "degraded",
"components": [
{
"id": "api",
"name": "API",
"description": "Accepting sends and serving the REST API.",
"state": "operational",
"note": null
},
{
"id": "smtp",
"name": "SMTP submission",
"description": "Accepting mail submitted over SMTP.",
"state": "operational",
"note": null
},
{
"id": "outbound",
"name": "Outbound delivery",
"description": "Delivering accepted mail to recipient mail servers.",
"state": "degraded",
"note": "Delivery is running behind. Mail is queued, not lost."
},
{
"id": "inbound",
"name": "Inbound mail",
"description": "Receiving bounces, complaints and replies.",
"state": "operational",
"note": null
},
{
"id": "webhooks",
"name": "Event webhooks",
"description": "Posting delivery events to your endpoints.",
"state": "operational",
"note": null
}
],
"checkedAt": "2026-08-25T20:54:28.952Z",
"stale": false
}posthastemail.dev and not from api.posthastemail.dev, which is deliberate and is the whole reason it is usable. A status endpoint that lives on the API host goes down with the API — exactly when you need it. This one is served by a different process on a different container, so when the API is unreachable this feed keeps answering and tells you so.Fields
| Field | Meaning |
|---|---|
| overall | operational, degraded, partial_outage or major_outage. A roll-up of the components, nothing more — read the components if you want to know what is actually wrong. |
| components[].id | Stable and safe to key on: api, smtp, outbound, inbound, webhooks. New ids may be added; existing ones will not be renamed. |
| components[].name, .description | Human-readable, and free to change. Display them if you like, but do not match on them. |
| components[].state | One of the four states below. Handle an unrecognised value as unknown rather than as healthy. |
| components[].note | A short sentence, or null. Drawn from a fixed vocabulary — never an error message, and never anything derived from an exception. |
| checkedAt | RFC 3339, UTC. When the underlying checks last completed — not when you made this request. Compare it against your own clock and stop trusting the payload once it stops moving. |
| stale | true when our own checks have stopped reporting. Every component reads unknown in that case, because stale good news is worse than an admission of ignorance. |
What the four states mean
Green and red alone would lie about this platform. Accepting mail and delivering it are different jobs done by different processes, and the outage customers actually feel is the quiet one where the first keeps working perfectly and the second has stopped — every process alive, every health check passing, and nothing arriving. That is what degraded is for.
| State | What it means for you |
|---|---|
| operational | Working normally, and confirmed within the last few seconds. |
| degraded | Still working, but not properly. On outbound this almost always means we are accepting your mail and delivering it late: the queue is not draining, so messages you sent are sitting waiting rather than being refused or lost. On webhooks it means the same thing about events reaching your endpoint. Nothing is dropped. Nothing needs re-sending — a retry here would duplicate a message that is still on its way. |
| outage | Not working. Anything already accepted stays queued and goes out on recovery, so a send that returned 202 is still our responsibility. New sends against a component in this state will fail. |
| unknown | We cannot currently tell. Published as its own state rather than guessed at in either direction — usually because a component has not reported since a deploy, or because our own checks have stopped completing. |
unknown as healthy. It is the state that exists so we never have to round a gap in our knowledge up to green, and collapsing it back to operational in your own code undoes that.Polling it
// Poll no faster than every 30 seconds. The verdict behind this feed is
// recomputed on a fixed clock, so a tighter loop returns the same bytes and
// only costs you requests.
const res = await fetch('https://posthastemail.dev/status/data')
const status = await res.json()
// Treat anything that is not 'operational' as "tell somebody".
// 'unknown' is deliberately NOT the same as 'operational' — it means we
// could not tell, and silently rounding it up to healthy is how a
// dashboard ends up green through an outage.
const unhappy = status.components.filter((c) => c.state !== 'operational')
// Old data is not current data. If the feed stops moving, say so rather
// than continuing to display whatever it last said.
const ageMs = Date.now() - Date.parse(status.checkedAt)
if (status.stale || ageMs > 120_000) {
showBanner('Posthaste status is not currently reporting')
}| Property | Value |
|---|---|
| Authentication | None. Sending an API key is harmless and achieves nothing. |
| Rate limit | None. The response is a cached snapshot, so it costs us the same whether one person reads it or a hundred thousand do — and throttling a status feed during an incident would turn one problem into two. |
| Caching | s-maxage=10, stale-while-revalidate=30. Expect a verdict up to about twenty seconds old; the checks behind it run on a fixed clock regardless of traffic. |
| Recommended interval | 30 seconds. Faster returns the same bytes. |
| Cross-origin | Allowed from any origin, so a browser dashboard can read it directly. There is no credential on this response and there never will be. |
What it does not cover
Everything here is platform-wide. Delivery to one particular recipient domain can fail while every component is green — receivers defer and block for their own reasons — and no platform status feed can tell you about that. For a specific message, read its waybill through the messages API, which records every attempt and the exact response the receiving server gave.
There are also no incident histories, uptime percentages or scheduled-maintenance windows in this feed. It reports what is true right now, measured, and nothing that would require somebody to have remembered to type it in.
NextErrors →Every error type the API returns, what causes it, and whether to retry.