API Mailbox · v1
Développeurs
Envoyez, lisez et répondez aux e-mails de l’entreprise depuis votre propre serveur, et recevez un webhook signé à chaque nouveau message.
Démarrer
Chaque clé API appartient à une boîte mail de votre domaine, par exemple service@yourcompany.rw. Avec elle, votre serveur peut envoyer depuis cette adresse, lister et lire les messages reçus, et répondre dans le même fil.
L’administrateur Shaka de votre organisation — la boîte info@ de votre domaine — crée les clés dans Admin → Organisations → API keys. Vous n’avez pas besoin d’un compte administrateur : demandez une clé et gardez-la sur votre serveur.
- URL de base
https://mail.shaka.rw/api/v1- En-tête
Authorization: Bearer dm_live_…- Format
- JSON via HTTPS
Envoyez chaque requête à l’URL de base, avec la clé dans l’en-tête Authorization. Essayez :
curl
curl https://mail.shaka.rw/api/v1/me \
-H "Authorization: Bearer dm_live_YOUR_KEY" La référence technique ci-dessous est en anglais.
Authentification
Send the key as a Bearer token on every request. Keys start with dm_live_, the current prefix for live keys.
Header
Authorization: Bearer dm_live_… The secret is shown once, when the key is created. Keep it on your server: the API does not allow browser (CORS) requests, so never ship a key in web or app code.
Each key has one or more scopes:
send— send new mail and reply.read— list and open messages, mark them read, move them to trash and download attachments.webhook— receive a signed webhook when mail arrives.
Points d’accès
Paths are relative to https://mail.shaka.rw/api/v1.
| Point d’accès | Portée | Rôle |
|---|---|---|
GET /me | any | Organisation and mailbox identity |
GET /messages | read | List messages — filter with direction, q, trash, cursor, limit |
POST /messages | send | Send mail (supports Idempotency-Key) |
GET /messages/:id | read | Full message; marks it read |
PATCH /messages/:id | read | Set is_read, or restore from trash |
DELETE /messages/:id | read | Move to trash; ?hard=1 deletes it permanently |
POST /messages/:id/reply | send | Reply in the same thread |
GET /messages/:id/attachments/:attachmentId | read | Download or preview an attachment |
Exemples
Identity
Request
curl https://mail.shaka.rw/api/v1/me \
-H "Authorization: Bearer dm_live_YOUR_KEY" Response 200
Response
{
"organization": {
"id": "org_01h…",
"domain": "yourdomain.com",
"name": "Acme"
},
"mailbox": {
"id": "mbx_01h…",
"email": "noreply@yourdomain.com",
"name": "Noreply"
},
"scopes": ["send", "read", "webhook"],
"key": {
"id": "key_01h…",
"name": "Production",
"prefix": "dm_live_abc"
}
} List messages
Request
curl "https://mail.shaka.rw/api/v1/messages?direction=inbound&limit=50" \
-H "Authorization: Bearer dm_live_YOUR_KEY" Response 200
Response
{
"messages": [
{
"id": "eml_01h…",
"direction": "inbound",
"from_addr": "customer@example.com",
"to_addr": "noreply@yourdomain.com",
"subject": "Hello",
"is_read": false,
"is_draft": false,
"has_attachments": false,
"has_replied": false,
"created_at": "2026-08-03 12:00:00",
"updated_at": null
}
],
"nextCursor": null
} Send
Request
curl -X POST https://mail.shaka.rw/api/v1/messages \
-H "Authorization: Bearer dm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-42-receipt" \
-d '{
"to": "customer@example.com",
"subject": "Your verification code",
"text": "Your code is 482913."
}' Response 201 — sending again with the same Idempotency-Key returns 200 and "idempotent": true
Response
{
"ok": true,
"id": "eml_01h…"
} Get a message
Request
curl https://mail.shaka.rw/api/v1/messages/eml_01h… \
-H "Authorization: Bearer dm_live_YOUR_KEY" Response 200 — also marks the message read
Response
{
"message": {
"id": "eml_01h…",
"user_id": "mbx_01h…",
"direction": "inbound",
"from_addr": "customer@example.com",
"to_addr": "noreply@yourdomain.com",
"cc_addr": null,
"bcc_addr": null,
"subject": "Hello",
"body_text": "Hi there…",
"body_html": "<p>Hi there…</p>",
"message_id": "<abc@example.com>",
"in_reply_to": null,
"reply_to_email_id": null,
"is_read": 1,
"is_draft": 0,
"deleted_at": null,
"created_at": "2026-08-03 12:00:00",
"updated_at": null
},
"attachments": [
{
"id": "att_01h…",
"email_id": "eml_01h…",
"filename": "receipt.pdf",
"content_type": "application/pdf",
"size_bytes": 12044,
"created_at": "2026-08-03 12:00:01"
}
]
} Mark read or restore
Request
curl -X PATCH https://mail.shaka.rw/api/v1/messages/eml_01h… \
-H "Authorization: Bearer dm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "is_read": true }' Response 200 (also for { "restore": true })
Response
{
"ok": true
} Delete
Request
curl -X DELETE https://mail.shaka.rw/api/v1/messages/eml_01h… \
-H "Authorization: Bearer dm_live_YOUR_KEY" Response 200 — moves the message to trash; add ?hard=1 to delete it permanently
Response
{
"ok": true,
"permanent": false
} Reply
Request
curl -X POST https://mail.shaka.rw/api/v1/messages/eml_01h…/reply \
-H "Authorization: Bearer dm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Thanks for your note."
}' Response 201
Response
{
"ok": true,
"id": "eml_01h…"
} Download an attachment
Request
curl -OJ "https://mail.shaka.rw/api/v1/messages/eml_01h…/attachments/att_01h…?download=1" \
-H "Authorization: Bearer dm_live_YOUR_KEY" Response 200 — the raw file. Content-Type comes from the file; Content-Disposition is attachment with download=1, and may be inline for types a browser can preview.
Webhooks
When mail arrives at a key’s mailbox, keys with the webhook scope and a
webhook URL receive a signed POST with this body:
message.received
{
"type": "message.received",
"id": "<delivery_id>",
"created_at": "2026-08-03T00:00:00.000Z",
"data": {
"message_id": "<email_id>",
"from": "sender@example.com",
"to": "noreply@yourdomain.com",
"subject": "Hello",
"snippet": "…",
"has_attachments": false
}
} Check the X-Shaka-Signature header before you trust a delivery. Its value is sha256=<hex>: an HMAC-SHA256 of the raw request body, keyed with the
webhook secret shown once when the key was created.
Every delivery also carries the same value in X-Dime-Signature — the legacy
name, kept so receivers built before the rename keep working. New code should read X-Shaka-Signature.
Node.js
import crypto from 'node:crypto';
// rawBody: the exact bytes you received.
// header: request.headers['x-shaka-signature']
export function verifyShakaSignature(rawBody, header, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const received = Buffer.from(String(header ?? ''));
const wanted = Buffer.from(expected);
return received.length === wanted.length
&& crypto.timingSafeEqual(received, wanted);
} v1 delivers best-effort, with no retries. Every delivery is logged for audit.
Erreurs et limites
Errors come back as { "error": "…", "code": "…" } with the HTTP
status — for example 401 unauthorized for a missing or revoked key and 403 forbidden when the key lacks the scope.
Uptime monitors can call GET https://mail.shaka.rw/api/health without a key; it returns { "ok": true, "status": "healthy" }.
- Keys belong to one organisation and are bound to one mailbox on its domain.
- Browser (CORS) requests are not allowed — call the API from your server.
- Subjects can be up to 150 characters. The API does not apply the web app’s phishing-phrase checks.
- Idempotency keys on send are remembered for 24 hours.
Agents IA et MCP
Prompt pour agents de code
Copiez un prompt prêt à l’emploi qui couvre l’authentification, les portées, les points d’accès, le corps d’envoi, l’idempotence et la vérification des webhooks — puis collez-le dans votre agent de code.
Prompt IA
# Shaka Client Mailbox API — AI skill
You are integrating the **Shaka Client Mailbox API (v1)**: server-to-server access to one organisation service mailbox. Use this as the source of truth for auth, endpoints, bodies, webhooks, and limits.
## Base URL
`https://mail.shaka.rw`
- API root: `https://mail.shaka.rw/api/v1`
- Health (no auth): `GET https://mail.shaka.rw/api/health`
Response `200`:
```json
{ "ok": true, "status": "healthy" }
```
If DB is down → `503` `{ "ok": false, "status": "unhealthy" }`.
## Critical constraints
- **Server-to-server only.** Browser CORS is not enabled — never call this API from frontend JS. Keep keys and webhook secrets on the backend.
- Keys are **organisation-scoped** and bound to **one service mailbox** on that org’s domain.
- Platform admins create keys under **Admin → Organisations → API keys**. The secret is shown **once** at creation (`dm_live_…` prefix).
## Authentication
```
Authorization: Bearer dm_live_YOUR_KEY
```
Scopes (grant as needed):
| Scope | Purpose |
|-------|---------|
| `send` | Send mail and reply |
| `read` | List, get, patch, delete messages; download attachments |
| `webhook` | Receive signed inbound webhooks (requires webhook URL + secret at key creation) |
Missing scope → `403` with `{ "error": "Missing scope: …", "code": "forbidden" }`.
Invalid/missing auth → `401` `{ "error": "Unauthorized", "code": "unauthorized" }`.
## Error shape
All API errors:
```json
{ "error": "human-readable message", "code": "snake_case_code" }
```
Common codes: `unauthorized`, `forbidden`, `invalid_request`, `invalid_subject`, `not_found`, `send_failed`, `send_unconfigured`, `unavailable`, `trash_failed`, `delete_failed`, `restore_failed`.
## Endpoints
### `GET /api/v1/me` — any scope
```bash
curl https://mail.shaka.rw/api/v1/me \
-H "Authorization: Bearer dm_live_YOUR_KEY"
```
Response `200`:
```json
{
"organization": {
"id": "org_01h…",
"domain": "yourdomain.com",
"name": "Acme"
},
"mailbox": {
"id": "mbx_01h…",
"email": "noreply@yourdomain.com",
"name": "Noreply"
},
"scopes": ["send", "read", "webhook"],
"key": {
"id": "key_01h…",
"name": "Production",
"prefix": "dm_live_abc"
}
}
```
### `GET /api/v1/messages` — `read`
Query params:
| Param | Notes |
|-------|--------|
| `direction` | `inbound` \| `outbound` (optional) |
| `q` | Search string (optional) |
| `trash` | `1` for trash folder |
| `cursor` | Pagination cursor (previous page’s last `created_at`) |
| `limit` | 1–100, default 50 |
```bash
curl "https://mail.shaka.rw/api/v1/messages?direction=inbound&limit=50" \
-H "Authorization: Bearer dm_live_YOUR_KEY"
```
Response `200` (list rows use snake_case; pagination cursor is camelCase `nextCursor`):
```json
{
"messages": [
{
"id": "eml_01h…",
"direction": "inbound",
"from_addr": "customer@example.com",
"to_addr": "noreply@yourdomain.com",
"subject": "Hello",
"is_read": false,
"is_draft": false,
"has_attachments": false,
"has_replied": false,
"created_at": "2026-08-03 12:00:00",
"updated_at": null
}
],
"nextCursor": null
}
```
When a full page is returned, `nextCursor` is the last message’s `created_at` string; otherwise `null`.
### `POST /api/v1/messages` — `send`
Send outbound mail from the bound service mailbox.
**Headers (optional but recommended):**
- `Idempotency-Key: <string>` — remembered **24 hours**. Replay returns `200` `{ "ok": true, "id": "…", "idempotent": true }` instead of sending again.
- `Content-Type: application/json`
**Body:**
```json
{
"to": "customer@example.com",
"cc": "optional@example.com",
"bcc": "optional@example.com",
"subject": "Required, ≤ 150 characters",
"text": "Plain text body",
"html": "<p>Optional HTML</p>",
"attachments": [
{
"filename": "receipt.pdf",
"type": "application/pdf",
"content": "<base64>"
}
]
}
```
Rules:
- `to` and a body (`text` and/or `html`) are required.
- Subject is required, max **150** characters. API does **not** apply web-UI phishing phrase bans (`validateApiSubject` = length only).
- If only `html` is sent, plain text is derived by stripping tags; if only `text`, HTML is `\n` → `<br>\n`.
- Attachments: max **5** per email; each file ≤ **~30MB** raw; `content` is **base64**.
Response `201`:
```json
{ "ok": true, "id": "eml_01h…" }
```
### `GET /api/v1/messages/:id` — `read`
Full message + attachments metadata. Marks the message read (unless in trash). `404` if missing/draft.
```bash
curl https://mail.shaka.rw/api/v1/messages/eml_01h… \
-H "Authorization: Bearer dm_live_YOUR_KEY"
```
Response `200` (detail `message` mirrors DB row shape: `is_read` / `is_draft` are `0`|`1` integers):
```json
{
"message": {
"id": "eml_01h…",
"user_id": "mbx_01h…",
"direction": "inbound",
"from_addr": "customer@example.com",
"to_addr": "noreply@yourdomain.com",
"cc_addr": null,
"bcc_addr": null,
"subject": "Hello",
"body_text": "Hi there…",
"body_html": "<p>Hi there…</p>",
"message_id": "<abc@example.com>",
"in_reply_to": null,
"reply_to_email_id": null,
"is_read": 1,
"is_draft": 0,
"deleted_at": null,
"created_at": "2026-08-03 12:00:00",
"updated_at": null
},
"attachments": [
{
"id": "att_01h…",
"email_id": "eml_01h…",
"filename": "receipt.pdf",
"content_type": "application/pdf",
"size_bytes": 12044,
"created_at": "2026-08-03 12:00:01"
}
]
}
```
### `PATCH /api/v1/messages/:id` — `read`
Body (one action):
```json
{ "is_read": true }
```
or
```json
{ "restore": true }
```
```bash
curl -X PATCH https://mail.shaka.rw/api/v1/messages/eml_01h… \
-H "Authorization: Bearer dm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "is_read": true }'
```
Response `200`:
```json
{ "ok": true }
```
### `DELETE /api/v1/messages/:id` — `read`
- Soft-delete (trash) by default.
- Permanent: `?hard=1` or `?permanent=1`, or delete again while already in trash.
```bash
curl -X DELETE https://mail.shaka.rw/api/v1/messages/eml_01h… \
-H "Authorization: Bearer dm_live_YOUR_KEY"
```
Response `200` (soft):
```json
{ "ok": true, "permanent": false }
```
Hard delete → `{ "ok": true, "permanent": true }`.
### `POST /api/v1/messages/:id/reply` — `send`
Reply with threading (`In-Reply-To` / `References` from original). Subject becomes `Re: …` if needed. Recipient is the other party (inbound → original `from`, outbound → original `to`).
```json
{
"text": "Thanks for your note.",
"html": "<p>Optional</p>",
"cc": "…",
"bcc": "…",
"attachments": [{ "filename": "…", "type": "…", "content": "<base64>" }]
}
```
Body (`text` and/or `html`) required.
```bash
curl -X POST https://mail.shaka.rw/api/v1/messages/eml_01h…/reply \
-H "Authorization: Bearer dm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "text": "Thanks for your note." }'
```
Response `201`:
```json
{ "ok": true, "id": "eml_01h…" }
```
### `GET /api/v1/messages/:id/attachments/:attachmentId` — `read`
Binary download. `?download=1` forces attachment disposition; otherwise previewable types may be `inline`.
```bash
curl -OJ "https://mail.shaka.rw/api/v1/messages/eml_01h…/attachments/att_01h…?download=1" \
-H "Authorization: Bearer dm_live_YOUR_KEY"
```
Response `200` — raw file bytes with `Content-Type` from the attachment, `Content-Length`, and `Content-Disposition` (`attachment` or `inline`). Not JSON.
## Inbound webhooks
Keys with `webhook` scope and a configured HTTPS webhook URL receive a signed `POST` when mail arrives at the bound mailbox.
**Headers:**
- `Content-Type: application/json`
- `X-Shaka-Signature: sha256=<hmac_hex>` — HMAC-SHA256 of the **raw request body** using the webhook secret (shown once at key creation)
- `X-Dime-Signature` — the same value under the legacy header name, kept for receivers built before the rename. New code should read `X-Shaka-Signature`.
- `User-Agent: Shaka-Webhooks/1.0`
**Payload (body your endpoint receives):**
```json
{
"type": "message.received",
"id": "<delivery_id>",
"created_at": "2026-08-03T00:00:00.000Z",
"data": {
"message_id": "<email_id>",
"from": "sender@example.com",
"to": "noreply@yourdomain.com",
"subject": "Hello",
"snippet": "…",
"has_attachments": false
}
}
```
Verify `X-Shaka-Signature` with a timing-safe compare of `sha256=` + hex HMAC over the exact raw body. v1 is **best-effort with no retries**; deliveries are logged for audit. Fetch full content via `GET /api/v1/messages/:id` after verifying the webhook.
## Example: send mail
```bash
curl -X POST https://mail.shaka.rw/api/v1/messages \
-H "Authorization: Bearer dm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-42-receipt" \
-d '{
"to": "customer@example.com",
"subject": "Your verification code",
"text": "Your code is 482913."
}'
```
Response `201`:
```json
{ "ok": true, "id": "eml_01h…" }
```
Idempotent replay → `200` `{ "ok": true, "id": "eml_01h…", "idempotent": true }`.
## Example: verify webhook (Node)
```js
import crypto from 'node:crypto';
// rawBody: the exact bytes you received.
// header: request.headers['x-shaka-signature']
function verifyShakaSignature(rawBody, header, secret) {
const expected =
'sha256=' +
crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const received = Buffer.from(String(header ?? ''));
const wanted = Buffer.from(expected);
return received.length === wanted.length
&& crypto.timingSafeEqual(received, wanted);
}
```
## Integration checklist for coding agents
1. Store `dm_live_…` and webhook secret only in server env / secrets manager.
2. Call `https://mail.shaka.rw/api/v1/…` with Bearer auth; never from the browser.
3. Prefer `Idempotency-Key` on every send that might retry.
4. Keep subjects human and ≤ 150 chars.
5. On inbound webhook: verify `X-Shaka-Signature`, then `GET` the message by `data.message_id`.
6. Handle `{ error, code }` and HTTP status; treat `403` as missing scope configuration.
Connecter un assistant IA
Des assistants comme ChatGPT, Claude et Cursor peuvent utiliser une boîte Shaka via MCP. La page Plugins détaille la configuration de chacun, étape par étape.
MCP endpoint
URL
https://mail.shaka.rw/mcp Always use this URL — the apex host redirects. Clients that support custom MCP servers sign in with OAuth (dynamic client registration and PKCE): paste the URL, sign in with your Shaka account and allow mailbox access. Platform admins can also allow admin tools.
Clients without OAuth send Authorization: Bearer … on every request. The
token decides the tool set:
- Mailbox tools — an organisation API key
dm_live_…, with the samereadandsendscopes as the REST API. Tools:mail_me,mail_list_messages,mail_get_message,mail_send,mail_reply,mail_update_message,mail_trash_message,mail_get_attachment. - Admin tools — a platform admin session token (the same value as the
mail_sessioncookie). Get one withshaka login; it is stored incredentials.jsonin the shaka config folder. Tools cover organisations, mailboxes, API keys, search and reading any mailbox.
API keys never unlock admin tools, and admin session tokens never unlock the mailbox mail_* tools.
Config with sign-in (Cursor, Claude Desktop)
mcp.json
{
"mcpServers": {
"shaka": {
"url": "https://mail.shaka.rw/mcp"
}
}
} Config with an API key
A remote MCP config through mcp-remote, sending a Bearer header:
mcp.json
{
"mcpServers": {
"shaka": {
"command": "npx",
"args": [
"mcp-remote",
"https://mail.shaka.rw/mcp",
"--header",
"Authorization: Bearer dm_live_YOUR_KEY"
]
}
}
} Listed as rw.shaka/mail in the official MCP Registry. For admin tools, replace the Bearer value with your admin session token from shaka login.