---
title: "Send a test email"
method: POST
path: "/v1/design_studio/emails/{id}/test_send"
tags: ["Design Studio emails"]
---

# Send a test email

`POST /v1/design_studio/emails/{id}/test_send`

Sends a test email to a real inbox through your sending domain using the content saved to Design Studio. The test includes the tracking pixel and link parameters a real send would add, so you can see your email as it would appear in an actual email client. To get the rendered HTML back in the API response instead, use [Preview an email](/integrations/api/app/tag/design-studio-emails/previewEmail/).

* **If you already linked the email to a workflow**, then keep in mind that what's *saved* to Design Studio may differ from what's *published* to the linked workflow. Make sure you publish your changes when you're ready to push them to your linked workflow.

* **If your email has translations**, you have to send this request separately for each language variant. Pass the ID of the specific translation you want to test, which you can retrieve from [List email translations](/integrations/api/app/tag/design-studio-emails/listEmailTranslations/).

* **If the email contains liquid variables**, you can provide sample data in the request body to check how the liquid renders. Without sample data, the send still succeeds, but each variable renders as an empty string and the response includes a warning. A variable with a fallback filter like `default` renders its fallback instead. A liquid syntax error, like a missing close tag, always fails the request.

Your account has a set number of test emails you can send per day. This endpoint counts towards that quota. Learn more in [Plan features](/accounts/billing/plan-features/#general-features).

## Path parameters

- `id` string, uuid, required

## Request body

- object
  - `to` string[], required — The email addresses you want to send the test to. You can send to up to 25 addresses per request; up to three addresses on a trial account; or a single address (the account owner's address or your workspace's delivery address) if your account isn't verified yet.
  - `customer_id` string, nullable — The person whose profile attributes fill in `customer.*` liquid variables. Pass the person's `cio_id` or the `id` that identifies them in your workspace. If nobody matches, the request fails. When you omit this field and don't pass `customer` values, the email renders with empty `customer.*` values and the response includes a warning.
  - `customer` object, nullable — Sample profile attributes for `customer.*` liquid variables, as a flat object of values. Encode a nested value as a JSON string. You can pass this alongside `customer_id`: Customer.io uses the profile's attributes, and a value here overrides the profile's value for the same key.
  - `event` object, nullable — Sample event data for `event.*` liquid variables. The `event` key references trigger data for event-triggered automations.
  - `trigger` object, nullable — Sample trigger data for `trigger.*` liquid variables. The `trigger` key references trigger data for these specific workflows: API-triggered broadcasts or transactional messages.
  - `lax` boolean — Set to `true` to render a liquid variable your sample data doesn't cover as an empty string rather than failing the request. Only applies when you pass `customer_id` or `customer`; without sample data, the email renders this way anyway and the response includes a warning.
  - `prepend_test` boolean — Set to `true` to add `[TEST]` to the start of the subject line.
  - `tracked` boolean — Set to `true` to add a tracking pixel or `false` to leave it out. If you don't pass this field, Customer.io uses the tracking setting of the workflow message the email is linked to, and adds the pixel when the email isn't linked to a workflow message. Customer.io never adds the pixel for unverified accounts.

## Response `200`

Test accepted for delivery

- object
  - `node_id` string, uuid — The email node that was tested.
  - `node_type` 'EMAIL' — The response represents an email.
  - `language` string — The language code if the message is a language variant. Omitted for the default-language node.
  - `accepted` boolean — Always `true` in a 200 response. Anything that stops the send returns a 4xx instead. Acceptance isn't proof of delivery.
  - `to` string[] — The recipients the test was sent to.
  - `subject` string — The rendered subject line, including the `[TEST]` prefix if you set `prepend_test`.
  - `from` string — The rendered `From` header the test was sent with.
  - `warnings` string[] — The ways this test differs from a real send. Customer.io delivered the email anyway, so read these before you trust what landed in the inbox. You might see that no profile data was available, that context a test send can't supply rendered as empty strings (like `campaign.*` or `message.*` on an email that isn't linked to a workflow), that Customer.io ignored a routing key like `recipient` or `from_address` in your `event` or `trigger` data, or that a recipient field in the template failed to render. That last one doesn't change who got the test: it goes only to the addresses in `to`. Omitted when there's nothing to report.

## Other responses

- `400` — The ID isn't a valid email node, your account isn't verified yet and `to` holds an address it can't send to, or you've reached your [daily test-send limit](/accounts/billing/plan-features/#general-features).
- `401` — Unauthorized - missing or invalid API key
- `404` — The email node doesn't exist.
- `422` — A request field is invalid or the email's liquid didn't render. Each error's `source.pointer` names what failed, as `/data/attributes/<name>`. That name isn't always something you sent: `node_id` points at the email itself, and a liquid error can name `url_params`, `layout`, or `snippets`.
- `429` — Rate limited. Every request to this endpoint draws from two buckets: a per-workspace bucket (burst of 20, refilling one every 9 seconds), the same limit the dashboard applies to its own test sends, and a per-IP bucket (burst of 30, refilling one every 10 seconds) that catches one IP address spread across many accounts. Hitting either limit returns this response. The response carries a `Retry-After` header.

## Changes

- **2026-09-22** `00bda3284527` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/customer/apis/customer-io-journeys-api-reference/changes/v1/design_studio/emails/:id/test_send/post.md)

---

[API](https://skmtc.dev/customer/apis/customer-io-journeys-api-reference.md) · [All operations](https://skmtc.dev/customer/apis/customer-io-journeys-api-reference/llms.txt) · [OpenAPI document](https://skmtc.dev/customer/apis/customer-io-journeys-api-reference/revisions/d9edec5f938c?raw)
