Skip to content
TTopMail

Email

Send transactional emails, batch messages, and check delivery status. All email endpoints support idempotency via the Idempotency-Key header.

Send Email

POST
/email/send

Send a transactional email to one or more recipients

NameTypeRequiredDescription
tostring | string[]RequiredRecipient email address, or an array of up to 100 email addresses
subjectstringRequiredEmail subject line (max 998 characters)
htmlstringOptionalHTML body content. Required if text and template_id are not provided.
textstringOptionalPlain text body content. Required if html and template_id are not provided.
template_idstring (UUID)OptionalID of a saved template to use. Required if html and text are not provided.
template_dataRecord<string, string>OptionalKey-value pairs for template variable substitution using {{variable}} syntax
from_emailstringOptionalSender email address. Falls back to workspace default. Used as the Reply-To address. Emails are sent from your workspace's shared sending domain unless you have a verified custom domain.
from_namestringOptionalSender display name (max 100 characters). Defaults to workspace setting or "TopMail".
reply_tostringOptionalReply-to email address
track_opensbooleanOptionalEnable open tracking. Defaults to true.
track_clicksbooleanOptionalEnable click tracking. Defaults to true.
send_atstring (ISO 8601)OptionalSchedule delivery for a future time. Must be within 72 hours.
curl -X POST https://api.topmail.so/api/v1/email/send \
-H "Authorization: Bearer tm_live_abc123" \
-H "Content-Type: application/json" \
-d '{
"to": "user@example.com",
"subject": "Welcome to TopMail",
"html": "<h1>Hello {{first_name}}</h1><p>Thanks for signing up!</p>",
"from_email": "hello@yourdomain.com",
"from_name": "Your App",
"reply_to": "support@yourdomain.com",
"track_opens": true,
"track_clicks": true
}'
{
"data": {
"sent": 1,
"failed": 0,
"skipped": 0,
"total": 1,
"results": [
{
"to": "user@example.com",
"message_id": "ses-message-id-12345",
"success": true
}
]
}
}

Multiple Recipients

Pass an array of up to 100 email addresses in the to field. Each recipient is processed independently -- suppressed or invalid addresses are skipped without failing the entire request. The response includes per-recipient results.

Shared Sending Domain

No domain setup is needed to start sending. Every workspace includes a shared sending domain. Emails are sent from your shared domain (e.g., nick@abc123.send.topmail-manage.com), and from_email is set as Reply-To so replies go back to you. To send directly from your own domain, verify a custom domain in Settings > Domains.

Scheduled Sends

Include a send_at parameter with an ISO 8601 datetime to schedule an email for future delivery. The schedule must be within 72 hours from the current time.

curl -X POST https://api.topmail.so/api/v1/email/send \
-H "Authorization: Bearer tm_live_abc123" \
-H "Content-Type: application/json" \
-d '{
"to": "user@example.com",
"subject": "Your weekly digest",
"html": "<h1>Weekly Digest</h1><p>Here are your updates...</p>",
"send_at": "2025-01-15T09:00:00Z"
}'

Sandbox Mode

When using a sandbox API key (prefixed with tm_test_), emails are not actually sent. The API returns mock success responses with simulated message IDs, allowing you to test your integration without sending real emails or consuming your email quota.

Email Compliance

TopMail automatically ensures every email includes the compliance elements required by CAN-SPAM and other email regulations. Before sending, each HTML email is checked for two things: a working unsubscribe link (using TopMail's merge tags) and a physical mailing address.

Has {{unsubscribe_url}} + physical address

TopMail uses your footer as-is and only adds subtle TopMail branding.

Has {{unsubscribe_url}} but no address

TopMail keeps your unsubscribe link and appends your company name, address (from Settings > Brand), and branding.

Missing unsubscribe merge tag

TopMail appends a full compliant footer with unsubscribe link, preference center, company address, and branding. External unsubscribe links are not recognized — you must use TopMail's merge tags.

Set up your brand details

Go to Settings > Brand and fill in your company name and physical address. The auto-generated footer uses these values, so keeping them accurate ensures your emails look professional and stay compliant.

Building your own footer?

Use {{unsubscribe_url}} and {{preferences_url}} in your HTML. These are replaced with working TopMail-managed links at send time. Include a physical address alongside them to fully control your footer.

Batch Send

POST
/email/batch

Send up to 1,000 emails in a single request. Each message can have unique content, recipients, and settings.

Each item in the messages array accepts the same fields as the single send endpoint (except send_at), but with a single to address per message.

NameTypeRequiredDescription
messagesarrayRequiredArray of 1-1,000 message objects
messages[].tostringRequiredRecipient email address
messages[].subjectstringRequiredEmail subject line
messages[].htmlstringOptionalHTML body content
messages[].textstringOptionalPlain text body content
messages[].template_idstring (UUID)OptionalTemplate ID to use
messages[].template_dataRecord<string, string>OptionalTemplate variable substitution
messages[].from_emailstringOptionalSender email (defaults to workspace setting)
messages[].from_namestringOptionalSender display name
messages[].reply_tostringOptionalReply-to address
messages[].track_opensbooleanOptionalEnable open tracking (default: true)
messages[].track_clicksbooleanOptionalEnable click tracking (default: true)
curl -X POST https://api.topmail.so/api/v1/email/batch \
-H "Authorization: Bearer tm_live_abc123" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"to": "alice@example.com",
"subject": "Your order shipped!",
"html": "<p>Hi Alice, your order #1234 is on the way.</p>"
},
{
"to": "bob@example.com",
"subject": "Your order shipped!",
"html": "<p>Hi Bob, your order #5678 is on the way.</p>"
}
]
}'
{
"data": {
"batch_id": "batch-uuid-12345",
"status": "completed",
"total": 2,
"sent": 2,
"failed": 0
}
}

Batch Status

GET
/email/batch/:batchId

Check the status of a batch send

NameTypeRequiredDescription
batchIdstringRequiredThe batch ID returned from the batch send endpoint (URL parameter)
curl https://api.topmail.so/api/v1/email/batch/batch-uuid-12345 \
-H "Authorization: Bearer tm_live_abc123"
{
"data": {
"batch_id": "batch-uuid-12345",
"status": "completed",
"total": 100,
"sent": 98,
"failed": 2,
"created_at": "2025-01-15T08:30:00.000Z",
"completed_at": "2025-01-15T08:30:45.000Z"
}
}

Message Status

GET
/email/:messageId/status

Look up the delivery status of a sent message

NameTypeRequiredDescription
messageIdstringRequiredThe SES message ID returned from the send endpoint (URL parameter)
curl https://api.topmail.so/api/v1/email/ses-message-id-12345/status \
-H "Authorization: Bearer tm_live_abc123"
{
"data": {
"message_id": "ses-message-id-12345",
"status": "delivered",
"to": "user@example.com",
"sent_at": "2025-01-15T08:30:00.000Z",
"delivered_at": "2025-01-15T08:30:02.000Z",
"opened_at": "2025-01-15T09:15:30.000Z",
"clicked_at": "2025-01-15T09:16:00.000Z",
"bounced_at": null,
"bounce_type": null
}
}

Idempotency

The send and batch endpoints support idempotent requests to prevent duplicate sends caused by network retries. Include an Idempotency-Key header with a unique value (e.g., a UUID). If a request with the same key has already been processed, the original response will be returned without resending the email.

curl -X POST https://api.topmail.so/api/v1/email/send \
-H "Authorization: Bearer tm_live_abc123" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id-12345" \
-d '{
"to": "user@example.com",
"subject": "Order confirmation",
"html": "<p>Your order has been confirmed.</p>"
}'

Bulk Sending Best Practices

Follow these guidelines when sending high volumes of email via the API:

Use the batch endpoint

Use POST /email/batch (up to 1,000 messages per request) instead of looping individual calls to POST /email/send. This reduces your API request count and is significantly more efficient.

Use idempotency keys for safe retries

Always include an Idempotency-Key header on send and batch requests. If a network error occurs, you can safely retry the same request without risking duplicate sends.

Handle rate limits gracefully

If you receive a 429 response, wait for the number of seconds in the Retry-After header before retrying. Use exponential backoff for consecutive 429s. Check the X-RateLimit-Remaining header to proactively throttle before hitting the limit.

Be aware of workspace sending limits

In addition to the per-key API rate limit (requests per minute), your workspace has hourly and daily email sending limits. These are separate from API rate limits and apply to the total number of emails delivered. Check your workspace settings for current limits.

Need higher limits?

If your use case requires higher API rate limits or email sending quotas, contact TopMail support to discuss your needs.

Developer Docs - TopMail | TopMail