Errors

Every error has the shape { statusCode, message, name, docs_url }. Branch on name; message says what to do next, and X-Request-Id identifies the request.

CodeStatusCauseResolution
validation_error400A field has the wrong type or format or is over its size limit, the body is not valid JSON, or the body was not sent as JSONRead message for the specific problem and how to fix it, then send the request again
invalid_idempotency_key400The Idempotency-Key header is empty or longer than 256 charactersSend a key of 1 to 256 characters, such as a UUID, or leave the header out
missing_api_key401The request has no Authorization: Bearer <key> headerSend the agent key or the reviewer key as Authorization: Bearer <key>
invalid_api_key401The key in Authorization is not one this service issuedCheck the key for typos, then use the agent key or the reviewer key you were given
invalid_permission403The key is valid but not allowed here: agent keys never approve or send, reviewer keys never change draftsDo not retry with another key. Agents poll GET /drafts/{id} until status is rejected, sent or failed; only the agent key changes drafts
not_found404No route matches the method and path, or no resource has the id in the pathCheck the method and path against the API reference, and the id against the one the create response returned
missing_required_field422The body is missing one or more required fieldsAdd the fields named in message and send the request again
missing_required_parameter422The query string is missing one or more required parametersAdd the parameters named in message and send the request again
invalid_parameter422A path or query parameter has the wrong format, such as a draft id that is not a UUIDUse the format message gives for the path or query parameter it names, then send the request again
invalid_from_address422from is not an email address, or it is not on the domain this service sends fromLeave from out to send from the default address, or use an address on the domain message names
revision_stale409The revision the request names is not the current revision of the draft, usually because another revision was saved firstFetch the draft, apply your change to current_revision, and send it again with its id
content_hash_mismatch409The content_hash sent with an approval is not the hash of the revision it names, so what the reviewer saw is not what would be sentReload the draft, review current_revision again, and approve it with its own content_hash
draft_locked409The draft is approved, sending or sent, so it can no longer changePoll GET /drafts/{id} until it is sent or failed, then follow next_step. Create a new draft only for a different email
invalid_state_transition409The draft is not in a status this action accepts, such as submitting a draft that was approvedFetch the draft, read status and next_step, and do what next_step says
resource_locked409Other requests kept changing the draft while this one was being savedWait a moment, fetch the draft, and send the request again
invalid_idempotent_request409The Idempotency-Key was used in the last 24 hours for a request with a different bodyUse a new key for a new draft. To get the first response again, send the original body with the same key
concurrent_idempotent_requests409Another request with the same Idempotency-Key is still in progressWait a second and send the same request again with the same key: once the first one finishes, the retry returns its response
missing_webhook_signature401A request to POST /webhooks/resend has no svix-id, svix-timestamp or svix-signature header, so it is not a Resend webhookOnly Resend calls this route; agents never do. Resend signs every event, so check that nothing between Resend and the service drops the svix-* headers
invalid_webhook_signature401The svix-signature of a webhook does not match its body and RESEND_WEBHOOK_SECRET, or its svix-timestamp is more than 5 minutes from nowNothing was recorded. Set RESEND_WEBHOOK_SECRET to the signing secret of this webhook in the Resend dashboard and redeploy; Resend retries the event
webhook_not_configured503RESEND_WEBHOOK_SECRET is not set, so the service cannot verify Resend webhooks and records no delivery eventsThe operator sets RESEND_WEBHOOK_SECRET to the signing secret of the webhook in the Resend dashboard and redeploys; Resend retries the events it could not deliver
internal_server_error500Something failed inside the serviceRetry a GET, or POST /drafts with the same Idempotency-Key and body. Otherwise fetch the resource to check whether the change was saved before you retry. If it keeps failing, report the X-Request-Id

Send failures

After approval the service sends the email through Resend, always with the Idempotency-Key send-email/<revision_id>, so a retry can never deliver it twice. Each failed attempt is recorded with one of these reasons. Retried reasons put the send back in the queue until its next attempt (the Retry-After Resend sent, or a backoff from 30 seconds to 30 minutes), and the draft stays locked; when the 10 Inngest retries run out, recover-stalled-sends starts a new run. Any other 4xx, a daily or monthly quota error included, ends the send and moves the draft to failed, with failure_reason rejected when no earlier attempt could have delivered the email, and outcome_unknown when one could.

ReasonRetriedCauseResolution
rate_limitedYesResend answered 429 without a quota error name, usually rate_limit_exceeded: too many requests per secondNothing: the send waits for the delay in Retry-After when Resend sends one, otherwise backs off exponentially, and tries again
server_errorYesResend, or the network in front of it, answered with a 5xx statusNothing: the send backs off and tries again with the same key
in_progressYesResend is still handling an earlier request with the same key (concurrent_idempotent_requests or resource_locked)Nothing: the send tries again shortly
networkYesThe request timed out or never reached Resend, so the email may or may not have been sentNothing: the retry carries the same key, so Resend sends it at most once
unknown_responseYesResend answered without an error name or a statusNothing: the send tries again with the same key
rejectedNoResend refused the email with a 4xx, such as validation_error, invalid_from_address, an unverified domain, or the 429 daily_quota_exceeded or monthly_quota_exceeded, and no earlier attempt could have delivered itIf the error is about the email (an address, the subject or a body), fix it in a new revision. If it is about the sending domain or the API key, the operator fixes that first. After a quota error, wait for the reset instead: a daily quota resets at midnight UTC and a monthly one at the start of the month, and neither needs the operator. Nothing was delivered, so a new revision with the same content can then be submitted and approved again
outcome_unknownNoAn attempt whose outcome is unknown (a timeout, a 5xx, a concurrent request) may have delivered the email, and the send cannot be retried safely: 23 hours have passed since that attempt, and Resend forgets an idempotency key after 24, or a later attempt got a 4xxThe agent may prepare a new revision, but a person looks for the email in the Resend dashboard before approving it. If the last error is about the sending domain or the API key, the operator fixes that first; after a quota error, wait for the quota to reset; otherwise the new revision fails the same way

API reference · OpenAPI document