Start with the message. Fall back to the request.
A request ID identifies one API attempt. A message ID identifies the durable email across every asynchronous stage.
Two IDs, two questions
What happened in this API call?
Validation, policy denial, accepted request, or idempotent replay. Every retry gets a new request ID.
What happened to this email?
Queue state, provider attempts, recipient events, and webhook outcomes. Idempotent retries converge on one message ID.
Use either reference
GET /v1/email-traces/msg_01example
GET /v1/email-traces/req_01exampleThe response is tenant-scoped and support-safe. It includes request outcomes, message and recipient state, safe provider attempt metadata, normalized events, and webhook delivery status. It excludes bodies, attachments, credentials, raw provider payloads, and webhook response content.
TypeScript SDK
const accepted = await client.sendEmail(message, {
idempotencyKey: "receipt:v1:order_8291",
})
const trace = await client.traceEmail(accepted.id)
console.log(trace.requests, trace.provider_attempts, trace.events)Triage by state
queuedDurably accepted; waiting for provider submission.policy_heldStored but blocked until current policy permits submission.submittedThe provider accepted the submission.unknownProvider outcome is ambiguous. Investigate; do not retry blindly.bouncedDelivery failed for at least one recipient.complainedA recipient reported spam; suppression protects future sends.