> ## Documentation Index
> Fetch the complete documentation index at: https://docs.minimo.it/llms.txt
> Use this file to discover all available pages before exploring further.

# Send One-Shot HTML Email

> Dispatch a single transactional email by sending a pre-rendered HTML payload — no template required

## Overview

Use this endpoint when you want to send a transactional email **without
creating a template** in the dashboard. You provide the rendered HTML
yourself and Minimo handles dispatch, delivery, and tracking.

Typical use cases:

* **Server-side rendered receipts**: invoices, order confirmations, shipping notifications
* **Authentication flows**: magic links, OTP delivery, email verification
* **Internal notifications**: alerts produced by your own background jobs
* **Anything dynamic enough to not fit a pre-defined template**

<Tip>
  Tracking (open pixel + click rewriting) is injected **server-side**. Send your raw HTML as-is — don't pre-rewrite
  links or append a pixel yourself.
</Tip>

## Before You Start

1. Generate an API key with the **`TRANSACTIONAL`** permission from the Minimo dashboard.
2. Make sure your company's email provider is configured (SMTP or SES).
3. Have your HTML body ready — Minimo doesn't render templates for this endpoint.

## What the Server Does to Your HTML

When you send `html`, the backend:

1. **Resolves placeholders**: `{{UNSUBSCRIBE_URL}}`, `{{PREFERENCES_URL}}`, and `{{FAKE_IMAGE_URL}}` are replaced with the per-recipient tracked URLs.
2. **Strips any pre-existing tracking pixel**: an `<img>` whose `src` matches the Minimo open-pixel pattern is removed so it can't double-fire.
3. **Rewrites tracked anchors**: every `<a href>` pointing to `http(s)` or a relative URL is replaced with a click-tracking URL. `mailto:`, fragment-only (`#…`), and pre-existing `/api/click/*` links are left alone.
4. **Appends the open-tracking pixel** at the end of `<body>` (or at the document root if no `<body>` exists).

The recipient receives the final HTML; analytics flow into `GET /public/v1/emails/{id}/stats`.

## Example Request

```bash theme={null}
curl --request POST \
  --url https://api.minimo.it/public/v1/emails \
  --header 'Authorization: Bearer mn-{apiClientId}-{secret}' \
  --header 'Content-Type: application/json' \
  --data '{
    "to": "customer@example.com",
    "subject": "Your receipt",
    "html": "<p>Thanks for your purchase. <a href=\"https://example.com/receipt/12345\">View receipt</a></p>"
  }'
```

## Response Shape

The endpoint **always returns HTTP 200** when authentication succeeds — inspect `status` to know whether the provider accepted the message.

```json theme={null}
{
  "data": {
    "id": "DMPNEF8",
    "status": "sent",
    "messageId": "0100019c8b3d4a4f-...",
    "statsUrl": "/public/v1/emails/DMPNEF8/stats"
  }
}
```

When the provider rejects the message you still get back the same `id`, so the delivery row remains queryable:

```json theme={null}
{
  "data": {
    "id": "DMPNEF8",
    "status": "failed",
    "failureReason": "Email address is not verified.",
    "statsUrl": "/public/v1/emails/DMPNEF8/stats"
  }
}
```

## Use Cases

<AccordionGroup>
  <Accordion title="Order receipt with embedded link">
    Render the receipt server-side and post it raw — Minimo rewrites the `View receipt` anchor into a tracked link.

    ```json theme={null}
    {
      "to": "customer@example.com",
      "subject": "Receipt for ORD-12345",
      "html": "<h1>Thanks, Jane!</h1><p>Your order <strong>ORD-12345</strong> is on the way.</p><p><a href=\"https://shop.example.com/orders/ORD-12345\">Track your order</a></p>"
    }
    ```
  </Accordion>

  <Accordion title="Magic-link sign-in">
    Override the sender per request to brand the email for a specific product surface.

    ```json theme={null}
    {
      "to": "newuser@example.com",
      "subject": "Sign in to Acme",
      "html": "<p>Hi! Click <a href=\"https://app.example.com/auth?token=eyJ...\">here</a> to sign in. Link expires in 15 minutes.</p>",
      "fromAddress": "no-reply@acme.com",
      "fromName": "Acme Sign-In"
    }
    ```
  </Accordion>

  <Accordion title="Internal alert from a background job">
    Plain HTML with a single CTA is enough — the open and click events surface in the stats endpoint.

    ```json theme={null}
    {
      "to": "ops@example.com",
      "subject": "[ALERT] Queue depth above threshold",
      "html": "<p>Worker queue at 12,400 jobs.</p><p><a href=\"https://admin.example.com/jobs\">Open dashboard</a></p>",
      "text": "Worker queue at 12,400 jobs. Open dashboard: https://admin.example.com/jobs"
    }
    ```
  </Accordion>
</AccordionGroup>

## Best Practices

### Pair every send with a `statsUrl` lookup

The response includes `statsUrl`. Persist the `id` (or the full `statsUrl`) alongside whatever business object triggered the send (order id, user id, etc.) so you can correlate opens and clicks later.

### Handle the `failed` branch

`status: "failed"` means the provider rejected the message — usually a malformed recipient, an unverified sender, or a hard bounce. Surface `failureReason` to your operators, then either retry with corrected data or log the failure.

### Keep HTML self-contained

Inline your CSS. Many email clients drop `<style>` blocks. Test in major clients (Gmail, Apple Mail, Outlook) before going live.

### Provide a `text` fallback for deliverability

Spam filters reward multipart messages. Adding a plain-text alternative reduces the chance of landing in the junk folder.

### Don't double-track

If your HTML already contains `/api/click/*` URLs or an open pixel from a previous Minimo send, leave them alone — the server detects and skips them. Inserting your own tracking on top is unnecessary.

## Common Errors

| Status | Body                                                  | Cause                                              |
| ------ | ----------------------------------------------------- | -------------------------------------------------- |
| `400`  | `{ "error": "to must be an email" }`                  | Invalid or missing required field                  |
| `401`  | —                                                     | API key missing or wrong format                    |
| `403`  | —                                                     | API key lacks the `TRANSACTIONAL` permission       |
| `200`  | `{ "data": { "status": "failed", "failureReason" } }` | Provider rejected the message (still a `200`)      |
| `500`  | —                                                     | Server misconfiguration (e.g. `WEB_APP_URL` unset) |

## Related Endpoints

* [Get One-Shot Email Statistics](/api-reference/messaging-channels/email/html-stats) — poll opens and clicks for a delivery
* [Send Email Template](/api-reference/messaging-channels/email/send-template) — same idea, but starting from a dashboard template


## OpenAPI

````yaml post /public/v1/emails
openapi: 3.1.0
info:
  title: API Documentation
  description: Documentation for transactional and subscribe APIs
  version: 1.0.0
servers:
  - url: https://app.minimo.it
security:
  - bearerAuth: []
paths:
  /public/v1/emails:
    servers:
      - url: https://api.minimo.it
    post:
      summary: Send a one-shot HTML email
      description: |
        Dispatches a single transactional HTML email via the company's
        configured email provider. The server parses the HTML payload,
        injects the open-tracking pixel, and rewrites `<a>` hrefs into
        tracked URLs (same logic as template-backed campaigns). Returns
        an opaque delivery id usable in `GET /public/v1/emails/{id}/stats`.

        Requires an API key with the `TRANSACTIONAL` permission. The
        endpoint always returns HTTP 200 — inspect `status` to tell
        `sent` from `failed`.
      operationId: sendHtmlEmail
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - to
                - subject
                - html
              properties:
                to:
                  type: string
                  format: email
                  description: Recipient email address.
                subject:
                  type: string
                  description: Email subject line.
                html:
                  type: string
                  description: |
                    Pre-rendered HTML body. The server injects the open
                    pixel and rewrites anchor `href`s — do not pre-process
                    them on your side.
                text:
                  type: string
                  description: Plain-text fallback for clients without HTML support.
                fromAddress:
                  type: string
                  format: email
                  description: >-
                    Per-request sender override. Falls back to the company email
                    settings.
                fromName:
                  type: string
                  description: Per-request sender display name override.
            examples:
              minimal:
                summary: Minimal payload
                value:
                  to: customer@example.com
                  subject: Your receipt
                  html: >-
                    <p>Thanks for your purchase. <a
                    href="https://example.com/receipt/12345">View
                    receipt</a></p>
              withSenderOverride:
                summary: With sender override
                value:
                  to: customer@example.com
                  subject: Your receipt
                  html: <p>Receipt</p>
                  text: >-
                    Thanks for your purchase. View your receipt at
                    https://example.com/receipt/12345
                  fromAddress: hello@example.com
                  fromName: Acme Receipts
      responses:
        '200':
          description: |
            Delivery accepted by the queue. Check `data.status` to tell
            whether the provider actually accepted the message.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        description: Opaque sqid-encoded delivery id.
                        example: DMPNEF8
                      status:
                        type: string
                        enum:
                          - sent
                          - failed
                      messageId:
                        type: string
                        description: >-
                          Provider-side message id (e.g. AWS SES MessageId).
                          Populated on success.
                      failureReason:
                        type: string
                        description: Populated when `status = "failed"`.
                      statsUrl:
                        type: string
                        description: Relative URL to poll for delivery statistics.
                        example: /public/v1/emails/DMPNEF8/stats
              examples:
                sent:
                  summary: Successful dispatch
                  value:
                    data:
                      id: DMPNEF8
                      status: sent
                      messageId: 0100019c8b3d4a4f-...
                      statsUrl: /public/v1/emails/DMPNEF8/stats
                failed:
                  summary: Provider rejected the message
                  value:
                    data:
                      id: DMPNEF8
                      status: failed
                      failureReason: Email address is not verified.
                      statsUrl: /public/v1/emails/DMPNEF8/stats
        '400':
          description: Validation error in the request body.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: to must be an email
        '401':
          description: Missing or invalid API key.
        '403':
          description: API key lacks the `TRANSACTIONAL` permission.
        '500':
          description: Server-side configuration error (e.g. `WEB_APP_URL` not set).
      security:
        - bearerAuth: []
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: mn-API_CLIENT_ID-API_KEY

````