Send your first transactional email
This is the one-shot path to a first real send: verify your domain, create a sender, render a template, dispatch, and watch the result arrive on a webhook. Everything below is runnable — substitute your own API key, domain, and recipient. For the full day-one-to-at-scale plan (warm-up ramps, bulk patterns, deliverability posture), see the Email channel lifecycle guide. This page gets you to one delivered email; that one takes you to volume. You will:- Verify your sending domain
- Create a sender profile
- Send a templated message
- Subscribe to delivery and bounce events
- Handle the suppression list
- Debug the common first-send errors
- Hand off to the at-scale plan
dv_test_sk_...), they short-circuit
before delivery but still exercise validation, so the integration code you
write in the sandbox runs unchanged on live keys. Do the domain
verification in step 1 before you flip to live traffic.
1. Verify your sending domain
Everything downstream — sender validation, tracking, deliverability — hangs off a verified domain. Add your domain under Channels → Email → Domains, which lists the exact DNS records (SPF, DKIM, DMARC, and the Return-Path CNAME) to publish at your DNS provider. Once the records propagate, run Verify DNS on the domain row and confirm the per-record traffic-light view grades green:valid. If your DNS lives
in a Cloudflare zone, the
Cloudflare auto-configure guide
can create the missing records for you from the same domain row; either
way, re-run Verify DNS after any manual change.
2. Create a sender profile
Sends need a registered sender row so the platform can bind yourfrom
address to a verified domain. Register one under Channels → Email →
Senders, or over the API:
esend_...). When you later omit
from on a send, the sender marked isDefault resolves the address; set
that flag on the one you want as the fallback. One sender row is enough
for a first send — add aliases later.
3. Send a templated message
The send endpoint takes a finishedhtml (or text) body — it does not
accept a template_id. Either html or text must be non-empty, or the
request is rejected before anything is queued.
The one-shot send — inline body, curl, done:
data.succeeded carrying one row
per recipient, and the envelope meta.request_id to correlate with the
webhook events below.
For template variables ({{...}} merge tags in a saved block layout) you
render first, then send the rendered HTML — two passes:
- Render with
POST /api/v1/messages/email-builder/render. The response carries the renderedhtml, an AMP-for-Email alternative, and atemplateIdto correlate with a later save. - Resolve every
{{...}}tag in the rendered HTML, then dispatch it through the same send endpoint. An unresolved tag lands on the wire as the literal string, so resolve them all before sending.
POST /api/v1/messages/email-builder/save — the full mechanics are in
Using Templates.
4. Subscribe to delivery and bounce events
Register one endpoint that subscribes the delivery family and the engagement pair, and filter onchannel: "email":
message.delivered— accepted by the recipient servermessage.failed— bounced (hard or soft) or marked as spamemail.opened/email.clicked— engagement (first-open deduped; clicks fire on every distinct click)
X-Orbit-Signature header on every delivery and return 2xx
within the delivery window — the walkthrough is in
Receive your first webhook. In sandbox
mode the send is short-circuited, so you can exercise your verification
and retry handling without a live recipient; the
sandbox guide covers the canned outcomes.
5. Handle the suppression list
Hard bounces, exhausted soft bounces, complaints, and unsubscribes are held out of future sends automatically, and the served quota slot is refunded at the pre-send gate. Manage the list yourself under Channels → Email → Suppressions — search it, export, add manual entries, or bulk-import from a previous ESP. Removal is a deliberate action: a re-bounce or a re-complaint re-suppresses the address. For a per-contact reachability check before you route a send, use the contact deliverability health guide.6. Common first-send errors
- Domain unverified (403). A provider-side
403returns as403 VALIDATION_ERRORwithdetails.provider_message. Most often this is a sending domain whose verification regressed or never completed. Re-run Verify DNS, then checkGET /api/v1/email/domains/:domainId/dns-statusuntil every record gradesvalid. CHANNEL_NOT_CONFIGURED(503). The email provider was never set up for the tenant. Finish step 1, then your sender in step 2.- Suppressed recipient (
RECIPIENT_OPTED_OUT, 422). The recipient is on your suppression list and the quota slot is refunded. Route the flow to an alternate channel instead of un-suppressing by reflex. - Invalid
from(NO_SENDER_CONFIGURED, 422). No sender resolved — either no row owns the address and no default applies, or the defaulted address never got registered. Re-check step 2. VALIDATION_ERROR(422) on the body. Malformed addresses, an empty body, or a disallowed attachment. Eitherhtmlortextmust be non-empty; unresolved merge tags would ship the literal{{...}}string to the recipient, so resolve them all before dispatch.RATE_LIMITED(429). You crossed the per-tenant channel pool (roughly 200 sends per minute). Back off and retry.