# Send API

Set `BASE=https://werkmail.eu`. Authenticate with a session, team, or project bearer.

## Single send

`POST /api/practices/{id}/send` → **202**

```bash
curl -sS -X POST "$BASE/api/practices/$PRACTICE_ID/send" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-123" \
  -d '{
    "from": "Praxis <noreply@your-domain.example>",
    "to": ["you@example.com"],
    "reply_to": ["praxis@example.com"],
    "subject": "Hello from Werkmail",
    "text": "Plain text body",
    "html": "<p>HTML body</p>",
    "tag": "appointment",
    "route": "outgoing",
    "message_kind": "transactional",
    "metadata": {"patient_id": "42"}
  }'
```

Response:

```json
{ "message_id": "uuid", "id": "uuid", "status": "queued" }
```

Blocked content returns 4xx with `code` `content_blocked`, `content_reasons`, and `scanner`.

### Fields

| Field | Type | Notes |
| --- | --- | --- |
| `from` | string | Optional; defaults to the tenant From |
| `to` | string or string[] | Required |
| `cc`, `bcc`, `reply_to` | string or string[] | Optional |
| `subject` | string | Required |
| `text` / `text_body` | string | One of text or html required |
| `html` / `html_body` | string | |
| `route` | string | Slug or UUID; default `outgoing` |
| `message_kind` | string | `termin`, `recall`, `doi`, `transactional`, `newsletter`, `custom`, … |
| `tag` | string | First tag; used in filters |
| `tags` | string[] | Normalized case-insensitive unique |
| `metadata` | object | String values; echoed on webhooks |
| `headers` | object | Extra MIME headers (restricted set) |
| `attachments` | array | See [Sending](/sending) |
| `settings.track_opens` | bool | Override route default |
| `settings.track_clicks` | bool | Override route default |
| `calendar` | object | ICS invite (`summary`, `starts_at`, …) |
| `idempotency_key` | string | Same as the header |

Recipient cap: 50 (to+cc+bcc). Dedup across buckets.

## Batch

`POST /api/practices/{id}/send/batch` — JSON array, max **500**.

```bash
curl -sS -X POST "$BASE/api/practices/$PRACTICE_ID/send/batch" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '[
    {"to":"a@example.com","subject":"One","text":"hi","idempotency_key":"order-1","tags":["orders"]},
    {"to":"b@example.com","subject":"Two","text":"hi","idempotency_key":"order-2","tag":"receipt"}
  ]'
```

## Bulk (templated)

`POST /api/practices/{id}/send/bulk` and `POST /api/emails/send-bulk` (flat; pass `project_id`).

Placeholders use `{{variable}}`. Each recipient is rendered locally and sent through the shared pipeline. Response is a per-recipient `results[]` array.

```bash
curl -sS -X POST "$BASE/api/emails/send-bulk" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: campaign-2026-03-20" \
  -d '{
    "project_id": "'"$PRACTICE_ID"'",
    "template": {
      "subject": "Hello {{name}}",
      "html": "<p>Hi {{name}}, your code is {{code}}.</p>"
    },
    "recipients": [
      {"email":"a@example.com","data":{"name":"Ada","code":"42"}},
      {"email":"b@example.com","data":{"name":"Grace","code":"7"}}
    ]
  }'
```

Defaults: `message_kind=newsletter`, route `broadcast`. List-Unsubscribe is always injected.

## Score (dry-run)

`POST /api/practices/{id}/send/score` — same body as send, no delivery.

Returns `score`, `grade`, `inbox_risk`, `would_block`, `reasons`, `warnings`, `scanner`, `recommendations`, and broadcast/Impressum eligibility when the route is marketing.

## Test send

`POST /api/practices/{id}/send-test`

```json
{ "kind": "quality", "to": "you@example.com" }
```

`kind`: `quality` | `success` | `bounce` | `complaint` | `ooto`

`success` / `bounce` / `complaint` / `ooto` target the mailbox simulator. `quality` sends a placement-oriented sample to the address you provide (allowlisted in safe mode).
