Skip to main content

Sending

to and subject are required, plus either templateId or htmlContent. If you send both, htmlContent is used.
  • Liquid. The subject, preheader and body are rendered with Liquid: {{ data.x }} (also {{ x }}), {{ contact.first_name }}, {{ customField.plan }}, and loops. Every value is HTML-escaped. data is limited to 256 KB and 32 levels of nesting.
  • Idempotency. A repeated externalId returns the original message with deduplicated: true, and nothing is sent.
  • Contact. Pass contact to create or update the recipient first. If the plan’s contact limit blocks that, the email is still sent. The recipient’s language, or the language field, picks a template translation.
  • Send policy. sendPolicy (a key or ID from Settings → Send policies) decides the delivery route and default sender. If you leave it out, the workspace’s Transactional policy applies. That policy delivers to people who unsubscribed from marketing, but never to bounced, complained or erased addresses (422). An unknown policy answers 400 SEND_POLICY_NOT_FOUND.
  • Missing variables. Variables the template marks as required always block a send, and strict: true blocks on any unresolved variable. Either way the answer is 422 MISSING_VARIABLES with missing[]. Unresolved variables that don’t block are returned in warnings.
  • Attachments. Up to 20, base64-encoded, at most 3 MB each and 10 MB in total. Also available: cc/bcc (up to 10 each), replyTo, and fromEmail, which must be on a domain the workspace owns.

Checking before you send

POST /v1/transactional/validate renders exactly as a send would, but sends and stores nothing. It returns the rendered subject, every variable path the template reads, the ones this payload leaves unresolved, and valid: false when a send would be refused. It is useful in CI.

Delivery status

A message moves from queued to sent. It then becomes delivered, bounced or complained as the provider reports back, or it ends as failed. Look it up by our ID or by your key:
GET /v1/transactional lists messages newest first. You can filter by to, status and externalId, and page through the results with before / nextBefore.