---
title: "Send an email"
method: POST
path: "/api/v1/emails"
tags: ["Emails"]
---

# Send an email

`POST /api/v1/emails`

## Request

- Base URL: `https://mailcheer.com`
- URL: `https://mailcheer.com/api/v1/emails`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-Api-Key`

Permission required: `emails:send`.

Answers **202**: Amazon has accepted the message, it is not delivered yet. Delivery is confirmed a few seconds later with `GET /api/v1/emails/{id}`.

Use the `Idempotency-Key` header: replaying a successful call returns the same response without a second send, for 24 hours. A refusal is not kept: once its cause is lifted, the same call with the same key goes out.

An address that unsubscribed from the workspace receives an individual email; with `unsubscribe_url`, the send is a newsletter or prospecting and is refused to it (`422 suppressed_recipient`). A bounce, a complaint or a manual suppression refuses both.

With a test key (`mch_test_…`), the same checks and the same response — but nothing goes out and the quota is not touched; events are simulated. See Test mode.

## Headers

- `Idempotency-Key` string

## Request body

- EmailRequest: The body of `POST /api/v1/emails`. `from`, `to` and `subject` are required, and **at least one of `html` or `text`** — an empty body is refused with a 422. Attachments go in `attachments`, in Resend's format. Unknown keys are silently ignored: a body written for Resend passes through as is.
  - `from` string, required: **Required.** An address on a domain verified in the workspace — otherwise 422 `unverified_from_domain`, which hands you the list of domains you can use. `GET /api/v1/me` gives it too, before you try. Both forms are accepted: the bare address, or “Name <address@domain.com>” so the name shows in the inbox.
  - `to` string, required: **Required.** One recipient, or an array of 1 to 50 recipients. At most 50 addresses in all, `to`, `cc` and `bcc` together — our sending provider's limit per message; beyond, `422 validation_error`. Beyond 50, split into several calls — or go through a campaign. Each entry accepts the form “Name <address@domain.com>” as well as the bare address, and is 3 to 320 characters long. ⚠️ **Recipients see one another**: they go out in the same message, not as blind copies. For each to receive their own, make one call per person.
  - `subject` string, required: **Required.** The message's subject. The 998-character ceiling is the email standard's; in practice an inbox shows 40 to 60.
  - `html` string: The body in HTML. `html` or `text`: at least one of the two, both if you can — a message with no text version is marked down by filters.
  - `text` string: The body in plain text, for clients that do not display HTML.
  - `cc` string: Visible copy. One address or an array of 1 to 50. Recipients in copy see and are seen. They count against the quota and go through the same checks as `to` — suppression list included.
  - `bcc` string: Blind copy. One address or an array of 1 to 50. It appears in no header of the received message, including when the send carries attachments — it stays in the envelope. Same quota and same checks as `to`.
  - `replyTo` string: The address recipients will reply to, when it differs from `from`. One, or an array of 1 to 10. Same form as `to`: the bare address or “Name <address>”.
  - `reply_to` string: Alias of `replyTo`, for clients coming from Resend. The address recipients will reply to, when it differs from `from`. One, or an array of 1 to 10. Same form as `to`: the bare address or “Name <address>”. Fill in only one of the two.
  - `headers` object: Free-form headers, as name → value pairs. The ones that carry the message's identity (From, To, Reply-To, List-Unsubscribe…) are ignored: they are ours to answer for.
  - `tags` object: Free-form tags for your own tracking; they come back in delivery notifications and on `GET /api/v1/emails/{id}`. Two ways to write them: an object of key → value, or an array of `{name, value}` as with Resend (10 entries at most in that second form). Names and values are **rewritten, not refused**: any character outside `A-Z a-z 0-9 _ -` becomes `_`, and beyond 256 characters the value is cut. An accent or a space therefore passes without error, but not unchanged.
  - `unsubscribe_url` string, uri: The one-click unsubscribe address. Mailcheer then sets `List-Unsubscribe` AND `List-Unsubscribe-Post` — the two always go together, and without the second Gmail shows no button. Gmail and Yahoo have expected this header since February 2024 on any send to people who did not write to you first; without it, the only way out you offer is the Spam button, and it is your sending domain's reputation that pays. Must be https. The header itself is still rejected inside `headers`: Mailcheer writes it, you only provide the target. It also says what kind of send this is: with it, a newsletter or prospecting, refused to an address that unsubscribed; without it, an individual email, which an unsubscribe does not stop.
  - `attachments` object[]: Attachments, in Resend's format: `filename` + `content` in base64. Twenty at most. **The limit is Amazon SES's: 40 MB per message once encoded**, which is roughly 30 MB of actual files (base64 adds a third). Beyond that the call is refused with a 422 naming the file, its size and the size reached — no message goes out without its attachment. Rejected, and always out loud: extensions inbox providers reject (`.exe`, `.bat`, `.js`, `.vbs`…), a `content` that is not valid base64, and Resend's `path` field — our server will not fetch a file from an address you choose; encode it. Without an attachment, nothing changes in the way the message goes out.
    - `filename` string, required: The file name as it will appear in the inbox. Line breaks and path separators are stripped from it.
    - `content` string, required: The file, encoded in **base64**. That is the only accepted form.
    - `content_type` string: The MIME type. Optional: inferred from the extension when missing (`.pdf` → `application/pdf`), falling back to `application/octet-stream`.
    - `contentType` string: Alias of `content_type`.
    - `content_id` string: Makes the attachment an **inline image**: reference it in the HTML with `cid:` (`<img src="cid:logo">`). It goes out with a `Content-ID` header in a `multipart/related` part and shows in place instead of as a download. Letters, digits, `.`, `_`, `-`, `@`, no spaces; unique within the message.
    - `contentId` string: Alias of `content_id`.
  - `track_opens` boolean: Insert an open-tracking pixel (HTML body only). Omitted, your workspace's “Track opens” setting decides (the Workspace screen of the app); sent, it overrides that setting for this email. In France, the CNIL's recommendation of 14 April 2026 makes measuring opens with a pixel, to track campaign performance, subject to the recipient's prior consent.
  - `track_clicks` boolean: Track clicks: the http(s) links of the <a> tags of the HTML go through your tracking domain. Left untouched: mailto:, tel:, anchors, unsubscribe_url and links carrying data-no-track. Without this field, the workspace's “Track clicks” setting decides — on by default, for every email, individual ones included. `false` leaves a message untouched. Login, verification and reset links (recognized by their address or a token) always stay untouched.
  - `verify` boolean: Verify recipients before sending (same checks as `POST /api/v1/verify`). An `invalid` recipient refuses the entire call with `422 undeliverable_recipient`; `risky` and `unknown` addresses go out. Default false.

## Response `202`

Accepted by our sending provider.

- Email
  - `id` string: The identifier to pass back to `GET /api/v1/emails/{id}` to learn about delivery.
  - `object` 'email'
  - `from` string
  - `to` string[]: Always an array in the response, even if you passed a single address.
  - `subject` string
  - `created_at` string, date-time

## Other responses

- `401`: Key missing, unknown or revoked.
- `402`: The send is refused before anything leaves; nothing was sent or queued. Two causes, told apart by `error.code`: - `quota_exceeded` — the plan's monthly quota does not cover this send. `details` gives the numbers: the plan, its quota, what was sent this month, what is waiting in the queue, what remains, what this call asked for, and when the counter resets. - `free_plan_domain_used` — the workspace is on the free plan, and the sending domain (or another subdomain of the same registered domain) has already used the free plan this month in another workspace. The free plan applies to a sending domain, not to an account. `details`: `domain`, `root_domain`, `period`, `resets_at`. Upgrading to a paid plan lifts the refusal immediately. - `key_quota_exceeded` — the workspace still has room, but THIS key reached its own monthly limit (Settings → API). Another key of the workspace can still send. `details`: `key_id`, `key_name`, `limit`, `used`, `remaining`, `requested`, `resets_at`.
- `403`: The key does not have the requested permission, or the workspace cannot send: `sending_blocked` (suspended, blocked or banned — nothing goes out, through any channel), `commitment_required` (the anti-spam commitment is not accepted), `reputation_blocked` (too many bounces or complaints).
- `409`: Incompatible state, idempotency key reused with a different body, or an identical request still in progress.
- `422`: Malformed field, unverified domain, or recipient on the suppression list.
- `423`: `sending_paused` — the workspace is paused for a safety review. Nothing is wrong with the call: send it again after the decision, unchanged. `Retry-After` gives an interval to try again (300 seconds): while the review lasts, the call returns `423` again, and writes nothing.
- `429`: More than 600 requests per minute for this key (`rate_limit_exceeded` — a batch of N subscribers counts as N), or a new workspace reached its daily limit (`daily_quota_exceeded`: 1,000 emails per rolling 24 hours until it is verified). `Retry-After` always says how many seconds to wait.
- `502`: Our sending provider refused the message.

---

[API](https://skmtc.dev/mailcheer/apis/mailcheer-api.md) · [All operations](https://skmtc.dev/mailcheer/apis/mailcheer-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mailcheer/apis/mailcheer-api/revisions/ba64b7ef2170?raw)
