Skip to Content
Emails

Emails

Password resets, receipts, magic links. Same verified domain and same monthly allowance as your campaigns: one provider, one reputation.

The request shape is Resend-compatible. Migrating usually means changing the base URL, the key and from.

The Email object

{ "id": "eml_0dz4k9x2v7q1", "object": "email", "from": "Acme <noreply@yourdomain.com>", "to": ["jane@acme.com"], "reply_to": null, "list_unsubscribe": null, "subject": "Reset your password", "status": "delivered", "error": null, "tags": { "type": "password-reset" }, "sandbox": false, "created_at": "2026-08-21T09:00:00.000Z", "sent_at": "2026-08-21T09:00:01.000Z", "delivered_at": "2026-08-21T09:00:03.000Z", "bounced_at": null, "complained_at": null }

Status

queued → sent → delivered, or it ends at bounced, complained, failed or suppressed.

StatusMeaning
queuedAccepted, waiting for the worker.
sentHanded to the mail provider. Not yet delivered.
deliveredThe receiving server accepted it.
bouncedCame back. The address may now be suppressed.
complainedMarked as spam. The address is suppressed.
failedNever left. error says why.
suppressedBlocked before sending: the address is unmailable.

Delivery is asynchronous. Transactional sends are top-priority jobs and never wait behind a campaign, so they usually complete in seconds.

Send an email

POST/v1/emails
curl -X POST https://go.day3.app/api/v1/emails \ -H "Authorization: Bearer $DAY3_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: pwreset-user-8412-1755764400" \ -d '{ "from": "Acme <noreply@yourdomain.com>", "to": ["jane@acme.com"], "subject": "Reset your password", "html": "<p>Click to reset.</p>", "text": "Click to reset.", "reply_to": "support@yourdomain.com", "headers": { "X-Entity-Ref-ID": "pwreset-8412" }, "tags": { "type": "password-reset" } }'

Body

FieldTypeRequiredNotes
fromstringYesaddr or "Name <addr>". Domain must be verified.
tostring or string[]YesUp to 50 addresses. See Multiple recipients.
subjectstringYesControl characters are rejected.
htmlstringOne ofHTML body.
textstringOne ofPlain-text body. Send both when you can.
reply_tostringNoAny address, on any domain.
headersobjectNoUp to 20 custom headers. Reserved names are rejected.
tagsobjectNoFlat string map for your own filtering.

Send at least one of html or text. Returns 200 with the Email object, status: "queued".

Always set Idempotency-Key. A retried password reset that sends twice is a support ticket. See Idempotency.

from must be on a verified domain

Verify the domain in the app under Sending. Then any local part works with no sender setup: noreply@, receipts@, billing@.

An unverified domain is 403 domain_not_verified. Verify it; don’t retry.

The day3 test address

Every new organization gets one pre-verified address on a day3-owned domain, so your first send needs no DNS:

acme@sandbox.day3.app

The local part comes from your organization name. Find the exact address in the app under Sending, on the card above the domain list.

A send from the test address must be a sandbox send: your account is in sandbox mode and every recipient is a member of your organization. It stops working the moment you upgrade. Anything else is 403 domain_not_verified with a message saying so, including a send to a teammate on a paid plan.

The address shares a sending reputation with every day3 account, so it only reaches as far as sandbox limits do. The check fails closed: anything it can’t prove is a sandbox send is refused.

  • The footer carries day3’s postal address, not yours.
  • It never counts as your verified domain in the app.

Use it to get working on day one. Verify your own domain before you upgrade; the only code change is from.

Multiple recipients

to takes a string or an array of up to 50 addresses. An array is one message with every address in the To header. Everyone sees everyone. For 50 private emails, send 50 requests.

Addresses are trimmed, lowercased and de-duplicated. Each one left counts against your monthly allowance.

Custom headers

Up to 20. These are rejected with 400:

  • Body-derived: From, To, Subject, Reply-To and friends.
  • MIME plumbing.
  • Authentication and trace: DKIM-Signature, Authentication-Results, ARC-*, Received, Sender, Resent-*.
  • The unsubscribe headers.
  • day3’s own X-Account-ID and X-Transactional-Email-ID.
  • Anything starting with X-SES-.

Limits

LimitValue
Recipients per message50
Custom headers20
html + text + headers + tags1.5 MB of UTF-8
Rate120 sends per minute per organization

Control characters are rejected in subject, header values and addresses. A display name can’t contain <, >, " or \. Both are header-injection defence.

Retrieve an email

GET/v1/emails/{email_id}

Returns the Email object plus events, the provider’s delivery timeline:

{ "id": "eml_0dz4k9x2v7q1", "object": "email", "status": "delivered", "events": [ { "type": "sent", "created_at": "2026-08-21T09:00:01.000Z" }, { "type": "delivery", "created_at": "2026-08-21T09:00:03.000Z" } ] }

Bodies are pruned after 30 days. The metadata row is kept forever, and the API reports the content as expired rather than returning an empty string.

List emails

GET/v1/emails

Newest first, cursor-paginated. Filter with ?status=queued|sent|delivered|bounced|complained|failed|suppressed.

curl "https://go.day3.app/api/v1/emails?status=bounced&limit=100" \ -H "Authorization: Bearer $DAY3_API_KEY"

Suppression

Transactional mail follows different rules from campaigns:

  • Unsubscribes don’t block it. Leaving a newsletter must never stop a password reset.
  • Hard bounces, complaints and provider suppressions do, with 400 email_suppressed. Don’t retry. It damages your reputation.

Bounces and complaints here feed the same suppression list and the same reputation auto-pause as campaigns. There is no separate reputation.

Sandbox mode

On a free plan, sends are real but only reach members of your organization, on a small monthly allowance. Responses carry "sandbox": true.

SituationResponse
Recipient outside your organization403 sandbox_recipient_not_allowed
Allowance exhausted403 plan_limit_reached

Upgrading lifts both with no code change. See Authentication.

Errors

StatusCodeCause
400invalid_requestMissing required field, reserved header, or over a size limit.
400invalid_emailAn address is not valid.
400email_suppressedThe address hard-bounced, complained, or is provider-suppressed.
403domain_not_verifiedfrom domain not verified, or the test address on a non-sandbox send.
403sandbox_recipient_not_allowedFree plan, recipient outside the organization.
403plan_limit_reachedMonthly send allowance exhausted.
403sending_disabledSending is off for this account.
429rate_limit_exceededOver 120 sends per minute. Honour Retry-After.

Delivery events

Polling GET /emails/{id} works. Webhooks are better: day3 POSTs email.delivered, email.bounced, email.complained and suppression.created as they happen. Use suppression.created to keep your own database honest about dead addresses.

Last updated on