Runs examples for real against your account. Test keys only; kept in this tab, never stored on our side. Get a key
ZapSMTP / Docs

Send your first email.

One HTTP request or an SMTP connection. Every example on these pages runs for real: paste a test key above and press Try it.

  1. Create an account and a test key. Sign up, open API keys and create a key in test mode. Test keys (zap_test_…) go through validation, logging, events and webhooks, but nothing is delivered and nothing counts against your plan.
  2. Send to the simulator. Addresses at simulator.zapsmtp.xyz act out delivery scenarios: delivered@, bounced@, complained@ and more.
  3. Verify your domain and switch to a live key. Add your domain, publish the DNS records it gives you and verify it. Live keys send only from verified domains.

Base URL https://zapsmtp.xyz/v1. Authenticate with Authorization: Bearer <key>. Requests and responses are JSON.

Send your first email

POST/v1/emails

Send an email (or schedule it with scheduled_at).

Requires a full-access or sending-access key. Live keys: From domain must be verified by the account. Test keys: any From domain, nothing delivered, monthly quota not charged.

Recipients are deduplicated and lowercased; max 50 across to+cc+bcc. Message-ID is <id>@<hostname>, or <id>.<thread_id>@<hostname> for a reply that continues a conversation.

The response also carries thread_id: the conversation this email belongs to. An email with In-Reply-To / References headers (set them in headers) joins the thread of the message it answers; otherwise it starts a new one.

failed_recipients: addresses the mail server refused while it took the others; they are not sent and not charged. unconfirmed: true: the mail server took the message but its answer was lost; it counts as sent and must not be retried (its events tell the outcome).

Scheduled emails (scheduled_at): at most 10,000 (256 MB) may wait per account. At send time the account and the key are checked again: a suspended account or a revoked or expired key fails the email.

With reply tracking on, the response carries reply_to: the tracked address the email carries in place of its own Reply-To. Human replies are forwarded to the domain’s reply_forward_to, else to the email’s first Reply-To, else to its From.

Headers

Idempotency-Keystring
optional, 1-256 bytes (see Idempotency)

Body parameters

fromstring
RFC 5322 address, e.g. "Acme <hello@acme.com>". Domain must be verified (live keys). When only an address is given, the domain's from_name (brand settings) is used as display name. May be omitted when template is used and the template defines from. Limits: missing → 422 missing_required_field; unparsable → 422 invalid_parameter "invalid from address"
tostring | string[]required
Recipient(s). Limits: at least one; each must parse as an address (422 invalid_parameter "invalid to address ...")
ccstring | string[]
Cc recipients.
bccstring | string[]
Bcc recipients.
reply_tostring | string[]
Reply-To address(es).
subjectstring
Subject line. May be omitted when template is used and the template defines a subject. Limits: blank → 422 missing_required_field
htmlstring
HTML body. At least one of html/text is required (unless template is used). Limits: both empty → 422 missing_required_field "html or text is required"; cannot be combined with template (422 invalid_parameter)
textstring
Plain-text body. When only html is given, a plain-text part is generated from it (readable text, links as text (url), hidden elements such as the preheader left out).
headersobject<string,string>
Custom headers. Limits: Reserved headers are rejected (422 invalid_parameter "header ... cannot be set"): From, To, Cc, Bcc, Reply-To, Subject, Date, Message-Id, Mime-Version, Content-Type, Content-Transfer-Encoding, DKIM-Signature, Return-Path, Received, any X-Zap-*, X-ZapRef. Names must be printable ASCII without spaces or ':'.
attachmentsobject[]
Attachments; 40 MB total. Limits: 422 invalid_attachment with message attachments[i]: set either content or path, not both | content must be base64 | content or path is required | filename is required | path must be an http(s) URL | fetching path returned HTTP <n> | attachments exceed 40MB in total
Show 5 fields
filenamestringrequired
File name shown to the recipient.
contentstring
File content, base64-encoded. Set content or path, not both.
pathstring
An HTTPS URL we download the file from instead (private and internal addresses are refused).
content_typestring
MIME type. Detected from the file name when omitted.
content_idstring
Makes the file inline: reference it in the HTML as cid:<content_id>.
tagsobject[]
Returned in webhooks as data.tags. Limits: at most 20 tags per email; name and value must match ^[A-Za-z0-9_-]{1,256}$ (422 invalid_parameter)
Show 2 fields
namestringrequired
Letters, digits, _ and -, up to 256 characters.
valuestringrequired
Same characters, up to 256.
scheduled_atstring
RFC 3339 time (e.g. 2026-10-21T15:00:00Z) or relative "in <n> <unit>" with unit min|mins|minute|minutes|h|hour|hours|d|day|days. Times in the past send immediately. Limits: unparsable → 422 invalid_parameter; more than 7 days ahead → 422 invalid_parameter "scheduled_at must be within 7 days"
metadataobject<string,string>
Returned in webhooks and GET /emails; filter with GET /emails?metadata[key]=value. Limits: max 10 keys; keys ^[A-Za-z0-9_-]{1,40}$; values max 500 bytes (UTF-8) (422 invalid_parameter)
streamstring
Limits: other values → 422 invalid_parameter; conflicting with the key's stream → 403 restricted_api_key One of transactional, broadcast. Default transactional (or the key's stream).
trackingobject
Overrides the From domain's open, click and reply tracking defaults for this email.
Show 3 fields
opensboolean
Track opens for this email.
clicksboolean
Track link clicks for this email.
repliesboolean
Reply tracking: the email carries a signed Reply-To on the domain’s reply_domain, so replies come back tied to this email (email.replied). Needs a reply domain on the sending domain (see Update a domain), else 422 invalid_parameter.
templateobject
Render a stored, published template: id = template id or alias. Request subject/from override the template's. Cannot be combined with html/text. Limits: missing id → 422 missing_required_field "template.id is required"; unknown → 422 invalid_parameter "template <id> not found"; never published → 422 invalid_parameter; variables.global → 422 invalid_parameter (reserved); a value of the wrong type → 422 template_variables_invalid; a required variable without value or default → 422 template_variables_missing; anything else the template uses still absent → 422 template_render_error "missing template variables: a, b"
Show 2 fields
idstringrequired
Template id or alias. The template must be published.
variablesobject
Values for the template’s own variables. Checked against the published variable schema: defaults fill absent values, optional variables without one render empty, names the schema does not declare are passed through. global is reserved: global variables come from the account.
tlsstring
required or verified: deliver only over TLS (verified: with a valid certificate for the receiving server). Raises the domain's policy, never lowers it.

Errors

validation_errorHTTP 400
invalid JSON / unknown field
invalid_idempotency_keyHTTP 400
Idempotency-Key must be 1 to 256 bytes
domain_not_verifiedHTTP 403
sender domain is not verified for this account
restricted_api_keyHTTP 403
key restricted to another domain or stream
account_suspendedHTTP 403
account is suspended
idempotency_key_reusedHTTP 409
idempotency_request_in_progressHTTP 409
payload_too_largeHTTP 413
request body exceeds 40MB
misdirected_cellHTTP 421
the account is served by another cell: "this account is served by cell <cell> at <url>", Zap-Cell: <cell>; send to that URL
missing_required_fieldHTTP 422
invalid_parameterHTTP 422
e.g. too many recipients (max 50), invalid address, bad tags/metadata/stream/scheduled_at, "at least one recipient is required"
invalid_attachmentHTTP 422
template_render_errorHTTP 422
template_variables_invalidHTTP 422
template variables of the wrong type: a, b (checked against the published variable schema)
template_variables_missingHTTP 422
missing required template variables: a, b (required, no value and no default)
rejectedHTTP 422
the mail transfer agent refused the message: <reason>
rate_limit_exceededHTTP 429
rate limit exceeded
monthly_quota_exceededHTTP 429
monthly quota exceeded
daily_quota_exceededHTTP 429
daily sending limit reached; it resets at 00:00 UTC
overloadedHTTP 429
server at capacity ("the server is at capacity; retry shortly", Retry-After: 1) or event pipeline backed up ("the event pipeline is backed up; retry shortly", Retry-After: 5); nothing sent, retry
service_unavailableHTTP 503
a backing store failing over ("a backing service is failing over; retry shortly", Retry-After: 2); MTA unreachable ("mail transfer agent temporarily unavailable, retry"), suppression list being rebuilt ("suppression list is being rebuilt, retry shortly"), or too many large request bodies in flight (Retry-After: 5)
POST/v1/emails
curl -X POST 'https://zapsmtp.xyz/v1/emails' \
  -H "Authorization: Bearer $ZAP_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
  "from": "Acme <hello@acme.com>",
  "to": [
    "delivered@simulator.zapsmtp.xyz"
  ],
  "subject": "Hello from ZapSMTP",
  "html": "<p>It works.</p>"
}'
Set ZAP_API_KEY in your environment.
Response 200 OK
{
  "id": "019a1f2e-7c3b-7d41-9a52-0c4b8e6f1a23"
}

Check what happened

GET/v1/emails/{id}

Retrieve one email with per-recipient status and its event timeline.

Requires a full-access key.

Only data inside the plan's log retention is visible. metadata may be null.

Path parameters

idstringrequired
Email id

Errors

restricted_api_keyHTTP 403
not_foundHTTP 404
email not found (events can take a few seconds to appear)
GET/v1/emails/{id}
curl 'https://zapsmtp.xyz/v1/emails/019a1f2e-7c3b-7d41-9a52-0c4b8e6f1a23' \
  -H "Authorization: Bearer $ZAP_API_KEY"
Set ZAP_API_KEY in your environment.
Response 200 OK
{
  "object": "email",
  "id": "019a1f2e-7c3b-7d41-9a52-0c4b8e6f1a23",
  "from": "Acme <hello@acme.com>",
  "subject": "Your receipt",
  "created_at": "2026-10-21T15:04:10.902Z",
  "recipients": [
    {
      "email": "jane@example.org",
      "status": "delivered",
      "updated_at": "2026-10-21T15:04:12.381Z",
      "response": "250 2.0.0 OK 1761059052 x7si123"
    }
  ],
  "events": [
    {
      "type": "Reception",
      "status": "queued",
      "recipient": "jane@example.org",
      "time": "2026-10-21T15:04:10.902Z",
      "attempts": 0
    },
    {
      "type": "Delivery",
      "status": "delivered",
      "recipient": "jane@example.org",
      "time": "2026-10-21T15:04:12.381Z",
      "response_code": 250,
      "response": "250 2.0.0 OK 1761059052 x7si123",
      "attempts": 1,
      "mx": "gmail-smtp-in.l.google.com.",
      "peer_addr": "142.250.153.27",
      "tls": "TLSv1.3 TLS_AES_256_GCM_SHA384",
      "egress_source": "ip-1"
    }
  ],
  "stream": "transactional",
  "test": false,
  "metadata": {
    "order_id": "A-1042"
  }
}

API reference

Billing, account settings and team management belong to the dashboard and are not in this reference. Team and client-account endpoints (accounts, members, invites) refuse API keys with 403 session_required.

Guides