# Mail

> Send mail from your own domains, read the delivery log, receive inbound mail, and manage suppressions.

Rytiv sends mail from domains you prove (SPF, DKIM and a verification record are shown by the domain routes) and keeps a log per message. A key with the scope `mail:send` or `mail:send:<domain>` may send and nothing else.

## Endpoints

- [`POST /v1/emails`](#post-v1emails): Sends a message from one of the organization's proven domains
- [`GET /v1/emails`](#get-v1emails): The delivery log
- [`GET /v1/emails/:id`](#get-v1emailsid): One message with its deliveries
- [`GET /v1/mail/domains`](#get-v1maildomains): The sending domains and their status
- [`POST /v1/mail/domains`](#post-v1maildomains): Adds a sending domain and answers the records to set
- [`GET /v1/mail/domains/:domain`](#get-v1maildomainsdomain): One sending domain
- [`POST /v1/mail/domains/:domain/verify`](#post-v1maildomainsdomainverify): Checks the domain's records now
- [`DELETE /v1/mail/domains/:domain`](#delete-v1maildomainsdomain): Removes a sending domain
- [`POST /v1/mail/domains/:domain/inbound/routes`](#post-v1maildomainsdomaininboundroutes): Routes inbound mail for an address of the domain
- [`DELETE /v1/mail/domains/:domain/inbound/routes/:id`](#delete-v1maildomainsdomaininboundroutesid): Removes an inbound route
- [`GET /v1/mail/inbound`](#get-v1mailinbound): Inbound messages
- [`GET /v1/mail/inbound/:id`](#get-v1mailinboundid): One inbound message
- [`GET /v1/mail/suppressions`](#get-v1mailsuppressions): Addresses Rytiv no longer sends to (bounces, complaints)
- [`DELETE /v1/mail/suppressions/:address`](#delete-v1mailsuppressionsaddress): Removes an address from the suppression list

## POST /v1/emails

Sends a message from one of the organization's proven domains.

**Auth:** API key with full access or the scope `mail:send` (or `mail:send:<domain>` for the sending domain). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Request body

JSON, the shape `SendEmailRequest` of the contract.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `from` | string | required | 3 to 400 characters. |
| `to` | string | required | Another hostname of the same app to redirect to, or `null` to clear the redirect. 3 to 254 characters. |
| `subject` | string | required | 1 to 998 characters. |
| `text` | string | optional | at most 1000000 characters. |
| `html` | string | optional | The page as HTML. at most 1000000 characters. |
| `replyTo` | string | optional | 3 to 254 characters. |
| `headers` | object | optional |  |
| `headers.List-Unsubscribe` | string | optional |  |
| `headers.List-Unsubscribe-Post` | string | optional |  |
| `headers.In-Reply-To` | string | optional |  |
| `headers.References` | string | optional |  |
| `attachments` | array of object | optional | 0 to 10 items. |

### Example request

```bash
curl -X POST "https://api.rytiv.app/v1/emails" \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"from":"<from>","to":"www.example.com","subject":"<subject>"}'
```

### Response

`202` with a JSON object carrying `status`.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `bad-request` | The request is malformed or a field is out of range. `error` names every field; `issues` lists them. |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |

## GET /v1/emails

The delivery log.

**Auth:** API key with full access or the scope `read`. An agent token of class `production` needs `prodDataRead` for it; `sandbox` and `twin` may call it.

### Query parameters

| Name | Description |
| --- | --- |
| `limit` | How many entries, at most. |

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/emails" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with a JSON body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |

## GET /v1/emails/:id

One message with its deliveries.

**Auth:** API key with full access or the scope `read`. An agent token of class `production` needs `prodDataRead` for it; `sandbox` and `twin` may call it.

### Path parameters

| Name | Description |
| --- | --- |
| `id` | The object's id. |

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/emails/<id>" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with a JSON object carrying `events` and the fields of the object the handler builds.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## GET /v1/mail/domains

The sending domains and their status.

**Auth:** API key with full access or the scope `read`. Every class of agent token may call it.

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/mail/domains" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with a JSON body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |

## POST /v1/mail/domains

Adds a sending domain and answers the records to set.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Request body

JSON, the shape `AddMailDomainRequest` of the contract.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `domain` | string | required | The domain, for example `www.example.com`. Wildcards are not accepted. 1 to 253 characters. |

### Example request

```bash
curl -X POST "https://api.rytiv.app/v1/mail/domains" \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domain":"www.example.com"}'
```

### Response

`201` with a JSON object carrying `appDomain`, `records` and the fields of the object the handler builds.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `bad-request` | The request is malformed or a field is out of range. `error` names every field; `issues` lists them. |
| 400 | `name-reserved` | The platform keeps this name (for example `api`, `console`, `docs`, `www`, and every name that is `rytiv` or starts with `rytiv-`). |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 409 | `already-exists` | You already have it; an idempotent create may treat this as done. |
| 409 | `name-taken` | The name is used somewhere on Rytiv (an app, a database, a volume or a bucket). |
| 429 | `too-many-pending` |  |

## GET /v1/mail/domains/:domain

One sending domain.

**Auth:** API key with full access or the scope `read`. Every class of agent token may call it.

### Path parameters

| Name | Description |
| --- | --- |
| `domain` | The domain, as added. |

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/mail/domains/<domain>" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with a JSON object carrying `appDomain`, `records`, `inbound` and the fields of the object the handler builds.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## POST /v1/mail/domains/:domain/verify

Checks the domain's records now.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Path parameters

| Name | Description |
| --- | --- |
| `domain` | The domain, as added. |

### Example request

```bash
curl -X POST "https://api.rytiv.app/v1/mail/domains/<domain>/verify" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with a JSON object carrying `domain`, `status`, `checks`.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## DELETE /v1/mail/domains/:domain

Removes a sending domain.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Path parameters

| Name | Description |
| --- | --- |
| `domain` | The domain, as added. |

### Example request

```bash
curl -X DELETE "https://api.rytiv.app/v1/mail/domains/<domain>" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with a JSON object carrying `domain`, `removed`.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## POST /v1/mail/domains/:domain/inbound/routes

Routes inbound mail for an address of the domain.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Path parameters

| Name | Description |
| --- | --- |
| `domain` | The domain, as added. |

### Request body

JSON, the shape `AddMailInboundRouteRequest` of the contract.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `address` | string | required | 1 to 320 characters. |
| `action` | `store` | optional | default `"store"`. |

### Example request

```bash
curl -X POST "https://api.rytiv.app/v1/mail/domains/<domain>/inbound/routes" \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"address":"<address>"}'
```

### Response

`201` with a JSON body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## DELETE /v1/mail/domains/:domain/inbound/routes/:id

Removes an inbound route.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Path parameters

| Name | Description |
| --- | --- |
| `domain` | The domain, as added. |
| `id` | The object's id. |

### Example request

```bash
curl -X DELETE "https://api.rytiv.app/v1/mail/domains/<domain>/inbound/routes/<id>" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`204` with no body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## GET /v1/mail/inbound

Inbound messages.

**Auth:** API key with full access or the scope `read`. An agent token of class `production` needs `prodDataRead` for it; `sandbox` and `twin` may call it.

### Query parameters

| Name | Description |
| --- | --- |
| `limit` | How many entries, at most. |
| `domain` |  |

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/mail/inbound" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with a JSON body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |

## GET /v1/mail/inbound/:id

One inbound message.

**Auth:** API key with full access or the scope `read`. An agent token of class `production` needs `prodDataRead` for it; `sandbox` and `twin` may call it.

### Path parameters

| Name | Description |
| --- | --- |
| `id` | The object's id. |

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/mail/inbound/<id>" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with a JSON object carrying `raw`, `text` and the fields of the object the handler builds.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## GET /v1/mail/suppressions

Addresses Rytiv no longer sends to (bounces, complaints).

**Auth:** API key with full access or the scope `read`. An agent token of class `production` needs `prodDataRead` for it; `sandbox` and `twin` may call it.

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/mail/suppressions" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with a JSON body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |

## DELETE /v1/mail/suppressions/:address

Removes an address from the suppression list.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Path parameters

| Name | Description |
| --- | --- |
| `address` | The e-mail address. |

### Example request

```bash
curl -X DELETE "https://api.rytiv.app/v1/mail/suppressions/<address>" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`204` with no body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |
