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

# Drafts

> Prepare a message and leave it for a person to review and send.

Sending and scheduling both commit: something leaves, now or later. A draft is
the third thing, and the only one that puts a person back in the loop —
compose a reply, leave it in the Drafts folder, let somebody read it and press
send.

**Required permission:** Drafts → Read to list, Drafts → Write to create, edit
or delete.

<Note>
  **Drafts written here appear in the dashboard.** That is the point: a draft
  nobody can see is just a string in a database. Whoever opens the web app finds
  it in Drafts, opens it in the composer, edits it and sends it like any other.
</Note>

<Warning>
  **These are organization drafts. A key never sees anybody's personal drafts.**

  Drafts written by a person in the web app belong to that person and are
  invisible to every API key. Half-written mail is often the most sensitive thing
  in an account, and a key leaked from a CI job should not expose what somebody
  has not decided to send yet.

  The reverse is not symmetrical on purpose: drafts a key creates **are** visible
  to your team in the dashboard, because a draft nobody can reach cannot be
  reviewed.
</Warning>

## List drafts

```
GET https://app.ruber.me/api/v1/drafts
```

Takes no parameters — drafts belong to the organization, not to a mailbox.
Newest first, up to 200.

```json theme={null}
{
  "data": [
    {
      "id": "61b76e1d-bd4b-4729-aae9-b3fdc8af1caa",
      "subject": "Re: Invoice 42",
      "to": ["billing@supplier.com"],
      "cc": [],
      "bcc": [],
      "attachment_count": 0,
      "created_at": "2026-09-07T22:20:05.446Z",
      "updated_at": "2026-09-07T22:20:18.411Z"
    }
  ]
}
```

No bodies in the list — fetch one for its text.

## Create a draft

```
POST https://app.ruber.me/api/v1/drafts
```

```bash theme={null}
curl -X POST https://app.ruber.me/api/v1/drafts \
  -H "Authorization: Bearer rk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["billing@supplier.com"],
    "subject": "Re: Invoice 42",
    "text": "Thanks — paid this morning.",
    "html": "<p>Thanks — paid this morning.</p>"
  }'
```

| Field             | Type            |                                            |
| ----------------- | --------------- | ------------------------------------------ |
| `to`, `cc`, `bcc` | array or string | Up to 25 each. **All optional.**           |
| `subject`         | string          | Up to 200 characters.                      |
| `text`            | string          | Plain-text body, up to 100,000 characters. |
| `html`            | string          | HTML body, up to 400,000 characters.       |

`201 Created`, returning the draft including its `id`.

<Note>
  **Every field is optional, including the recipients.** A draft is unfinished by
  definition — that is what makes it a draft. Refusing to save one because the
  address is half-typed is how a drafts feature loses somebody's work.

  Validation happens when the message is *sent*, not while it is being written.
</Note>

## Read one

```
GET https://app.ruber.me/api/v1/drafts/{id}
```

The same fields as the list, plus `text` and `html`.

## Replace one

```
PATCH https://app.ruber.me/api/v1/drafts/{id}
```

Takes the same body as create.

<Warning>
  **This replaces the whole draft; it does not merge.** A field you leave out is
  cleared, not kept.

  A merging PATCH would need some way to say "clear the subject", and the obvious
  encoding — omit the field — is already how you say "leave it alone". Send the
  draft as you want it to be.
</Warning>

## Delete one

```
DELETE https://app.ruber.me/api/v1/drafts/{id}
```

```json theme={null}
{ "deleted": true, "id": "61b76e1d-bd4b-4729-aae9-b3fdc8af1caa" }
```

Gone, along with its stored body. There is no Trash for drafts.

## Sending a draft

There is no "send this draft" call. Read it, then
[`POST /v1/messages`](/api/sending) with its contents and a `from`, and delete
the draft once the send succeeds.

That is deliberate rather than missing. A draft has no sender — `from` is
chosen when the message goes, and a send endpoint that inherited it from a
draft would be a second, quieter way to decide which address your mail leaves
under.

## Errors

| Code              | HTTP  | Cause                                                                      |
| ----------------- | ----- | -------------------------------------------------------------------------- |
| `invalid_request` | `400` | Malformed JSON, a bad address, or a field over its limit.                  |
| `unauthorized`    | `401` | Missing, malformed, unknown or revoked key.                                |
| `forbidden`       | `403` | The key lacks `drafts:read` or `drafts:write`.                             |
| `not_found`       | `404` | No organization draft with that id. A personal draft answers the same way. |
| `rate_limited`    | `429` | Over 600 requests in a minute.                                             |
| `internal_error`  | `500` | Storage was unreachable. Nothing was saved.                                |

See [Errors](/api/introduction#errors) for the full response shape.
