---
title: "Send a batch of emails"
method: POST
path: "/emails/batch"
tags: ["Email"]
---

# Send a batch of emails

`POST /emails/batch`

## Request

- Base URL: `https://api.hypamoon.com/api/v1`
- URL: `https://api.hypamoon.com/api/v1/emails/batch`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://app.hypamoon.com/oauth/authorize`, token URL `https://api.hypamoon.com/oauth/token`, scopes: `messages:write`)

Sends 1 to 100 separate emails in one call, like Resend's batch: each email has its own to (and its own text), so everyone sees only their own email, for example a personal invitation per person. Each email follows the rules of POST /emails, except that a batch has no attachments (send those one by one). All or nothing: every email is checked first, and if one is refused, none is sent and the error says which (index). Every email and every recipient counts toward the limits and the bundle; the whole batch is one call for the per-minute limit. An Idempotency-Key (header or idempotency_key) returns the same emails instead of sending them again. With a test key nothing is sent (status simulated).

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `emails` object[], required: 1 to 100 separate emails, each with its own to (and its own text, for personal invitations): everyone sees only their own email. No attachments in a batch; send those one by one.
    - `from` string, required: The sender: an address on a verified sending domain of this project, optionally with a name, like "Acme Plumbing <hello@acme.com>".
    - `to` union, required: One address or a list (at most 50 recipients in to, cc and bcc together). Everyone in to and cc sees the others, as in any email; use bcc, or a batch with one email per person, when they must not. Every address must appear in this project (a form submission, a document, a chat conversation, a user of one of your apps) or be confirmed by the owner.
      - string
      - string[]
    - `cc` string[]: Optional. Copy recipients; they see the to and cc addresses. The same rules as to.
    - `bcc` string[]: Optional. Hidden recipients; nobody sees them. The same rules as to.
    - `subject` string, required: The subject line.
    - `text` string: The plain text body. Send text, html or both.
    - `html` string: The HTML body. No scripts, no forms; links must show where they go.
    - `reply_to` string: Optional. Where replies go, like the user's own mailbox.
    - `category` 'transactional' | 'other': transactional: a reply to something the recipients did (an order, a booking, a form they sent). other (default): anything else; it gets an unsubscribe link with the project's postal address (needs a business address in the company details) and a List-Unsubscribe header, is not sent to people who unsubscribed, and goes to exactly one recipient (use a batch for more people).
    - `in_reply_to` string, uuid: The id of the form submission (a document from a public form) or the chat conversation this email answers. Required when the recipient appears in the project only through a form or a chat; the address must be in that submission or conversation.
    - `check_recipients` boolean: Optional, default true: before the first email to an address, check that its domain exists and accepts email, and refuse likely typos (like gmial.com) and, for category other, throwaway addresses. false skips those checks; an address that bounced before is always refused.
  - `idempotency_key` string: Optional. The same key returns the same emails instead of sending them again (also as the Idempotency-Key header).

## Response `200`

The same Idempotency-Key as before: the original emails, and nothing is sent again.

- object
  - `data` object[], required: The emails in the order of the request.
    - `id` string, uuid, required: The id of the email. sendEmail and sendEmailBatch in an app return it; getEmail(id) reads it back.
    - `app_id` string, uuid, nullable, required: The app that sent it with env.HYPAMOON.sendEmail; null when it was sent with the API, by an agent or from the portal.
    - `from` string, required
    - `to` string, required: The first address in to. All recipients are in recipients.
    - `recipients` object, required: Everyone this email went to. After 90 days the addresses are masked, like s***@example.org.
      - `to` string[], required
      - `cc` string[], required
      - `bcc` string[], required
    - `recipient_count` integer, required: to, cc and bcc together; each counts toward the limits and the bundle.
    - `attachments` object[], required: Name, type and size of each attachment. The content is never stored.
      - `filename` string, required
      - `content_type` string, required
      - `size` integer, required: In bytes.
    - `reply_to` string, nullable, required
    - `subject` string, required
    - `category` 'transactional' | 'other', required
    - `status` 'queued' | 'sent' | 'delivered' | 'bounced' | 'complained' | 'failed' | 'simulated', required: queued, sent (accepted for delivery), delivered, bounced, complained (marked as spam), failed, or simulated (test mode).
    - `recipient_source` 'confirmed' | 'member' | 'form' | 'document' | 'conversation' | 'app_user' | 'buyer' | 'pass_holder' | 'null', nullable, required: Where the recipient appears in the project.
    - `test` boolean, required
    - `error` string, nullable, required
    - `reason` string, nullable, required: Why the email bounced, was marked as spam, is delayed or was not sent, in plain words. null otherwise.
    - `recipient_status` object[], required: The status per recipient. With several recipients one can bounce while the others are delivered.
      - `email` string, required: The address. Masked after 90 days, like s***@example.org.
      - `field` 'to' | 'cc' | 'bcc', required
      - `status` 'queued' | 'sent' | 'delivered' | 'bounced' | 'complained' | 'failed' | 'simulated', required
      - `reason` string, nullable, required: Why it bounced, was marked as spam, is delayed or was not sent.
      - `updated_at` string, nullable, required: When this recipient's status last changed.
    - `timeline` object[], required: Every step, oldest first: queued, then sent (or simulated, or failed), then what the recipient's mail server reported.
      - `status` 'queued' | 'sent' | 'delivered' | 'delayed' | 'bounced' | 'complained' | 'failed' | 'simulated', required: queued, sent, delivered, delayed (the recipient's server has not accepted it yet; retried automatically), bounced, complained, failed or simulated.
      - `at` string, required: When it happened (ISO 8601).
      - `recipient` string, nullable, required: The recipient this step is about; null for every recipient.
      - `reason` string, nullable, required: A short reason in plain words, for delayed, bounced, complained and failed.
    - `created_at` string, required
    - `sent_at` string, nullable, required
    - `delivered_at` string, nullable, required
    - `bounced_at` string, nullable, required
    - `complained_at` string, nullable, required
    - `sender` 'hypamoon_fallback': Only when the project has no verified sending domain of its own: the email went from Hypamoon's address ("<app or project> via Hypamoon"), in a neutral layout without the project's images or design. Tell the user so.
    - `next_step` string: With sender hypamoon_fallback: how to send from the project's own address with its own design.

## Other responses

- `201`: Accepted for delivery (or simulated with a test key), in the order of the request.
- `400`: Bad Request: invalid_json, idempotency_key_invalid.
- `401`: Unauthorized: unauthorized, missing_api_key, invalid_api_key, api_key_revoked, project_deleted.
- `402`: Payment Required: insufficient_balance.
- `403`: Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, mail_not_enabled, email_paused, recipient_not_allowed.
- `409`: Conflict: idempotency_key_reused, idempotency_request_in_progress, email_domain_not_verified.
- `422`: Unprocessable Content: validation_failed, email_address_invalid, company_details_missing, email_blocked, attachment_not_allowed.
- `429`: Too Many Requests: rate_limited, email_limit_reached.
- `500`: Internal Server Error: internal_error.
- `502`: Bad Gateway: upstream_error.
- `503`: Service Unavailable: service_unavailable.

---

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