Email

Transactional email

Send one-to-one email such as receipts, password resets and notifications. The endpoint accepts Brevo's v3 payload, so existing Brevo integrations work after changing the base URL and API key.

POST/v3/smtp/emailPermission smtp:send

Messages are accepted, validated and queued; the response returns as soon as the message is queued. Delivery, opens, clicks and bounces are tracked afterwards — see Statuses & events.

Request body

ParameterTypeDescription
senderrequiredobject{ name?, email }. The email must be on a verified sender domain of your brand, otherwise the request fails with 400 invalid_parameter.
torequiredobject[]Recipients, each Object { email, name? }. Required unless every entry of messageVersions has its own to.
ccobject[]Carbon-copy recipients, each Object { email, name? }.
bccobject[]Blind carbon-copy recipients, each Object { email, name? }.
replyToobjectObject { email, name? } used as the Reply-To address.
subjectstringSubject line; may contain template tags. Required unless the template provides one.
htmlContentstringHTML body, rendered with the template language. Either htmlContent, textContent or templateId is required.
textContentstringPlain-text body (also rendered with the template language).
templateIdintegerNumeric id of a saved template (the id returned by GET /v3/smtp/templates). Its HTML and subject are used unless you pass htmlContent / subject. Unknown ids return 404 document_not_found; inactive templates return 400.
paramsobjectValues available as {{ params.KEY }} in the subject and body. Values may be strings, numbers, booleans, arrays and nested objects.
headersobjectCustom email headers, e.g. {"X-Order-Id": "A-1077"}. Values are sent as strings.
tagsstring[]Labels stored with the message for filtering logs and statistics in the portal.
attachmentobject[]Files attached to every message: each item is { content, name } (base64) or { url, name? }. Up to 10 files, 5 MB each, 10 MB in total. See attachments.
scheduledAtstring (date-time)RFC 3339 timestamp with offset, up to 72 hours ahead, e.g. 2026-11-01T09:00:00+08:00. The request then returns 202. See scheduled sending.
messageVersionsobject[]Up to 1000 personalised versions sent in one request. See batch sending.

Send HTML content

curl -X POST 'https://app.snappymails.com/v3/smtp/email' \
  -H "api-key: $SNAPPYMAILS_API_KEY" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "sender": {
      "name": "Acme Shop",
      "email": "orders@acme.com"
    },
    "to": [
      {
        "email": "jane@example.com",
        "name": "Jane Doe"
      }
    ],
    "replyTo": {
      "email": "support@acme.com",
      "name": "Acme Support"
    },
    "subject": "Order {{ params.ORDER_ID }} confirmed",
    "htmlContent": "<h1>Thanks, {{ params.NAME }}!</h1><p>Your order {{ params.ORDER_ID }} is confirmed.</p>",
    "textContent": "Thanks, {{ params.NAME }}! Your order {{ params.ORDER_ID }} is confirmed.",
    "params": {
      "NAME": "Jane",
      "ORDER_ID": "A-1077"
    },
    "tags": [
      "order-confirmation"
    ],
    "headers": {
      "X-Order-Id": "A-1077"
    }
  }'

Send with a template

Design the template in the portal (visual builder or HTML), then reference it by its numeric id and pass the dynamic data in params. Inside the template, use {{ params.ORDER_ID }}, loops and conditions — see the template language.

curl -X POST 'https://app.snappymails.com/v3/smtp/email' \
  -H "api-key: $SNAPPYMAILS_API_KEY" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "sender": {
      "name": "Acme Shop",
      "email": "orders@acme.com"
    },
    "to": [
      {
        "email": "jane@example.com",
        "name": "Jane Doe"
      }
    ],
    "templateId": 12,
    "params": {
      "ORDER_ID": "A-1077",
      "TOTAL": 42.5,
      "items": [
        {
          "name": "Coffee beans",
          "qty": 2
        },
        {
          "name": "Grinder",
          "qty": 1
        }
      ]
    },
    "tags": [
      "order-confirmation"
    ]
  }'

Batch sending with messageVersions

messageVersions sends up to 1000 individual messages in a single call. Each version can override these top-level fields:

ParameterTypeDescription
torequiredobject[]Recipients of this version.
cc / bccobject[]Copy recipients of this version.
replyToobjectFalls back to the top-level replyTo.
paramsobjectMerged over the top-level params (version keys win).
subjectstringFalls back to the top-level subject or the template subject.
htmlContentstringFalls back to the top-level htmlContent or the template.
textContentstringFalls back to the top-level textContent.

sender, templateId, headers, tags, attachment and scheduledAt are shared by all versions. The response contains one id per version, in the same order, plus a batchId that identifies the whole request (use it to cancel or query a scheduled batch).

curl -X POST 'https://app.snappymails.com/v3/smtp/email' \
  -H "api-key: $SNAPPYMAILS_API_KEY" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "sender": {
      "name": "Acme Shop",
      "email": "news@acme.com"
    },
    "templateId": 7,
    "params": {
      "PROMO": "AUTUMN20"
    },
    "messageVersions": [
      {
        "to": [
          {
            "email": "jane@example.com",
            "name": "Jane"
          }
        ],
        "params": {
          "NAME": "Jane"
        }
      },
      {
        "to": [
          {
            "email": "omar@example.com",
            "name": "Omar"
          }
        ],
        "params": {
          "NAME": "Omar",
          "PROMO": "VIP30"
        },
        "subject": "An extra treat for you, {{ params.NAME }}"
      }
    ]
  }'

Attachments

Pass attachment as in Brevo. Each item has either content (standard base64) and a name, or a url (name is optional and defaults to the last path segment of the URL). The files are added to every message of the request, including each messageVersions entry.

curl -X POST 'https://app.snappymails.com/v3/smtp/email' \
  -H "api-key: $SNAPPYMAILS_API_KEY" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "sender": {
      "name": "Acme Shop",
      "email": "orders@acme.com"
    },
    "to": [
      {
        "email": "jane@example.com",
        "name": "Jane Doe"
      }
    ],
    "subject": "Your invoice A-1077",
    "htmlContent": "<p>Hi {{ params.NAME }}, your invoice is attached.</p>",
    "params": {
      "NAME": "Jane"
    },
    "attachment": [
      {
        "content": "JVBERi0xLjQKJcfsj6IKNSAwIG9iago8PC9MZW5ndGggNiAwIFI+PgpzdHJlYW0K",
        "name": "invoice-A1077.pdf"
      },
      {
        "url": "https://cdn.acme.com/legal/terms.pdf",
        "name": "terms.pdf"
      }
    ]
  }'
RuleLimit
Files per requestAt most 10
Size per file5 MB (decoded)
Total size10 MB (decoded); the request body may be up to 20 MB
URL attachmentsDownloaded once when the request is made: https only, 10 s timeout, up to 5 redirects. Private and internal addresses are refused.
Blocked typesExecutable and script extensions (exe com bat cmd scr pif msi msp msc vbs vbe js jse jar ps1 psm1 wsf wsh hta cpl reg dll sys lnk inf scf app sh apk dmg iso) and any file whose content starts with an executable signature (Windows PE, ELF, Mach-O), whatever its name.

Problems return 400 invalid_parameter with a message naming the cause: bad base64, too many files, attachment too large, blocked file type or could not fetch attachment url. The SMTP relay accepts attachments as part of the MIME message, as before.

Scheduled sending

Set scheduledAt to send later, up to 72 hours ahead (further returns 400). A time less than 10 seconds ahead, or in the past, sends immediately. The quota for every recipient is reserved when the request is accepted (402 quota_exceeded as usual) and released if you cancel. Messages are released to the sending queue within a few seconds of scheduledAt; logs show them as QUEUED from then on. Scheduled messages are also listed in the portal under Transactional, Scheduled.

curl -X POST 'https://app.snappymails.com/v3/smtp/email' \
  -H "api-key: $SNAPPYMAILS_API_KEY" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "sender": {
      "name": "Acme Clinic",
      "email": "reminders@acme.com"
    },
    "to": [
      {
        "email": "jane@example.com",
        "name": "Jane Doe"
      }
    ],
    "subject": "Reminder: your appointment tomorrow at 10:00",
    "htmlContent": "<p>See you tomorrow, {{ params.NAME }}!</p>",
    "params": {
      "NAME": "Jane"
    },
    "scheduledAt": "2026-11-01T09:00:00+08:00"
  }'
202 Accepted
{
  "messageId": "<3f1c2a9e-8b7d-4d61-9a63-0c2f5e7b1d44@snappymails>"
}

Cancel a scheduled email

DELETE/v3/smtp/email/{identifier}Permission smtp:send

identifier is a messageId (with or without the <...> and @snappymails parts) or a batchId, which cancels every still-scheduled message of that request. Returns 204 No Content; 404 document_not_found when nothing matches and 400 invalid_parameter when the message is no longer scheduled (already sent or cancelled). The reserved quota is released.

curl -X DELETE 'https://app.snappymails.com/v3/smtp/email/5c6a2f0e-7d1b-4e3a-9f2c-8b1d0e4a6c11' \
  -H "api-key: $SNAPPYMAILS_API_KEY" \
  -H 'Accept: application/json'

Get the status of a scheduled email

GET/v3/smtp/emailStatus/{identifier}Permission smtp:send
curl 'https://app.snappymails.com/v3/smtp/emailStatus/3f1c2a9e-8b7d-4d61-9a63-0c2f5e7b1d44' \
  -H "api-key: $SNAPPYMAILS_API_KEY" \
  -H 'Accept: application/json'

status is queued (waiting for scheduledAt), inProgress, processed or cancelled. A batchId returns one entry per message of the batch.

By messageId
{
  "scheduledAt": "2026-11-01T09:00:00+08:00",
  "createdAt": "2026-10-31T08:12:40Z",
  "status": "queued"
}
By batchId
{
  "count": 2,
  "batches": [
    {
      "scheduledAt": "2026-11-01T09:00:00+08:00",
      "createdAt": "2026-10-31T08:12:40Z",
      "status": "queued"
    },
    {
      "scheduledAt": "2026-11-01T09:00:00+08:00",
      "createdAt": "2026-10-31T08:12:40Z",
      "status": "cancelled"
    }
  ]
}

Responses

201 Created

Single message
{
  "messageId": "<3f1c2a9e-8b7d-4d61-9a63-0c2f5e7b1d44@snappymails>"
}
With messageVersions
{
  "messageIds": [
    "<0b6f5c1e-1d2a-4f0e-9a51-6f3c2b1d7e90@snappymails>",
    "<9e4d3c2b-7a6f-4e5d-8c1b-2a3f4e5d6c7b@snappymails>"
  ],
  "batchId": "5c6a2f0e-7d1b-4e3a-9f2c-8b1d0e4a6c11"
}

202 Accepted

Returned instead of 201 when scheduledAt is more than 10 seconds ahead. The body has the same shape: messageId, or messageIds plus batchId with messageVersions.

Errors

StatusCodeWhen
400invalid_parameterInvalid JSON, missing to / subject / content, invalid email address, unverified sender domain, inactive template, template syntax error, more than 1000 messageVersions, scheduledAt more than 72 hours ahead, invalid or blocked attachment.
401unauthorizedMissing or invalid API key.
402quota_exceededMonthly quota exhausted. Each recipient (to, cc and bcc) counts as one email.
403permission_deniedKey without smtp:send.
403email_not_verifiedThe brand owner has not verified their email yet.
404document_not_foundtemplateId does not exist.
429too_many_requestsRate limit exceeded; see rate limits.

Alias: POST /v1/tx/send

POST/v1/tx/sendPermission smtp:send

/v1/tx/send is an alias of /v3/smtp/email with exactly the same body, responses and errors. Use whichever path fits your integration.

Good practices

  • Always send a textContent alongside htmlContent for better deliverability.
  • Store the returned messageId to correlate with logs and with webhook events (message-id).
  • Prefer url attachments for large shared files (terms, catalogues) to keep request bodies small.
  • Use tags consistently (e.g. password-reset, receipt) to compare performance per flow.

Migrating from Brevo

Replace https://api.brevo.com with your SnappyMails base URL and use a SnappyMails API key. Re-create your templates (or import their HTML) so the templateId values exist in your brand.