:::endpoint POST /v1/me/mail/assist

The writing help behind the spark in the web composer. Send the message as it
stands and say what to do with it; you get back a proposal for the whole body
and a few remarks for the writer. Nothing is saved. Show the proposal, and only
when the writer accepts it put it in the composer and
[save the draft](/docs/api/mail/draft) the way you always do.

What makes it worth calling rather than a spell checker is that it reads the
conversation the draft answers. Name the message being answered in
`in_reply_to` and the exchange around it is loaded on our side, from what this
account may read, so a remark can say that the amount in the answer is not the
amount the customer asked about. Never send the conversation yourself: it is not
read from the request.

Every call is paid for out of the environment's AI allowance and appears in its
AI usage alongside the calls made from the browser. That is why it takes
`mail:write` although it changes nothing.

Whether to offer it at all is `meta.writing_help` on
[the signatures](/docs/api/mail/signatures), which a composer asks for anyway.

## Three actions

| `action`    | What it does                                                                                                       |
| ----------- | ------------------------------------------------------------------------------------------------------------------ |
| `proofread` | Spelling, grammar and punctuation only. The wording, the structure and the tone stay the writer's                 |
| `improve`   | The same corrections, and wording that reads clearly and professionally. Same meaning, roughly the same length     |
| `custom`    | Whatever `instruction` asks. With an empty `body` it writes the message from the conversation                      |

The proposal is in the language of the draft, or of the conversation when there
is no draft yet. The remarks are in English.

## Request

```bash
curl https://api.proppertrading.com/v1/me/mail/assist \
  -H "Authorization: Bearer ptat_9f2c1a44_3b8e7d2f5c9a1b4e6d8f0a2c4e6b8d1f" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2a8e-4b7d-4e19-9a52-0d3b8c7e5f21" \
  -d '{
    "address_id": "mbx_w6sy3skvgydh5e3b",
    "action": "improve",
    "subject": "Re: Account locked after the news spike",
    "body": "<p>hi, we looked at it and the drawdown was 5.2% so the breach is correct, sorry</p>",
    "in_reply_to": "msg_h3k8q2w7n4r9t5vx"
  }'
```

| Parameter     | Type    | Description                                                                                                                                  |
| ------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `address_id`  | string  | Required. The `id` of the mailbox the message is written from, from [list mailboxes](/docs/api/mail/mailboxes)                                |
| `action`      | string  | Required. `proofread`, `improve` or `custom`                                                                                                 |
| `instruction` | string  | Required for `custom`, at most 1,000 characters. What to do, in the writer's words                                                           |
| `body`        | string  | HTML, as you would send it, without the quote and without the sign-off. Required unless `action` is `custom`. At most 100,000 characters     |
| `subject`     | string  | The subject as it stands. Optional, at most 255 characters                                                                                   |
| `in_reply_to` | string  | The `id` of the message being answered. Its conversation is what the help reads                                                              |
| `quoted`      | string  | HTML of a quoted message, for a forward or anything else without an `in_reply_to`. Read only when `in_reply_to` is absent                    |

## Response

```json
{
    "data": {
        "object": "mail_suggestion",
        "body": "<p>Hi, we looked into it, and the drawdown was 5.2%, so the breach is correct. Sorry about that.</p>",
        "remarks": [
            "The draft gives a 5.2% drawdown, while Daan says the dashboard shows 4.2% and the email says 5.1%; please verify the figure before sending."
        ]
    },
    "meta": {
        "request_id": "req_01m48qq03h3zc7n1w4c0kb6ty7"
    }
}
```

| Field     | Type     | Description                                                                                                                                         |
| --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `body`    | string   | The proposed body as HTML, through the same sanitiser as anything typed into a composer. It replaces the whole body, not a part of it               |
| `remarks` | string[] | At most five, one sentence each, for the writer rather than the recipient: a figure that does not match, a question left unanswered. Often empty |

A proofread of a draft with nothing wrong in it comes back with the same text.
Compare the two as text rather than as markup before offering to replace
anything, because the sanitiser may tidy markup it did not change a word of.

Retrying with the same `Idempotency-Key` returns the same proposal without a
second call to the model, and without a second charge. Asking again on purpose
needs a new key.

## Limits

Fifteen calls a minute per person, shared with the web composer, and each one
usually takes a few seconds. On top of that the environment's AI allowance
applies, per window and per week, as it does everywhere else AI is used.

## Errors

| Status | Code                           | Meaning                                                                                                                                                                              |
| ------ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 403    | `insufficient_scope`           | This device does not hold `mail:write`                                                                                                                                               |
| 403    | `mailbox_not_permitted`        | This account may not open a mailbox                                                                                                                                                  |
| 403    | `assist_unavailable`     | The environment has no AI: none is connected, or it is switched off. Waiting does not change it                                                                                      |
| 404    | `resource_not_found`           | `address_id` or `in_reply_to` is not in a mailbox this account can read                                                                                                              |
| 422    | `validation_failed`            | A field is missing or too long, or an id was sent as a number. `instruction` is required for `custom`, `body` for the other two                                                      |
| 429    | `assist_limit_reached`       | The AI allowance is used up. `state` says which: `session`, `week`, `none` (no AI in the package) or `platform` (our capacity). `resets_at` says when it returns, absent when waiting will not help |
| 429    | `rate_limit_exceeded`          | More than fifteen calls in a minute                                                                                                                                                  |
| 503    | `assist_failed`          | The model did not answer. Nothing changed; try again                                                                                                                                 |

```json
{
    "error": {
        "type": "rate_limit_error",
        "code": "assist_limit_reached",
        "message": "The AI allowance for this week is used up. It comes back in 2 days.",
        "doc_url": "https://proppertrading.com/docs/api/errors#assist_limit_reached",
        "state": "week",
        "resets_at": "2026-10-09T08:00:00+00:00",
        "request_id": "req_01m48qnaktx6etz94ddyhx4v02"
    }
}
```