> This page is part of the [Customer.io documentation](https://docs-customerio.netlify.app). For the complete index, see [llms.txt](https://docs-customerio.netlify.app/llms.txt).
> Last updated: June 8, 2026

# Set up a transactional email

Transactional messages are email or push notifications that your audience implicitly opts-into, like a transaction receipt or a password reset request. You can send transactional messages programmatically through Customer.io.

 New to transactional messaging?

Check out our [getting started](/journeys/send/transactional/api/) section to learn more about transactional concepts and how our transactional API works.

## Before you begin[](#before-you-begin)

Before you can send transactional emails, you need to:

*   Confirm your registration email and [verify your account](/accounts/account-verification).
*   [Authenticate your sending domain](/journeys/channels/email/deliverability/authentication). If you configured a [custom SMTP server](/journeys/channels/email/deliverability/custom-smtp/use-your-smtp-server/), [contact us](mailto:win@customer.io) to manually authenticate your sending domain.
*   Get your [app API key](https://fly.customer.io/settings/api_credentials?keyType=app). This is the bearer token that you’ll use when calling the transactional API. app API keys are not the same as the Track API keys that you use to update profiles and trigger events. Customer.io does not store app API keys (only a hashed version) and you can restrict app API keys to specific IP addresses for extra security.

**We also recommend that you use a different, specialized domain or subdomain for transactional messages**. A sending domain is the domain of the “From email address” when you send emails, and you can request that we add your transactional domain to our [specialized, transactional IP pool](/journeys/send/transactional/api/#transactional-emails-have-their-own-ip-pool). Email providers like Gmail monitor the sending domain for unusual behavior and spam complaints. Separating your transactional domain from your marketing domain—something like `marketing.example.com` and `transactional.example.com`—prevents your marketing messages from affecting your critical transactional messages. [Learn more about domain reputation](/journeys/channels/email/deliverability/best-practices#ip-and-domain-reputation).

## Create a transactional email[](#create-a-transactional-email)

 Try our Postman collection!

You can use our [Postman](https://www.getpostman.com) collection and associated environment to get started with the Customer.io API. Our environment is based on our US endpoints; if you’re in our EU region, you’ll need to add `-eu` to `track_api_url` and `app_api_url` variables.

(function(e,t,n,s,o,i,a){!e\[n\]&&(e\[n\]=function(){(e\[s\]||(e\[s\]=\[\])).push(arguments)}),!t.getElementById(n+s)&&t.getElementsByTagName("head")\[0\].appendChild((a=t.createElement("script"),a.id=n+s,a.async=1,a.src=o,a))})(window,document,"\_pm","PostmanRunObject","https://run.pstmn.io/button.js")

1.  Go to the **Transactional** page and click **Send your first message** or **Create message**—depending on whether there are already transactional messages in your workspace.
    
2.  Name your message and provide a description. These fields help your team members understand what kind of message this is (like “Password Reset Instructions”). You can also use the *Name* of your message instead of the `transactional_message_id` when you send your message.
    
    [![create_transactional_step1.png](https://docs.customer.io/images/image%28552%29.png)](#d043a05d0fb6a474cbb4d49700a56dca-lightbox)
    
3.  Click **Add Content** and set up your message. You’ll [choose your editor](/journeys/channels/email/editors/choose/) or start from an existing email. When you build your email, you can personalize messages using attributes (`{{customer.<attribute>}}`) or API trigger data (`{{trigger.<data-object-property>}}`) to customize your message.
    
    [![create_transactional_step2.png](https://docs.customer.io/images/create_transactional_email_step2.png)](#def62288cac8eab93847f23eaeb3327a-lightbox)
    
4.  Configure your message settings.
    
    *   **Send to unsubscribed people?** Unsubscribed people probably still want to receive your important transactional messages.
        
        [The FTC provides guidelines about what qualifies as a transactional message](https://www.ftc.gov/business-guidance/resources/can-spam-act-compliance-guide-business), including what to do for messages that combine transactional and marketing content.
        
    *   **Enable open and link tracking?** Enable this setting if you need to know if people open or click links in your transactional messages.
        
    *   **Protect sensitive data by disabling message retention?** This setting prevents Customer.io from retaining your message content in delivery history and associated API calls. You might want to do this to conceal sensitive content, like password reset tokens.
        
    *   **Queue messages as drafts?** This setting generates a draft for every message you trigger, rather than sending them automatically. You can review these messages under the “Drafts” tab and decide whether to send or delete them.
        
    *   **Set a Trigger Name**: This is a friendly name for your message that you can use instead of the `transactional_message_id` when you send your message. It may help make your integration more human-readable if you use triggers that represent the kinds of messages you send—like `password reset` or `order confirmation`.
        
    
    [![configure your message's settings](https://docs.customer.io/images/transactional-configure-settings.png)](#719a8bbd00b09dd859842c9bef79e280-lightbox)
    
5.  To complete the setup, you need to call the API and trigger a message. If you’re not yet ready to send a message directly from your code, you can use an HTTP client like [Postman](https://getpostman.com) or send a cURL request from your terminal to test your message and complete the setup process.
    
    [![image.png](https://docs.customer.io/images/image%28559%29.png)](#b87167cb09f6793cf713276073c24154-lightbox)
    

## Identify recipients[](#identify-recipients)

When you send a message, you’ll specify who you’re sending your message to. If the person you want to send a message to doesn’t exist yet, we’ll create them for you. This lets you send transactional messages to people even when they haven’t signed up for your marketing messages.

You can identify recipients by their email address in the `to` field or with an `identifiers` block.

When `to` is a single literal email address, you don’t need to pass an `identifiers` block; we treat the address as the identifier. In every other case—`to` with multiple addresses, using liquid in the `to` field, or omitting `to` so you use your `transactional_message_id`’s templated recipient—you must include `identifiers`.

## Default `from` address[](#default-from-address)

If you omit the `from` field in your request and the template you reference doesn’t have a `from` either, Customer.io falls back to your workspace’s default sender at render time. This requires at least one verified sending domain in your workspace; if your workspace has no verified domains, the request fails with a `400`. [Authenticate a sending domain](/journeys/channels/email/deliverability/authentication/) before relying on this fallback.

## Auto-create transactional message records[](#auto-create-transactional-message-records)

If you’re working with [Our CLI tool](/ai/cli/) or otherwise building your message from code, you can include the `auto_create` parameter along with a `transactional_message_id` string the first time you send a message.

Then you’ll include the `transactional_message_id` in subsequent sends to attribute metrics to the same “message” in Customer.io.

If you don’t include a `transactional_message_id` in your request, Customer.io attributes metrics to `"transactional_message_id": 1`. And, if you send different kinds of messages with the same `transactional_message_id`, they’ll all roll up under that single ID in your metrics, making it difficult to see metrics for each type of message.

**Transactional messages created with `auto_create` don’t contain message content.** You’ll pass full message payload (the `body`, `subject`, and `from` fields) with *every* send. If you want to store message content in Customer.io, you’ll need to use our UI or work with [our command line interface (CLI)](/ai/cli/) to populate your template with message content.

## Examples and API parameters[](#examples-and-api-parameters)

Below is a basic transactional email payload, followed by the full list of parameters. Your payload changes based on whether you reference a `transactional_message_id` (a template) or not. See our [REST API](/integrations/api/app/#operation/sendEmail) documentation for the full reference, including code samples in cURL and several languages.

```json
{
  "transactional_message_id": 44,
  "to": "cool.person@example.com",
  "subject": "Did you really login from a new location?",
  "identifiers": {
    "email": "cool.person@example.com"
  },
  "message_data": {
    "password_reset_token": "abcde-12345-fghij-d888",
    "account_id": "123dj"
  },
  "send_to_unsubscribed": true,
  "tracked": true,
  "disable_css_preprocessing": true
}
```

*   body string
    
    The HTML body of your message. If you provide `transactional_message_id`, this overrides the template’s body. If you send an AMP-enabled email (with `body_amp`) and the recipient’s email client doesn’t support AMP, this is the fallback email.
    
*   body\_plain string
    
    The plaintext body of your message. If you provide `transactional_message_id`, this overrides the template’s plaintext body.
    
*   from string
    
    The address that your email is from. This address must be verified by Customer.io—see [Authenticate your sending domain](/journeys/channels/email/deliverability/authentication/) for how to verify a domain. If you provide `transactional_message_id`, this overrides the template’s from address. You can include a display/friendly name in your from address, but we recommend that you use quotation marks around the friendly name to avoid potential issues with special characters, e.g. `\"Person\" <person@example.com>`.
    
    If you omit `from` (and the template has no `from` either, or you didn’t include a template), Customer.io falls back to your workspace’s default sender at render time. This requires at least one verified sending domain in your workspace.
    
*   language string
    
    Overrides language preferences for the person you want to send your transactional message to. Use one of our [supported two- or four-letter language codes](/journeys/channels/localization/getting-started/#supported-languages).
    
*   subject string
    
    The subject line for your message. If you provide `transactional_message_id`, this overrides the template’s subject.
    
*   transactional\_message\_id 
    
    The transactional message template you want to use. You can call the template by its numerical ID or by the *Trigger Name* that you assigned to the template in the UI (case insensitive).
    
    integer
    
    The ID of the transactional message you want to send.
    
    string
    
    The name of trigger for the transactional message you want to send. You set the trigger name in the **Configure Settings** step in the UI when setting up your message. This is case insensitive.
    

## Update the content of your email[](#update-the-content-of-your-email)

You can update the contents of your message through our user interface or API.

We’ve exposed [an API endpoint](/integrations/api/app/#operation/updateTransactional) so you can manage your message contents programmatically. This request takes a `body`, which represents the complete HTML content of your message.

You’ll reference the message you want to update by `transactional_id` and `content_id`. You can find both in the URL when you look at the content of a message in the format `https://fly.customer.io/journeys/env/last/composer/transactional/:transactional_message_id/templates/:content_id`. For example, if I look at a transactional message with this URL: `https://fly.customer.io/journeys/env/last/composer/transactional/3/templates/139`, then the `transactional_id` is 3 and the `content_id` is 139.

```shell
curl --request PUT \
  --url https://api.customer.io/v1/transactional/{transactional_id}/content/{content_id} \
  --header 'Authorization: Bearer REPLACE_BEARER_TOKEN' \
  --header 'content-type: application/json' \
  --data '{"body":"string"}'
```

 Did you just update a snippet?

If you just updated a [snippet](/journeys/liquid/snippets/) used in your messages, make sure you wait a couple of minutes before activating your workflow to ensure all updates appear in your delivered messages.