API y'agasanduku · v1
Abateganya
Ohereza, soma kandi usubize imeri z'ikigo uhereye kuri seriveri yawe, kandi wakire webhook isinyiye igihe ubutumwa bugeze.
Tangira
Buri rufunguzo rwa API ni urw'agasanduku kamwe kuri domain y'ikigo cyawe, urugero service@yourcompany.rw. Narwo, seriveri yawe ishobora kohereza imeri iturutse kuri iyo aderesi, kureba no gusoma ubutumwa bwinjiye, no gusubiza mu kiganiro kimwe.
Umuyobozi wa Shaka mu kigo cyanyu — agasanduku ka info@ kuri domain yanyu — ni we ukora imfunguzo muri Admin → Organisations → API keys. Ntukeneye konti y'ubuyobozi: saba urufunguzo, urubike kuri seriveri yawe.
- URL y'ibanze
https://mail.shaka.rw/api/v1- Umutwe
Authorization: Bearer dm_live_…- Imiterere
- JSON kuri HTTPS
Ohereza buri cyifuzo kuri URL y'ibanze, urufunguzo rushyizwe mu mutwe wa Authorization. Gerageza:
curl
curl https://mail.shaka.rw/api/v1/me \
-H "Authorization: Bearer dm_live_YOUR_KEY" Ibisobanuro bya tekiniki bikurikira biri mu Cyongereza.
Kwemeza
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.
Endpoints
Paths are relative to https://mail.shaka.rw/api/v1.
| Endpoint | Uburenganzira | Icyo ikora |
|---|---|---|
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 |
Ingero
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.
Amakosa n'imbibi
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.
Abafasha ba AI na MCP
Amabwiriza y'abafasha bandika code
Kopiya amabwiriza ateguye asobanura kwemeza, uburenganzira, endpoints, ibyoherezwa, idempotency no kugenzura webhooks — hanyuma uyashyire mu mufasha wawe wandika code.
Amabwiriza ya AI
# 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.
Huza umufasha wa AI
Abafasha nka ChatGPT, Claude na Cursor bashobora gukoresha agasanduku ka Shaka binyuze kuri MCP. Paji ya Plugins isobanura intambwe ku yindi uko buri wese ashyirwaho.
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.