Files
mailflare/docs/api.md
T
Oday BakkourandClaude Fable 5.1 c6a38ae8e5 feat(mail): conversation view, forwarding with attachments, rich-text composer
Completes the three follow-ups from the threading PR.

Conversation view
- `GET /api/messages?group=thread` returns one row per conversation (its
  newest message matching the filter) via a latest-per-thread subquery,
  with `threadCount`, `threadUnread`, and `threadMessageIds` (the ids the
  row stands for within the current filter).
- Lists default to conversation view, remembered per browser, with a header
  toggle. Row and bulk actions, drags, and selection expand to the whole
  conversation; a row is bold while any message in it is unread. Drafts are
  never grouped.

Forwarding
- Forward builds a "---------- Forwarded message ---------" block from the
  original and creates a draft with `forwardOfMessageId`; the server copies
  the original's attachments onto the draft (new R2 objects, since keys are
  unique per row). The composer shows them as removable chips.
- `/api/send` accepts `draftId` and sends the draft's stored files with
  whatever was uploaded in the request. Deleting a draft now removes its R2
  objects too, and `DELETE /api/drafts/[id]/attachments/[attachmentId]`
  drops one file.

Rich-text composer
- contentEditable editor with bold/italic/underline/strikethrough, lists,
  quote, link (⌘K) and clear formatting; pasted content is flattened to text.
- The body is one HTML string; the text/plain part is derived from it, with
  bullets, numbering and "> " quote prefixes so text-only clients and the
  reader's collapse heuristics still work.
- Reply and forward quotes are HTML wrapped in a marked block that both the
  composer (•••) and the reader ("Quoted text") fold, so rich replies keep
  their formatting instead of falling back to text. Signature handling and
  draft round-trips work on the HTML.
- Shared `.email-body` styles restore list markers and blockquotes that
  Tailwind preflight strips from rendered mail.

Verified on a fresh local D1: grouped inbox collapses 4 messages to 1 row
with the right ids; a forward from an imported message carried its PDF onto
the draft, survived a draft reload, sent with the file, and joined the
original's thread; deleting the draft dropped its R2 object. tsc (11) and
eslint (96) unchanged from baseline.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGeWDVSkXtVEmDAN2AtB9h
2026-09-06 15:13:40 +03:00

65 lines
3.7 KiB
Markdown

# API and integrations
Mailflare exposes APIs for domain management and sending email. Authentication and mailbox permissions still apply to these routes.
## Domain management
Adding or removing a domain from Mailflare also updates Cloudflare Email Routing and sending resources.
| Mailflare route | Purpose |
| --- | --- |
| `GET /api/domains` | List connected domains |
| `POST /api/domains` | Connect a domain and configure Cloudflare |
| `GET /api/domains/[id]` | Get a connected domain |
| `DELETE /api/domains/[id]` | Remove a domain and clean up its Cloudflare resources |
| `GET /api/domains/[id]/dns` | View its routing and sending DNS status |
The hostname must be the apex of a zone available to the configured Cloudflare credentials, or a subdomain of that zone. Creating a mailbox also creates the Cloudflare Email Routing rule that delivers its address to the `mailflare` Worker.
## Sending email
Send email through `POST /api/v1/send`. `to`, `cc` and `bcc` accept either a comma-separated header string or an array of addresses; each entry may carry a display name (`"Maya Chen" <maya@example.net>`). A message can reach up to 50 recipients across the three fields. Attachments are optional and use Base64-encoded content:
```json
{
"from": "support@example.com",
"to": ["user@example.net", "\"Maya Chen\" <maya@example.net>"],
"cc": "ops@example.com",
"bcc": ["audit@example.com"],
"subject": "Report",
"text": "Attached.",
"attachments": [
{
"filename": "report.pdf",
"type": "application/pdf",
"contentBase64": "<base64 data>"
}
]
}
```
To send a reply that threads correctly in the recipient's client, pass the parent's Message-ID as `inReplyTo` and its chain as `references` (a header string or an array). Mailflare writes the `In-Reply-To` and `References` headers, files the sent copy in the same conversation, and stores `threadId`, `inReplyTo` and `references` on every message.
```json
{
"from": "support@example.com",
"to": "user@example.net",
"subject": "Re: Report",
"text": "Thanks, received.",
"inReplyTo": "<CAF1abc@mail.example.net>",
"references": ["<CAF0root@mail.example.net>", "<CAF1abc@mail.example.net>"]
}
```
`GET /api/messages/{id}/thread` (session auth) returns every stored message in the same conversation, oldest first, excluding drafts and trash. `GET /api/messages?group=thread` collapses a list to one row per conversation (its newest message matching the filter) and adds `threadCount`, `threadUnread` and `threadMessageIds`, the ids that row stands for within the current filter, so bulk actions can act on the whole conversation.
Messages composed in Mailflare are sent as HTML with a plain-text alternative derived from it. Quoted or forwarded content is wrapped in `<div class="mailflare-quote" data-mailflare-quote="1">` so the reader can fold it. `POST /api/drafts` accepts `forwardOfMessageId`, which copies that message's attachments onto the new draft; `DELETE /api/drafts/{id}/attachments/{attachmentId}` removes one, and `POST /api/send` with `draftId` sends the draft's stored files along with any uploaded in the request.
The dashboard composer accepts up to 10 attachments, with a 10 MB limit per file and a 20 MB combined limit. Attachment metadata is stored in D1 and file content is stored in R2. Downloads require access to the mailbox containing the message.
## Real-time updates
Mailflare uses a Durable Object WebSocket hub to notify connected users after an inbound message is stored. Mailbox owners, the domain administrator, and delegated users receive events for mailboxes they can access.
The `REALTIME` binding and its migration are declared in `wrangler.jsonc`. When a WebSocket is temporarily unavailable, the app retries the connection and uses a slower refresh until it recovers.