Base URL
https://app.dmu.email/api/v1
Authentication
Bearer dmk_… API key
Rate limit
120 requests a minute per key

Overview

Instead of polling, give DMU Mail an address and it POSTs an event there when something happens: mail arrives, a send goes or fails, or a conversation is closed, assigned, labelled, snoozed or moved.

Making one

An administrator makes webhooks in Settings, then API keys, then Webhooks. Through the API, a key with webhooks:manage can make them too: see Manage webhooks. Each webhook lists the events it wants, and is told those and only those.

Who it acts as

A webhook acts as the person who made it, exactly as a key does. It is told only about conversations that person can see: not a colleague’s private mailbox, and a team mailbox only if they could open it. That is checked when the event is sent and again at every attempt, so access that ends stops the events too. If the person leaves or is suspended, their webhooks stop for good.

What arrives

Request

POST /your/endpoint
Content-Type: application/json
User-Agent: DMU-Mail-Webhooks/1.0 (+https://dmu.email)
DMU-Signature: t=1790000000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
DMU-Event: thread.assigned
DMU-Event-Id: 6f1c…
DMU-Delivery-Id: 0a2e…
DMU-Attempt: 1

Body

{
  "id": "6f1c…",
  "type": "thread.assigned",
  "created": "2026-09-29T10:14:03.211Z",
  "organisationId": "b054…",
  "data": {
    "threadId": "0315…",
    "mailboxId": "9e53…",
    "assigneeUserId": "…",
    "actor": { "userId": "…", "name": "Maria Kalogirou" }
  }
}

By default an event names things; it doesn’t carry them. data has the ids (threadId, messageId, mailboxId) and what changed. Ask the API for anything more, with Get a thread or Get a message. Your key is checked then, so a leaked webhook address, or a log that keeps request bodies, gives away that something happened and to which ids, not what anyone wrote.

A webhook can ask for message.* events to carry the message itself (includeContent), as data.message. It is read at the moment of sending, as the webhook’s person, and cleaned as Get a message cleans it. Each body is capped at 256 KiB (truncated is then true), inline images stay cid: references, and files are listed, never sent.

data.message

"message": {
  "id": "…", "threadId": "…", "mailboxId": "…", "subject": "Invoice INV-2026-0841",
  "from": { "address": "billing@acme.gr", "name": "Acme Billing" },
  "to": ["info@meridian.example"], "cc": [],
  "sentAt": "…", "status": null,
  "bodyHtml": "<p>…</p>", "bodyText": "…", "truncated": false,
  "attachments": [ { "id": "…", "filename": "invoice.pdf", "mimeType": "application/pdf", "size": 84213 } ]
}

Answering and retries

  • Answer with any 2xx within 10 seconds, and do the work afterwards. A receiver that processes before it answers will time out on a busy day.

  • Anything else is a failed attempt: a timeout, a refused connection, a bad certificate, a 3xx (redirects aren’t followed), a 4xx or a 5xx.

  • A failed event is tried again after 1 minute, 5 minutes, 30 minutes, 2 hours, 5 hours, 8 hours and 8 hours: eight attempts over about a day. Each attempt is signed afresh.

  • Delivery is at least once, so the same event can arrive twice. Handle each id once, and use created for order: events can arrive out of it.

  • When nothing has got through for about a day, DMU Mail turns the webhook off and emails every administrator. Switch it back on once it works; events from while it was off aren’t kept.

  • Deliveries are limited to 600 a minute per organisation. Beyond that they wait, without spending an attempt.

Addresses

https:// only, on the public internet. Addresses on a private network, with a user name or password in them, or on ports under 1024 other than 443 and 80 are refused, with a 400 url_* that says why. Every address the name resolves to is checked, and the connection goes to the address that was checked.