SMTP relay recipes: send email over HTTP
The SMTP relay capability is a second ingress path for the email channel: your client submits an RFC 5322 message over the authenticated SMTP edge described on Channels → SMTP relay, and the relay maps the session onto the same HTTP payloadPOST /api/v1/messages/email produces. This guide walks the equivalent HTTP flow end to end — the request body with an attachment, the response a happy path returns, the error branches worth coding against, and each SDK’s call — so you can model or verify client behaviour without touching an SMTP socket.
Use this flow before writing SMTP client code, or when you want the SMTP relay’s pipeline guarantees (suppression, verified-sender enforcement, shared rate pool) while keeping payloads and error codes fully visible.
1. Send with an attachment
POST /api/v1/messages/email accepts either html or text (at least one is required) plus an optional attachments array — each entry carries filename, content_type, and either a url (an Orbit Files API URL) or inline base64 content.
content_type must be on the MIME allowlist — see Email attachments for the full list and for when to use a Files URL instead of inline base64. The Idempotency-Key header makes client retries safe: the same key replays the original response instead of double-sending.
The same body shape is exactly what the SMTP relay produces after mapping an RFC 5322 submission — envelope RCPT TO recipients become to, the From: header becomes from, and the HTML part (preferred over plain text) becomes html.
2. The 202 response envelope
A successful send returns202 with the message record under data plus a meta block that also carries the request id you saw in the X-Request-Id response header:
status at acceptance time is queued (or scheduled when you pass scheduled_at); poll GET /api/v1/messages/{id} or consume the message-status webhooks to follow it to sent/delivered. price is the credits cost of the send.
3. Error branches to handle
Every error returns the standard envelope{ "error": { "code", "message", "status" }, "meta": { ... } }. Three code values account for most production failures on this path:
INVALID_RECIPIENT — 422
The recipient address failed the per-recipient gate (malformed mailbox, list-hygiene check, or suppression). The message is refused before queueing and you are not charged.CHANNEL_NOT_CONFIGURED — 503
The tenant has no working email provider: a sender domain was never verified, the provider credentials were revoked, or the channel bootstrap skipped the lane. Nothing is queued.554 after DATA is the SMTP-session equivalent of this branch). Surface details.provider_message if present for the provider-side detail.
RATE_LIMITED — 429
The shared email rate pool (default 200 messages per minute per tenant, shared with SMTP-relay ingress and burst fanout) is exhausted. The response usually carries aretry_after hint in details.
retry_after window, then retry. Because HTTP and SMTP relay traffic draw from one pool, moving sends between the two paths does not dodge the limit — see Rate limits.
Other codes you may see on this path: VALIDATION_ERROR (bad payload shape or attachment limit), QUOTA_EXCEEDED (billing cap), and MESSAGE_SEND_FAILED (provider-side failure — retry once, then alert). The full catalog lives on the Email channel page.
4. SDK snippets
Each SDK wrapsPOST /api/v1/messages/email; the examples below send the same attachment email and read the same code branches. Attachments with URL url fields are supported on all four SDKs; inline base64 content follows the same body shape shown in the curl above.
Node.js — @devotel-orbit/node
Python — orbit_sdk
Go — devotel/orbit
PHP — devotel/orbit
retry_after value in details is your best backoff hint — use it rather than a fixed sleep.
Where it connects back to SMTP
Once the client works over HTTP, point it at the SMTP edge only if HTTP is not practical — see Connection profile on the SMTP relay channel page for ports and theapikey sentinel. The same API key gates both paths, the same sender domain gates them both, and suppression drawn from either path applies to the other.