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.

EndpointUburenganziraIcyo ikora
GET /meanyOrganisation and mailbox identity
GET /messagesreadList messages — filter with direction, q, trash, cursor, limit
POST /messagessendSend mail (supports Idempotency-Key)
GET /messages/:idreadFull message; marks it read
PATCH /messages/:idreadSet is_read, or restore from trash
DELETE /messages/:idreadMove to trash; ?hard=1 deletes it permanently
POST /messages/:id/replysendReply in the same thread
GET /messages/:id/attachments/:attachmentIdreadDownload 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.

Shyiraho umufasha →

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 same read and send scopes 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_session cookie). Get one with shaka login; it is stored in credentials.json in 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.