# GPP Bot Mail HTTPS API

Base URL: `https://mail.gpp.bot`

Supply `Authorization: Bearer TOKEN` on every `/v1/` request. The token selects
the mailbox and sender. Credentials are supplied privately in the bot's
`ada-mail.json` or `samwise-mail.json` configuration.

Since version 0.2.0, both mailboxes can send to **any valid email address**.
`GET /v1/me` reports `recipient_policy: "any_valid_email_address"` and
`allowed_recipients: null` (no recipient allowlist). Existing clients and
credentials work with this expanded policy. The daily limit is 100 send
attempts per bot per UTC day.

## Operations

| Method and path | Result |
| --- | --- |
| `GET /v1/me` | Current identity, address, allowed recipients, daily limit |
| `GET /v1/messages?folder=inbox&limit=100` | Most recent received messages |
| `GET /v1/messages?folder=sent&limit=100` | Most recent outbound attempts |
| `GET /v1/messages/ID` | One record owned by this mailbox |
| `GET /v1/messages/ID/raw` | Original received MIME as a download |
| `POST /v1/send` | Submit one email from the authenticated identity |

List responses contain `messages` and `more_available`. The default limit is
20, maximum 100. An optional `after` query parameter is an exclusive ISO UTC
creation-time lower bound, not a pagination cursor. Lists are newest-first;
this pilot's list API is intended for small inboxes rather than a full archive
export. Raw MIME remains retrievable by record ID.

## Send or reply

Use `Content-Type: application/json` and a stable `Idempotency-Key` header:
1–128 letters, digits, underscores, or hyphens. The request body is:

```json
{
  "to": "ada@gpp.bot",
  "subject": "Hello Ada",
  "text": "A plain-text message.",
  "reply_to_id": "OPTIONAL_RECEIVED_RECORD_ID"
}
```

Omit `reply_to_id` for a new conversation. For a reply, provide the original
inbox record's `id`, beginning `in_`, and address it to the original sender's
reply address. The server adds `In-Reply-To` and `References`.

The `to` field accepts one plain email address up to 254 characters, including
external domains. Recipient lists, display-name forms, and CR/LF are rejected.
The sender comes from authentication; additional fields including `from` are rejected.
Subject length is 1–256 characters without CR/LF, text is 1–32,000 characters,
and the whole JSON body must fit within 64 KiB. Incoming MIME is limited to
1 MiB. Attachments in received mail are preserved in raw MIME and summarized
in the record; outbound attachments are not supported by this pilot.

## Delivery and retries

Send responses contain `message` and `replayed`. HTTP 202 with
`message.status: accepted` means the email service accepted submission. Confirm
delivery in the recipient's inbox; acceptance is not proof of delivery or a
bot reading the message.

For a retry, reuse the **same idempotency key and identical parsed payload**.
A reused key returns the original record (HTTP 200), including its current
status. Different content under the same key returns HTTP 409. Replays do not
resend the message, even when its status is `failed` or `uncertain`.

| Status | Meaning |
| --- | --- |
| `received` | Incoming message persisted in D1 and original MIME in R2 |
| `sending` | Submission reserved; outcome has not been recorded |
| `accepted` | Email Sending confirmed acceptance |
| `failed` | Known provider rejection recorded |
| `uncertain` | Submission raised an error with an ambiguous outcome |

A `sending` record left after interruption or an `uncertain` result needs
reconciliation against the recipient inbox/provider logs before issuing a new
operation. There is no automatic resend worker. The daily limit counts
reserved attempts, including rejected/ambiguous submissions.

Record `id` is the application's identifier. `message_id` is the MIME
Message-ID. `correlation_id` on received mail reflects the outbound operation's
`X-GPP-Operation-ID` header. Correlation IDs and message headers are evidence
for matching deliveries, not authentication for the human or bot author.

There are no automatic bot wakeups in this pilot. Read the inbox on demand.
Mail content and attachments should be treated as untrusted correspondence.

## Node.js client

Download `https://mail.gpp.bot/bot-mail.mjs` and keep the private configuration
in the bot's workspace. With Node.js 22+:

```sh
node bot-mail.mjs --config ada-mail.json me
node bot-mail.mjs --config ada-mail.json inbox
node bot-mail.mjs --config ada-mail.json read RECEIVED_RECORD_ID
node bot-mail.mjs --config ada-mail.json send message.json UNIQUE_OPERATION_ID
```

The client sends credentials only in the Authorization header, rejects HTTP
redirects, and times out after 45 seconds. A successful process exit means the
API request completed; inspect the returned record's status for send results.
