API Reference
API Reference
GitHub
Email Sending

Email Status

Track email delivery status

Every send answers with an id. This route turns that id back into delivery state, without the bodies, headers or attachments the full record carries — it is the cheap call to poll.

GET /api/v1/emails/{id}/status
curl http://localhost:3000/api/v1/emails/0198f6a1-3c7e-7b21-9f4d-2a5c8e0b1d33/status \
  -H "Authorization: Bearer myk_..."
{
    "id": "0198f6a1-3c7e-7b21-9f4d-2a5c8e0b1d33",
    "status": "sent",
    "attempts": 1,
    "error_message": "",
    "sent_at": "2026-01-01T00:00:01Z"
}

Five fields, and that is the whole response. Use GET /api/v1/emails/{id} when you need the message itself.

Machine API schema

The /api/v1 surface describes itself: run mailyard export-api-spec and feed the document to Swagger UI, Postman, or a client generator.

The states

StatusMeaningMoves on
queuedAccepted and waiting for a workerWhen a worker claims it
scheduledHeld for a future send_atAt that time
processingClaimed and being handed to SMTPWithin one attempt
sentAn SMTP server accepted itTerminal
failedEvery attempt was spentTerminal, unless retried
suppressedEvery recipient was blocked before sendingTerminal

A seventh value, pending, is accepted as a filter on the email log for historical reasons and is never written. Filtering on it always returns nothing.

sent means accepted, not delivered

It records that a receiving SMTP server took the message. What happens after that — a mailbox that is full, a spam folder, a delayed bounce — arrives separately, as a bounce or a webhook . A message can be sent and bounced at the same time, and both facts are true.

Between queued and sent a message may be tried against several SMTP servers. attempts counts attempts, not servers — a single attempt can walk a whole failover chain before it gives up.

Retry

POST /api/v1/emails/{id}/retry

Only a failed message can be retried. Anything else is refused, naming the state it is actually in:

only failed emails can be retried (status is "sent")

Retry resets the attempt counter

The message goes back to queued with attempts set to zero and the error message cleared, so it gets a full set of tries again rather than resuming where it left off.

That is what you want after fixing the cause — new credentials, a corrected DNS record. It is not a way to nudge a message that failed for a permanent reason, which will simply spend the whole budget again.

The response is the full email record, not the status summary. The worker is woken immediately, so a healthy queue picks the message up in the same second rather than at the next poll.

Polling

For a transactional send the interesting window is short — most messages leave queued within seconds. Poll this route every few seconds while the status is pending, queued or processing, and stop once it is not.

Do not poll a scheduled message. It is waiting on purpose, possibly for days, and the answer is the send_at you already know. The console makes the same distinction for the same reason.

For anything longer-lived, webhooks push the transitions instead of you asking for them.