Skip to content
YourMail

API reference

Send email

POST/v1/emails

Queue a single email for delivery.

Why use this

This is the heart of YourMail: send a single transactional email — a receipt, a password reset, a welcome message — straight from your app with one API call. There's no mail server to run and no SMTP to configure, and you get back an ID so you can follow whether it was delivered.

For example

A customer checks out in your shop. Your server makes one call to send, and they get their order confirmation — from your own address, tracked end to end.

Example

import { YourMail } from "yourmail";

const yourmail = new YourMail(process.env.YOURMAIL_API_KEY!);

const { id } = await yourmail.send({
  from: "hello@mail.acme.com",
  to: ["alice@example.com"],
  cc: ["bob@example.com"],
  bcc: ["archive@acme.com"],
  replyTo: "support@acme.com",
  subject: "Welcome to Acme",
  html: "<h1>Hello Alice</h1>",
  text: "Hello Alice",
  attachments: [
    {
      filename: "invoice.pdf",
      content: "<base64-encoded content>",
      contentType: "application/pdf",
    },
  ],
  tags: [{ name: "category", value: "welcome" }],
  idempotencyKey: "welcome-usr_123",
  headers: {
    "List-Unsubscribe": "<mailto:unsub@acme.com>",
    "X-Campaign-ID": "launch-2026",
  },
});

console.log(id);

Request body

All fields are JSON. from, to, subject, and at least one of html / text are required.

fromstring
RequiredA bare address (hello@mail.acme.com) or one with a display name (Acme Support <hello@mail.acme.com>) — the display name is what the recipient's inbox shows. Names containing a comma or dot are quoted for you. Must be on a verified domain you own; verification is domain-level, so once mail.acme.com is verified every address on it sends — support@, hello@, no-reply@ — with no per-address setup anywhere. The one exception is no-reply@yourmail.dev, the shared sandbox sender, which puts the send in test mode instead. Ownership is checked against the address, never the display name.
tostring | string[]
RequiredOne or more recipient addresses, each with an optional display name. Combined with cc + bcc: max 50 total. In test mode (from: no-reply@yourmail.dev) any recipient other than your signup email or an SES simulator address is silently rewritten to your signup inbox (cc/bcc dropped), not rejected — you may omit to entirely and it defaults to your own inbox.
ccstring | string[]
OptionalCarbon-copy recipients, display names allowed. Counts towards the 50-recipient cap.
bccstring | string[]
OptionalBlind-copy recipients, display names allowed. Counts towards the 50-recipient cap.
replyTostring
OptionalReply-To address, display name allowed. Must be a valid email. Worth setting on anything a human might answer: a from address is not a mailbox. Verifying a domain authorises sending from it, it does not create anywhere to receive — that needs MX records. If your domain has none (check with dig MX acme.com), every reply to your from address bounces, and the bounce goes to the recipient rather than to you, so you never find out. Point this at an inbox someone reads.
subjectstring
RequiredNon-empty string.
htmlstring
ConditionalHTML body. At least one of html or text is required. Combined html + text must be under 1 MB (UTF-8 bytes).
textstring
ConditionalPlain-text body. At least one of html or text is required.
attachmentsAttachment[]
OptionalMax 20 files. Each file: max 10 MB decoded. Total: max 25 MB decoded. Each item needs filename (string) and content (base64 string); contentType defaults to application/octet-stream.
tagsTag[]
OptionalMax 10 tags. Each tag is { name: string; value: string }. Used for filtering and analytics.
idempotencyKeystring
OptionalIf a send with this key was already queued, the original id is returned without re-sending. Safe to retry on network failures.
scheduledAtstring | number
OptionalHold the message until this time (ISO 8601 or Unix ms), up to 30 days ahead. A time at or before now sends immediately rather than erroring; Unix seconds are rejected with a 400, because read as ms they land in 1970 and would send at once. The message reads “scheduled” until it dispatches and can be cancelled with DELETE /v1/emails/:id until then — quota is charged at dispatch, so cancelling costs nothing. Outstanding scheduled sends are capped at your plan’s monthly allowance.
headersobject
OptionalCustom mail headers as a string→string object. Max 10 headers; each name max 64 chars; each value max 1 KB; combined total max 4 KB. Reserved headers (From, To, Subject, Content-Type, DKIM-Signature, etc.) are rejected.
bulkboolean
OptionalMarks the send as bulk (newsletters, announcements) rather than transactional. Requires exactly one `to` recipient and no cc/bcc — anything else is a 400. On a live send we inject one-click List-Unsubscribe headers so mailbox providers show an unsubscribe button, unless you set those headers yourself (test-mode sends go to your own inbox, so they get none). Recipients who unsubscribe are blocked from bulk mail only; your transactional email still reaches them. Defaults to false.

Response

On success the API returns HTTP 200 with the new email ID.

// 200 OK
{ "id": "jn7abc123def456" }

Errors

All errors follow the shape { error: { type, message, docs_url } }, where docs_url links straight to that type's entry. See Errors for the full reference. Common errors for this endpoint:

400validation_error

Missing or invalid field (from, to, subject, html/text)

401authentication_error

API key missing, malformed, or not recognised

403domain_error

Sending domain not verified or not owned by this account

403account_pending_review

Account not yet approved — see below

413validation_error

Body exceeds 1 MB, or attachment exceeds 10 MB / 25 MB total

422suppressed_recipient

Recipient is on this account's suppression list

422sandbox_recipient

Test mode only, and rare: a disallowed recipient is normally rewritten to your signup inbox rather than rejected — this fires only when your account has no signup email on file to rewrite to

429rate_limited

Per-second burst exceeded — Retry-After header tells you when to retry

429quota_exceeded

Daily or monthly send quota exhausted

New accounts are reviewed before they can email the outside world. Until a human approves your account, a send may only reach an address at a domain you have verified, your own account email, or an AWS SES simulator address — anyone else gets 403 account_pending_review. This is not a bug and not rate limiting, so don't retry it: keep integrating and testing against your own verified domain, which exercises the identical send path, while review usually finishes within a few hours. See account_pending_review for the full reference, or email support@yourmail.dev if it's blocking a launch.