New features, improvements, and fixes to Dial

August 20, 2026

Added

Know when a message is delivered, rejected, or read

Sending a message told you Dial accepted it, and nothing more — the carrier’s actual verdict arrived later with no way to hear about it. A new message.status_changed event now publishes each advance on the account stream, so you can react the moment a message lands or is rejected instead of polling GET /api/v1/messages.

Delivery and reads are reported as two independent axes, because not every rail reports both. deliveryState covers pendingdelivered | undelivered | failed, plus unconfirmed for rails that send no delivery receipts. readState covers unreadread, plus unsupported for rails that never report reads. Each event names which axis moved in changed and carries the current value of both, so one event is always a complete picture. Failures carry a plain-language deliveryError.

Every field is flat, so the stream is directly filterable — dial wait-for message.status_changed -f deliveryState=failed waits for a rejection. The event is also subscribable as a webhook.

GET /api/v1/messages gains the matching deliveryState, readState, readAt, and deliveryError fields, so a fetch and a stream event never disagree.

Changed

status is delivery-only; reads moved to readState

A message’s status no longer returns read. Reads now live on the independent readState and readAt fields, which means a message can be reported as both delivered and read — previously one value had to stand for both, so a read receipt hid the delivery confirmation. A read message now reports status: "delivered" with readState: "read".

status and statusError keep working and still describe delivery only. statusError is identical to the new deliveryError. status is the same axis as deliveryState in the older vocabulary, differing on two values: sent is pending, and unknown is unconfirmed. If you branch on status === "read", move to readState === "read".