Skip to content
MiniMailer
Docs
Toggle sidebar
Quick links
Documentation
Knowledge Base
API Reference
Changelog
No results found

Sending

This page covers transactional email — one message to a recipient, plus optional cc/bcc copies, triggered by your code: receipts, resets, notifications. To send one message to an audience, see Campaigns. Both send from a verified domain and feed the same event tracking and webhooks.

Transactional email

POST /outbound-emails queues a single email for delivery:

Attribute Required Notes
domain yes A verified domain you own
from yes Sender address on that domain
to yes One recipient address
cc no Carbon-copy recipients — one address or an array of up to 10. See Cc and bcc
bcc no Blind-carbon-copy recipients — one address or an array of up to 10. See Cc and bcc
reply_to no Where replies land — one address or an array of up to 10, on your sending domain's family. See Reply-to
subject yes Up to 998 characters
html yes HTML body
text no Plain-text alternative — include one; it helps spam filtering and accessibility
idempotency_key no Deduplicates retries (see below)
agent_id no Attributes the send to an AI agent for per-agent reputation (up to 128 chars, [A-Za-z0-9_-.])
attachments no Files to attach — see Attachments and inline images

The response is a 201 with the email's identifier and current status. Track it afterwards with GET /outbound-emails/{outboundEmail} or aggregate numbers from GET /outbound-emails/{outboundEmail}/stats.

Cc and bcc

Add cc and/or bcc to a transactional send — each accepts a single address or an array of up to 10 addresses:

  • cc recipients receive the message and are visible to everyone on it.
  • bcc recipients receive the message but never appear in its headers — other recipients cannot see them.

A few things happen for you automatically:

  • Suppression covers everyone. Every address — to, cc, and bcc — is checked against your domain's all-scope suppressions (bounces, complaints, manual entries — marketing unsubscribes never block transactional mail); a suppressed address rejects the whole send with a 422 naming the field, so nothing goes out partially.
  • Every recipient counts. Each cc/bcc address counts as one email toward your plan's volume and the free tier's quota, exactly like to.
  • Duplicates are dropped. An address repeated within a list, or already present in to (or in cc, for bcc), is silently de-duplicated.
  • Status follows to. The email's status tracks the primary recipient's journey. A bounce or complaint from a cc/bcc address is recorded as an event, fires its webhook, and suppresses that address — without flipping the send's status.

Reply-to

Set reply_to to steer replies somewhere other than your from address — a single address or an array of up to 10.

Reply-to addresses must stay in your sending domain's family: the sending domain itself, one of its subdomains, or its parent (sending from mail.acme.com? support@acme.com works). An address outside that family is rejected with a 422 — replies to mail you send should land with you, never with a third party.

Reply-to addresses are not recipients: they receive nothing at send time, so they are never suppression-checked and don't count toward your quota.

Attachments and inline images

Add an attachments array to a transactional send. Each entry needs a filename and exactly one source:

  • A local file — set content to the file's Base64-encoded bytes.
  • A remote file — set path to an HTTPS URL and we fetch it for you.

Optional per attachment: content_type (inferred from the filename when omitted) and content_id (see inline images below).

curl https://api.minimailer.app/outbound-emails \
  -X POST \
  -H "Authorization: Bearer $MINIMAILER_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -d '{
    "data": {
      "type": "outbound-emails",
      "attributes": {
        "from": "billing@acme.com",
        "to": "customer@example.com",
        "subject": "Your invoice",
        "html": "<p>Invoice attached.</p>",
        "attachments": [
          { "filename": "invoice.pdf", "content": "JVBERi0xLjcK..." },
          { "filename": "terms.pdf", "path": "https://cdn.acme.com/terms.pdf" }
        ]
      },
      "relationships": { "domain": { "data": { "type": "domains", "id": "{domain}" } } }
    }
  }'

Limits. The whole message, including attachments after encoding, must stay under 40 MB. Executable and script file types (.exe, .js, .bat, and similar) are rejected for the recipient's safety. A remote path must be a public HTTPS URL.

Inline images. To embed an image in the body instead of appending it as a download, give the attachment a content_id and reference it from your HTML with cid::

"attachments": [
  { "filename": "logo.png", "content": "iVBORw0KGgo...", "content_id": "logo" }
]
<img src="cid:logo" alt="Acme" />

Attachment metadata (filename, content type, size, and whether it is inline) is returned on the email resource; the file bytes themselves are never stored or returned.

Idempotency

Network timeouts make "did that send go through?" a real question. Pass an idempotency_key (any string up to 255 characters — an order ID, a job UUID) and retries of the same logical send become safe: the same key never produces a duplicate email.

Suppression checks happen for you

Every send is checked against your domain's suppression list before it goes out — every recipient on it, to, cc, and bcc alike. A recipient who hard-bounced or marked your mail as spam is rejected with a 422 instead of being delivered. This is not optional, and that's deliberate: repeatedly mailing a dead or unwilling address is the fastest way to destroy deliverability. Marketing unsubscribes are different — they block your campaigns, not your transactional mail, so an opted-out customer still gets their receipts and password resets. How suppression works →

Open and click tracking

Tracking is off by default and opt-in at two levels:

  1. Domain level — enable open and/or click tracking on the domain. This is the capability switch.
  2. Per sendtrack_opens / track_clicks on an individual email or campaign can narrow within what the domain allows (e.g. domain has tracking on, one send opts out). A per-send flag can't enable tracking the domain has off.

Opens are detected with a tracking pixel, clicks by wrapping links through a redirect. Both feed the email.opened / email.clicked webhooks and the stats endpoints. The default-off posture is a compliance choice: enable tracking when you have a lawful basis for it. EU data residency and privacy →

Rate limits and quotas

API rate limits scale with your subscription tier, applied per account across the whole API (including MCP):

Tier Requests per minute
Spark (free) 60
Starter 300
Growth 900
Scale 3,000

Hitting a limit returns 429 — back off and retry. Separately from rate limits, the free tier enforces its sending quota (5,000 emails/month, 1,000/day); a send over quota is rejected with an explanatory error rather than silently queued. Each recipient counts toward quota — a send with to plus three cc addresses consumes four emails, and a send that doesn't fully fit in the remaining quota is rejected whole rather than partially delivered.

Delivery lifecycle

After acceptance, an email moves through delivery events — sent, delivered, bounced, complained, opened, clicked. You can poll the stats endpoints, but the intended integration is webhooks: every lifecycle event pushes to your endpoint as it happens.