---
title: "Add an email recipient to a schedule."
method: POST
path: "/v1/dashboards/{id}/export-schedules/{sid}/recipients"
tags: ["exports"]
---

# Add an email recipient to a schedule.

`POST /v1/dashboards/{id}/export-schedules/{sid}/recipients`

## Request

- Base URL: `http://localhost:8080`
- URL: `http://localhost:8080/v1/dashboards/{id}/export-schedules/{sid}/recipients`
- Auth: one of:
  - API key in cookie `dvt_session`
  - HTTP bearer

Adds an email recipient to the schedule (ADR-0051 §3). Requires `dashboard:write`.
The email is NFC-normalized and lowercased before classification:
- If the email matches a current org member → `status=active` (immediate delivery).
- If external (not a member) → `status=pending_approval`. **No email is sent** to
  pending or rejected recipients — approval must be explicit (approve endpoint).
`email` and `is_external` are immutable after insert; to change an address,
delete and re-add the recipient (which re-enters the approval state machine).
Returns 409 if the email is already added to this schedule.

## Request body

- object
  - `email` string, email, required

## Response `201`

Recipient added.

- ExportRecipientEnvelope
  - `data` ExportRecipient, required: One email target on an export schedule, with its approval state machine (ADR-0051 §3). `email` and `isExternal` are immutable after insert — change an address by removing and re-adding the recipient (which re-enters the approval state machine).
    - `id` string, uuid, required
    - `scheduleId` string, uuid, required
    - `email` string, email, required
    - `isExternal` boolean, required: True when the email did not match a current org member at add time (fail-closed). Immutable after insert.
    - `status` 'active' | 'pending_approval' | 'rejected', required: `active` — will receive deliveries. `pending_approval` — awaiting owner/admin approval (external only). `rejected` — approval denied; never delivered to.
    - `approvedBy` string, uuid, nullable: The user who approved this recipient (external only).
    - `approvedAt` string, date-time, nullable
    - `createdAt` string, date-time, required
    - `updatedAt` string, date-time, required
  - `meta` Meta, required: Response metadata.
    - `requestId` string, required: Correlates with the X-Request-Id header.
    - `total` integer: Item count, present on collection responses.
    - `nextCursor` string, nullable: Opaque keyset-pagination cursor, present on paginated responses (e.g. GET /v1/dashboards) when more rows exist beyond this page. Pass it back as the `cursor` query param to fetch the next page; absent or null on the last page.
    - `roleResolveDegraded` boolean: Snowflake mode only (DVT-2260), on GET /v1/dashboards: present and true when the caller's live Snowflake role resolution failed or was unavailable — role-shared dashboards may be missing from this page. Omitted (not merely false) whenever resolution succeeded or the role-share arm does not apply (off snowflake mode, an org admin/owner, or off the ingress listener).

## Other responses

- `400`: Malformed request.
- `401`: Missing, invalid, or expired session.
- `403`: The caller's role does not permit this action.
- `404`: Resource does not exist.
- `409`: This email is already a recipient on this schedule.

---

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