SendComms
Email API

Send Email

POST/api/v1/email/send

Send transactional emails to one or more recipients. Supports HTML and plain text content, CC and BCC, attachments, tags, custom headers and idempotency keys. Perfect for welcome emails, password resets, notifications, and more.

Requires API key authentication. Include your key in the Authorization header.
Maximum 50 recipients per message, counted across to + cc + bcc combined. Going over returns 400 TOO_MANY_RECIPIENTS — use the batch endpoint for larger sends.

Select Language

REQUEST
curl -X POST \
  https://api.sendcomms.com/api/v1/email/send \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "user@example.com",
    "cc": ["manager@example.com"],
    "subject": "Welcome to our platform!",
    "html": "<h1>Welcome!</h1><p>Thanks for signing up.</p>",
    "from": "Your App <hello@yourdomain.com>",
    "replyTo": "support@yourdomain.com",
    "headers": {
      "List-Unsubscribe": "<https://yourdomain.com/unsubscribe?u=12345>"
    },
    "tags": [{ "name": "campaign", "value": "welcome" }],
    "idempotency_key": "welcome-user-12345"
  }'

Request Body

ParameterTypeRequiredDescription
tostring | string[]RequiredRecipient email address(es). Counts towards the 50-recipient cap shared with cc and bcc
subjectstringRequiredEmail subject line
htmlstringConditionalHTML content (required if no text)
textstringConditionalPlain text (required if no html)
fromstringOptionalSender address. Must be on a domain verified for your account, otherwise the request fails with 400 UNVERIFIED_SENDER_DOMAIN. Display names are supported: "Acme <hello@acme.com>". If omitted, your primary verified domain is used
replyTostringOptionalReply-To address. Not restricted to your verified domains
ccstring | string[]OptionalCC recipients. Counts towards the 50-recipient cap
bccstring | string[]OptionalBCC recipients — delivered on the envelope only, never shown in the message headers. Counts towards the 50-recipient cap
attachmentsobject[]OptionalFiles to attach, each { filename, content } with base64-encoded content. Up to 20MB total per message
tagsobject[]OptionalLabels for your own reporting, each { name, value }. Returned on the message record and searchable in your dashboard
headersobjectOptionalCustom message headers as string key/value pairs. Commonly used for List-Unsubscribe on bulk sends
idempotency_keystringOptionalYour own unique key for safe retries. Retrying with the same key returns the original result instead of sending again

Response Codes

200Success400Bad request401Unauthorized402No balance409Idempotency key still in flight429Rate limit exceeded

Common Errors

CodeStatusWhen it happens
TOO_MANY_RECIPIENTS400More than 50 recipients across to + cc + bcc
UNVERIFIED_SENDER_DOMAIN400The from address is not on a domain verified for your account
MISSING_CONTENT400Neither html nor text was supplied
INVALID_EMAIL400A to, cc or bcc address is not a valid email address
REQUEST_IN_PROGRESS409An earlier request with the same idempotency_key is still being processed

Sender Verification

The from address must use a domain you have verified for your account. We never silently substitute a different sender, so an unverified domain is rejected outright:

{
  "success": false,
  "error": {
    "code": "UNVERIFIED_SENDER_DOMAIN",
    "message": "The \"from\" domain \"acme.com\" is not verified for your account. Verify it under Domains, or omit \"from\" to use your default sender.",
    "details": {
      "provided_from": "Acme <hello@acme.com>",
      "provided_domain": "acme.com"
    }
  }
}
  • Display names are supported on every address field — Acme <hello@acme.com>.
  • Omit from to send from your primary verified domain.
  • Add and verify domains from the Domains endpoint.

Idempotency

Pass your own idempotency_key to make retries safe. If a request with that key already succeeded, we return the original result instead of sending a second email, and the replay is marked with an X-Idempotent-Replay: true response header.

  • Keys are scoped to your account and cached for 24 hours.
  • While the first request is still in flight, a retry with the same key returns 409 REQUEST_IN_PROGRESS — wait and retry.
  • If the send fails, the key is released so you can retry it cleanly.
  • Use a value that is unique to the action, e.g. welcome-user-12345.

Response

Success Response

When an email is successfully queued for delivery, you'll receive a response containing the transaction ID and email details. Keep transaction_id and email_id — both are echoed back on every webhook event for this message.

{
  "success": true,
  "data": {
    "transaction_id": "email_mjgc0ejr_3ca715bfb7a0",
    "email_id": "msg_8f21c0d4a97b",
    "status": "sent",
    "to": ["user@example.com"],
    "from": "Your App <hello@yourdomain.com>",
    "subject": "Welcome to our platform!",
    "recipients": 1,
    "price": { "amount": 0.01, "currency": "USD" },
    "remaining": 499,
    "quota": { "used": 1, "remaining": 499 },
    "created_at": "2026-08-23T10:30:00.000000+00:00"
  }
}