Status & tracking

Follow a letter from queued to delivered — the status lifecycle, the event timeline, webhook events, and milestone emails.

Every letter leaves a trail. Dairo tracks each letter through printing, posting, and delivery, and keeps its status and event timeline current — there is no carrier feed for you to integrate. Read the letter back by id, subscribe to a webhook event, or let Dairo email you at each milestone.

The lifecycle

A letter's status is a stable enum you can build on. The happy path runs:

queued → processing → printable → submitted → in_transit → delivered
                                                         └→ undeliverable
StatusMeaning
draftCreated with autoSend: false. Stored but held — nothing is printed or posted. A draft can be canceled freely.
queuedDairo accepted the letter and is preparing it for print. The status a normal autoSend: true create returns.
processingThe document is being validated and made ready for production. A letter that is held (see below) also stays here.
printableValidated and ready to print.
submittedHanded off for printing and posting. submittedAt is set.
in_transitPrinted, enclosed, and posted — the letter is moving through the postal network.
deliveredDelivered to the recipient. Terminal.
undeliverableThe carrier could not deliver the letter. Terminal.
canceledYou canceled the letter before dispatch. canceledAt is set. Terminal.
failedValidation or production failed before posting; the letter's error field says why. Terminal.

A letter that was sent with autoSend: true never goes back to draft. Once Dairo has handed it to production it moves forward only.

Test mode and held letters

Every letter carries providerMode. live means it will be printed and posted. test means it is validated and priced but never printed, mailed, or charged. A letter is in test mode when you sent it with dryRun: true or when the account's print mode is test.

To learn the account's print mode before you send, read letters.printMode from GET /v1/whoami (the MCP get_account_info tool with action: "whoami"). GET /v1/letters/requirements carries the same value as printMode.

A test-mode letter stops at processing. Its timeline gets one event that says why:

{
  "object": "letter_event",
  "type": "processing",
  "code": "test_mode",
  "description": "Created in test mode: the letter was validated and priced but will not be printed, mailed, or charged.",
  "reason": "Created in test mode: the letter was validated and priced but will not be printed, mailed, or charged."
}

A test-mode letter stays at processing permanently. It never advances, it is never printed, posted or charged, and it does not expire. Cancel it to close it out: POST /v1/letters/{id}/cancel, or the MCP send_letters tool with action: "cancel". A dryRun: true send creates no letter at all.

A live letter that production holds back gets the same kind of event with code: "hold".

An undeliverable letter cannot be re-routed — correct the address and create a new letter. A failed letter was never posted; fix the document and create it again.

Cancellation is only possible before dispatch — while a letter is draft, queued, processing, printable, or submitted. Once it is in_transit it cannot be recalled; a late cancel returns 409 with code: "letter_not_cancelable". The cancel call itself is on Send a letter.

The event timeline

Alongside its status, each letter carries a list of letter_event objects — the detailed delivery timeline. The timeline comes inlined on GET /v1/letters/{id} (newest first, up to 100 events) or on its own from GET /v1/letters/{id}/events.

{
  "object": "letter_event",
  "eventId": "lev_2",
  "letterId": "let_01HZX...",
  "type": "in_transit",
  "code": "handed_to_carrier",
  "description": "Handed to postal carrier",
  "location": null,
  "reason": null,
  "hasImage": false,
  "occurredAt": "2026-06-23T08:00:00Z",
  "recordedAt": "2026-06-23T08:00:11Z"
}
FieldMeaning
typeThe lifecycle stage the event reports, in the same vocabulary as the letter status — for example submitted, in_transit, delivered, undeliverable, failed.
codeA machine-readable detail code, when the event carries one; null otherwise.
descriptionA short human label for the event.
producerThe source that reported the event, when known; null otherwise.
locationWhere the event was recorded, when known; null otherwise.
reasonWhy the event happened, when known: the reason for an undeliverable event, or why a letter is held (test_mode, hold). null otherwise.
hasImageWhether a scan or image is associated with the event.
occurredAtWhen the event happened in the field. Order the timeline by this.
recordedAtWhen Dairo recorded the event.

Order a letter's history by occurredAt, not the time you fetched it — delivery updates can land slightly out of order.

Track a letter

Poll it. The most direct way to follow a letter is to fetch it by id: GET /v1/letters/{id} returns the current status and the event timeline (the print partner reports no tracking numbers, so a letter carries none). Fetching an in-flight letter refreshes its tracking state, so a poll returns the latest known status.

Subscribe to it. Every status change emits a letter.status_changed event carrying the letterId, the new status, and the batchId when the letter belongs to a batch. Point a webhook at your endpoint to receive it. The same event is also kept in the durable event stream, alongside everything else happening in your account. These events fire regardless of the email notifications setting below.

Owner email notifications

Dairo can also email the account owner at each delivery milestone — no code, no endpoint. When a letter reaches a milestone, the owner gets a short status email ("Your letter to Zürich, CH is on its way") with a link to the letters page in the dashboard.

Milestone emails are on by default. Turn them off for a single letter with notifications: false — useful for high-volume sends where you would rather watch the timeline than receive an email per milestone. Batches accept the same flag once for the whole run.

curl -X POST https://api.dairo.app/v1/letters \
  -H "Authorization: Bearer $DAIRO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "pdfBase64": "JVBERi0xLjQK...",
    "fileName": "invoice-2026-06.pdf",
    "to": {
      "name": "Jane Doe",
      "street": "Hauptstrasse", "houseNumber": "12",
      "postalCode": "8001", "city": "Zürich", "country": "CH"
    },
    "delivery": "economy",
    "notifications": false
  }'

When a notification email is sent

An email goes out only when the status changes into one of the delivery milestones — once per transition, never for a re-confirmed update, and never for internal churn like queued or processing.

Status reachedNotification
submitted"…has been handed off for printing and posting."
in_transit"…is on its way."
delivered"…was delivered."
undeliverable"…could not be delivered" (with the reason, when known).
failed"…could not be sent."

The remaining statuses — draft, queued, processing, printable, and canceled — never trigger an email.

Next steps

  • Send a batch — track many letters together with a live per-status rollup.
  • Pricing — what a letter costs before you send it.

On this page