EmailElement Docs

Transactional Email API Reference

Endpoint

POST https://transactional.emailelement.com/api/Email

Authentication

Include your API key in the request header:

x-api-key: YOUR_API_KEY

The API key must have Transactional Email API permission set to Full Access. A key without it gets a 403 with an empty body.

Request Body

Required Fields

Field Type Description
to object The recipient
to.email string Recipient address. Must be a valid email address
to.name string Recipient name. Optional
subject string Email subject line
transactionRelayId integer The Transactional Relay to send through

Content (one required)

At least one of these three must be supplied. Leaving all three empty is rejected.

Field Type Description
htmlBody string HTML content. Must be at or under 1 MB
textBody string Plain text content. Must be at or under 1 MB
templateId integer The Email Content template to send

Optional Fields

Field Type Description
from object Overrides the sender
from.name string Sender name
from.localPart string The part before the @. 64 characters or fewer, and cannot contain @, a space, a leading or trailing dot, or two dots in a row
fromName string Kept for older integrations. Use from.name instead
triggerId integer A trigger to fire for this email. The trigger event must be of type Transactional Email Api
substitutions object Key-value pairs replaced in the content. See Substitutions
delaySeconds integer Hold the email before sending. 0 to 43199, so at most just under 12 hours
attachments array File attachments. See Attachments
trackId1 to trackId5 string Your own identifiers, returned in webhook event payloads. 255 characters or fewer each

replyTo, cc and bcc are not supported.

Example: Basic Request

{
  "to": {
    "name": "Jane Smith",
    "email": "jane@example.com"
  },
  "subject": "Your Order Confirmation",
  "htmlBody": "<h1>Hello {firstName}!</h1><p>Your order #{orderId} is confirmed.</p>",
  "transactionRelayId": 2,
  "substitutions": {
    "firstName": "Jane",
    "orderId": "12345"
  }
}

Example: With Attachments

{
  "to": {
    "name": "Jane Smith",
    "email": "jane@example.com"
  },
  "subject": "Your Invoice",
  "htmlBody": "<p>Please find your invoice attached.</p>",
  "transactionRelayId": 2,
  "attachments": [
    {
      "filename": "invoice.pdf",
      "contentType": "application/pdf",
      "content": "base64-encoded-file-content-here"
    }
  ]
}

Attachments

Each attachment object requires:

Field Type Description
filename string File name with extension (e.g., invoice.pdf)
contentType string MIME type (e.g., application/pdf, image/png, text/csv)
content string Base64-encoded file content

The content values of all attachments on one request must add up to 20 MB or less. Content that is not valid Base64 is rejected.

Responses

Branch on the HTTP status code. None of the responses carry a success flag, so there is nothing else to branch on. A 403 has an empty body. Every other failure is a problem document sent as application/problem+json, but the two kinds carry the reason in different places: field-level rejections put it in an errors object keyed by field path and have no detail, while the rest put it in detail. Read both.

200 - Email accepted

The email passed validation and was queued. messageId is the run id for this email. Keep it, as it is the value that ties webhook events back to this request. errorCode is always 0 and message is always "Ok" on a 200, so neither is a useful branch.

{
  "to": "jane.doe@example.com",
  "submittedAt": "2026-08-13T17:04:22.1874316Z",
  "messageId": "84512300071",
  "errorCode": 0,
  "message": "Ok"
}

A 200 means the email was validated and queued, not that it was delivered. Suppression, bounce handling and the sending relay itself run afterwards and can still stop the send, so use the webhook events for that messageId to confirm delivery.

400 - Request failed validation

One or more fields were rejected before the email was queued. errors is keyed by the field path, and a single field can carry more than one message. This shape covers every rule above: a missing to, to.email or subject, a malformed to.email, a missing transactionRelayId, a body or attachment over its size limit, a delaySeconds outside 0 to 43199, an attachment that is not Base64, a localPart with prohibited characters, or a trackId over 255 characters.

{
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "To.Email": [
      "The ToEmail field must be a valid email address."
    ],
    "TransactionRelayId": [
      "The TransactionRelayId field is required."
    ]
  }
}

400 - No content supplied

None of htmlBody, textBody or templateId was supplied. The same message is repeated under all three field paths, because any one of them satisfies the rule.

{
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "TemplateId": [
      "At least one of HtmlBody, TextBody or TemplateId fields is required."
    ],
    "HtmlBody": [
      "At least one of HtmlBody, TextBody or TemplateId fields is required."
    ],
    "TextBody": [
      "At least one of HtmlBody, TextBody or TemplateId fields is required."
    ]
  }
}

400 - Referenced record missing or inactive

The request was well formed, but the transactionRelayId, templateId or triggerId it points at could not be used. detail carries the reason and is one of: "Transactional Relay does not exist for account.", "Transactional Relay is not active.", "Email Content Template does not exist for account.", "Email Content Template is not active.", "Trigger does not exist for account.", "Trigger is not active.", or "Trigger event is not of type Transactional Email Api."

A record belonging to another account is reported as not existing, so the endpoint cannot be used to probe for valid ids.

{
  "type": "https://tools.ietf.org/html/rfc7807",
  "title": "Bad Request",
  "status": 400,
  "detail": "Transactional Relay is not active.",
  "instance": "/api/Email",
  "errors": {
    "message": [
      "Transactional Relay is not active."
    ]
  },
  "traceId": "0HNC1P8QK4T3G:00000001"
}

401 - Missing or invalid API key

The x-api-key header was absent or not recognised. A missing header returns the same shape with "ApiKey was not provided".

{
  "type": "https://tools.ietf.org/html/rfc7235",
  "title": "Unauthorized",
  "status": 401,
  "detail": "Invalid ApiKey",
  "instance": "/api/Email",
  "errors": {
    "message": [
      "Invalid ApiKey"
    ]
  },
  "traceId": "0HNC1P8QK4T3G:00000001"
}

403 - Insufficient permissions

The API key is valid but does not carry Transactional Email API: Full Access. The response body is empty.

500 - Unexpected error

An unhandled error occurred. detail carries the underlying error text and varies, so do not match on it. Safe to retry with backoff, but retry with a fresh request rather than assuming the original was not queued.

{
  "type": "https://tools.ietf.org/html/rfc7807",
  "title": "Internal Server Error",
  "status": 500,
  "detail": "<error text>",
  "instance": "/api/Email",
  "errors": {
    "message": [
      "<error text>"
    ]
  },
  "traceId": "0HNC1P8QK4T3G:00000001"
}

Substitutions

substitutions is a set of key and value pairs that fill in parts of the subject, the HTML body or the text body at send time. Leave a key out and, when the recipient is on a Contact List, the value comes from their stored data instead.

How a value goes in depends on what the value is.

Plain values are matched on the key exactly as written

A key made only of letters, digits and underscores is matched as that bare word, and it is deliberately not matched inside braces. A key holding anything else is matched exactly as you wrote it.

So to fill in {email} in the body, the key has to be {email}, braces and all:

{
  "to": { "email": "jane.doe@example.com" },
  "subject": "Your order",
  "transactionRelayId": 42,
  "substitutions": {
    "{email}": "jane@example.com"
  },
  "textBody": "Hi {email}"
}

Leave the substitution out and {email} is filled from the contact's stored data instead:

{
  "to": { "email": "jane.doe@example.com" },
  "subject": "Your order",
  "transactionRelayId": 42,
  "textBody": "Hi {email}"
}

A custom name works the same way. With "{pet}": "dogs" supplied, Hi {email}, I heard you like {pet} reads "I heard you like dogs". Without it, {pet} is read from the contact's stored data.

Object and array values are only reachable through handlebars

A value that is an object or an array is never pasted in as text. It is handed to the handlebars pass, so reach into it with double braces and dot notation. Single braces will not find it.

Deep object replacement

{
  "htmlBody": "<p>Hello {{user.profile.firstName}} {Last Name}</p>",
  "transactionRelayId": 2,
  "substitutions": {
    "user": { "profile": { "firstName": "Ben" }, "active": false },
    "supportPhone": "1-800-555-5555"
  }
}

For a contact whose stored Last Name is "Parker", that renders as Hello Ben Parker. Note the two styles side by side: {{user.profile.firstName}} comes from the substitutions object, {Last Name} from the contact.

Each loop

{
  "htmlBody": "<ol>{{#each user.orderHistory}}<li>You ordered {{this.item}} on {{this.date}}</li>{{/each}}</ol>",
  "transactionRelayId": 2,
  "substitutions": {
    "user": {
      "orderHistory": [
        { "date": "2/1/2018", "item": "shoes" },
        { "date": "1/4/2017", "item": "hat" }
      ]
    }
  }
}

Renders one list item per order:

You ordered shoes on 2/1/2018
You ordered hat on 1/4/2017

If, else if, else

{
  "htmlBody": "{{#if user.profile.male}}<p>Dear Sir</p>{{else if user.profile.female}}<p>Dear Madame</p>{{else}}<p>Dear Customer</p>{{/if}}",
  "transactionRelayId": 2,
  "substitutions": {
    "user": { "profile": { "male": false, "female": false } }
  }
}

Both flags are false, so this renders Dear Customer.

Unless

{
  "htmlBody": "{{#unless user.active}}<p>Your account is suspended, please call {{supportPhone}}</p>{{/unless}}",
  "transactionRelayId": 2,
  "substitutions": {
    "user": { "active": false },
    "supportPhone": "1-800-555-5555"
  }
}

each, if, unless and deep object replacement are only available on the Transactional Email API. They are not part of Email Content replacement tags.

Stored Contact Data

If the recipient email is on a Contact List, their stored field values fill any tag the request did not supply. A substitution sent in the request wins over the stored value for the same name.

Something went wrong. Reloading the page usually fixes it. Reload ×

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please retry or reload the page.