https://api.telnyx.com/v2 and an Authorization: Bearer YOUR_API_KEY header.
Send a message
Send an email withPOST /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
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. Providetemplate_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
Setscheduled_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.DELETE /email_messages/{email_id}/schedule:
curl
200 with the message in "status": "cancelled". The scheduled_at value persists on the record even after cancellation.
Idempotency
Pass an optionalIdempotency-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
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 returns202 Accepted with the message in data:
status— the message lifecycle status (see below).id— the message UUID. Use it withGET /email_messages/{id},GET /email_messages/{id}/events, andDELETE /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 inrecipient_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 withGET /email_messages/{id}/events or GET /email_events.
Suppressed recipients
When one or more recipients are suppressed at send time, the202 response includes a top-level suppressed array describing each suppressed recipient. The message is still created for the non-suppressed recipients (if any).
422 with a recipient_suppressed error and the suppressed array (see Errors).
Tracking
Open, click, and unsubscribe tracking are configured through thetracking 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.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
Whenopen_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
Whenclick_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
Whenunsubscribe_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
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).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 withPOST /email_messages/batch. Each item in the messages array is a full CreateEmailRequest payload.
curl
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 use207 Multi-Status. When all messages succeed,errorsis empty and every item is indata. When one or more fail, the response contains adataarray for successes (which may be empty) and anerrorsarray for failures.
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):
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.