Skip to main content
The Telnyx Email API is a full email platform: transactional and marketing sending with attachments, templates, scheduling, and batch operations — plus open/click/unsubscribe tracking, automatic suppressions, group-scoped unsubscribe management, and a complete event lifecycle from queued to delivered. This guide covers the sending surface in depth. For receiving email, managing inboxes, or tracking events, see the Email API Overview. All requests use the production base URL https://api.telnyx.com/v2 and an Authorization: Bearer YOUR_API_KEY header.

Send a message

Send an email with POST /email_messages. The only required fields are from and to; subject is required unless you’re sending with template_id.
curl

Address fields

Body, headers, and attachments

Send an attachment by base64-encoding its content:
curl
Request attachments[] fields: Response attachments[] returns filename, content_type, url, sha256, size_bytes, disposition, and content_id. The base64 content you submitted is never returned — fetch the stored file from url instead.

Templates

Send with a stored template instead of inline bodies. Provide template_id and optional template_variables for Liquid rendering. When you use a template, subject is optional — the template’s subject is rendered; if the template has no subject or renders empty, the request returns 400.
curl
Non-object template_variables values may cause a 422 validation error on message creation. Pass an object.

Schedule a send

Set scheduled_at to an ISO 8601 timestamp in the future to schedule the message. The response returns 202 with "status": "scheduled" and a scheduled_at field.
curl
Invalid or past scheduled_at timestamps are silently ignored and the email is sent immediately.
send_at is a deprecated alias for scheduled_at. It is still accepted on requests for backward compatibility, but responses always return scheduled_at. When both are supplied, scheduled_at wins. Use scheduled_at in new integrations.
Cancel a scheduled message before it sends with DELETE /email_messages/{email_id}/schedule:
curl
A successful cancel returns 200 with the message in "status": "cancelled". The scheduled_at value persists on the record even after cancellation.

Idempotency

Pass an optional Idempotency-Key HTTP header to safely retry a send without creating duplicates. Generate a unique UUID v4 for each logical request, then reuse the same key only when retrying that operation with the same request body. Keys are retained for up to 24 hours, and only successful responses are replayed. Do not include sensitive data in the key.
curl
The first successful request returns its normal status and response body. A retry with the same key and request returns the stored status and body and adds this response header:
The header is omitted for first-time requests and error responses. Reusing a key with a different request returns 422 with code 10027; sending the same key while the original request is still running returns 409 with code 10036. Empty, duplicate, malformed, or overlong key headers return 400 with code 10015. If Edge cannot provide idempotency protection for a keyed request, it fails closed with 503 and code 10016.
idempotency_key is not a request-body field. Put the key only in the Idempotency-Key HTTP header.

Response

A successful send returns 202 Accepted with the message in data:
Key response fields:
  • status — the message lifecycle status (see below).
  • id — the message UUID. Use it with GET /email_messages/{id}, GET /email_messages/{id}/events, and DELETE /email_messages/{id}/schedule.
  • created_at — when the message was created.
  • scheduled_at — present when a future send was scheduled.
  • recipient_statuses — a map of per-recipient status to count, present once recipient rows exist.
  • sandbox — present when the message was created in sandbox mode.
Message status, recipient status, and event types are three different taxonomies. A message is a request; each recipient has its own delivery outcome; engagement signals such as opens and clicks are events, never message statuses.

Message status

The parent message status is request-lifecycle only — it does not describe delivery.

Recipient status

Each recipient carries its own delivery outcome. These are surfaced in recipient_statuses and through the recipients endpoints. The Events API uses a separate event-type taxonomy — several recipient statuses map lossily to a different stored event type (for example, expired, gw_reject, and injection_timeout all produce a failed stored event). For exact recipient status, use recipient_statuses or the recipient endpoints.

Event types

Events record what happened and when. Query them with GET /email_messages/{id}/events or GET /email_events.

Suppressed recipients

When one or more recipients are suppressed at send time, the 202 response includes a top-level suppressed array describing each suppressed recipient. The message is still created for the non-suppressed recipients (if any).
If all recipients are suppressed, the request returns 422 with a recipient_suppressed error and the suppressed array (see Errors).

Tracking

Open, click, and unsubscribe tracking are configured through the tracking object on an email domain:
curl
Tracking is scoped to the sending profile, not to an individual domain. The domain endpoint is the API surface, but the setting is stored on your account’s default sending profile. Every domain that shares that profile shares one tracking configuration, so updating tracking through one domain changes it for all of them. Registering a domain with tracking in POST /email_domains therefore only works for the first domain on the account — once a profile exists, the request is rejected with a validation error and you must use PATCH /email_domains/{id} instead.
Defaults for a new domain: open_tracking: false, click_tracking: false, unsubscribe_tracking: true. Open and click tracking are opt-in; one-click unsubscribe is on by default because Gmail and Yahoo bulk-sender rules require RFC 8058 unsubscribe support — disable it only if you handle unsubscribes yourself. The tracking endpoints themselves are public (no auth) because email clients and browsers hit them directly.

Open tracking

When open_tracking is enabled, messages with an HTML body get a 1×1 transparent tracking pixel injected before the closing </body> tag (or appended to the HTML when there is no </body>). When the recipient’s email client loads the image, Telnyx records an email.opened event.

Click tracking

When click_tracking is enabled, Telnyx rewrites href links in the HTML body to point through a tracking redirect. When a recipient clicks, Telnyx records an email.clicked event and redirects (HTTP 302) to the original URL. Links already pointing to the tracking service are not double-wrapped.

Unsubscribe tracking

When unsubscribe_tracking is enabled (the default), Telnyx adds List-Unsubscribe and List-Unsubscribe-Post: List-Unsubscribe=One-Click (RFC 8058) headers to outgoing messages, with a signed, unguessable unsubscribe URL. A recipient can unsubscribe via a link (GET) or one-click (POST, sent automatically by supporting email clients). On unsubscribe, Telnyx records an email.unsubscribed event and creates a suppression for the recipient.

Tracking events

Tracking events — email.opened, email.clicked, and email.unsubscribed — are stored as message events and delivered to configured webhooks. Query them per message:
curl
Or list events across the account:
curl
The email.opened event payload includes a first_open boolean that is true for the first open and false for subsequent opens. The email.clicked payload includes the clicked url, and email.unsubscribed includes the method (link or one_click).
A dedicated deep-dive on email webhooks is coming soon. Until then, event data is available via the endpoints above.

Errors

API errors return structured JSON. See the full Error Codes reference for all codes and troubleshooting. The standard error envelope:

Common error codes

recipient_suppressed (422) uses a non-standard envelope with the suppressed array alongside errors:
reputation_suspended (429) uses a string code rather than a numeric Telnyx code:

Batch sending

Send up to 50 messages in a single request with POST /email_messages/batch. Each item in the messages array is a full CreateEmailRequest payload.
curl
The Idempotency-Key applies to the entire batch request. Reuse it only when retrying the same batch body; do not add per-message idempotency keys inside messages.
  • 207 — all batch responses use 207 Multi-Status. When all messages succeed, errors is empty and every item is in data. When one or more fail, the response contains a data array for successes (which may be empty) and an errors array for failures.
The errors array for a batch uses a per-item shape — each entry has index (zero-based position in the request array), code, and message (not detail):
Batch item error codes: bad_request, unprocessable_entity, not_found, forbidden, service_unavailable, validation_error, recipient_suppressed, and reputation_suspended. unprocessable_entity is returned when an individual message exceeds the size limits — the rest of the batch still processes.