Dokumentacja
Publiczne API i serwer MCP
Buduj agentów, narzędzia CLI i integracje back-office na bazie qmailing. PLUS i wyżej odblokowuje uwierzytelnianie tokenem Bearer na każdym publicznym endpointcie oraz gotowy serwer MCP, który możesz podpiąć do Claude Desktop, Cursora lub dowolnego innego kompatybilnego klienta.
Pierwsze kroki
- 1. Przejdź na PLUS — publiczne API jest dostępne dla planów
PLUS,PRO,PRO_PLUSiPRO_MAX. Wywołania z FREE otrzymują402 Payment Requiredniezależnie od tokena. - 2. Wygeneruj token — wejdź w Ustawienia → Programiści, kliknij Nowy token, wybierz potrzebne scope'y i skopiuj wartość
qm_live_…, gdy się pojawi. Tokeny pokazują się tylko raz. - 3. Wykonaj pierwsze wywołanie:
curl https://qmailing.com/api/v1/pub/mailboxes \ -H "Authorization: Bearer qm_live_your_token_here"
Uwierzytelnianie
Każde wywołanie publicznego API zawiera nagłówek Authorization: Bearer qm_live_<…>. Format tokena ma prefiks (qm_live_ z 32 losowymi znakami), dzięki czemu wycieki w logach i repozytoriach są od razu widoczne.
Tokeny są przechowywane w postaci hasha (SHA-256). Tekst jawny jest pokazywany dokładnie raz w momencie wystawienia i nigdy więcej — jeśli go zgubisz, wygeneruj nowy. Każde żądanie wyzwala porównanie hasha w stałym czasie; last_used_at aktualizuje się przy sukcesie, dzięki czemu UI dla programistów może wskazywać przestarzałe poświadczenia.
apiAccess przy każdym podpisanym żądaniu — przy zmianie planu nie trzeba odwoływać tokenów pojedynczo.Scope'y
Każdy token zawiera jeden lub więcej ciągów scope. Endpointy deklarują, jakich scope'ów wymagają; brak wymaganego scope'a daje 403 InsufficientScope. Wieloznacznik (*) przyznaje wszystko — włączaj go tylko wtedy, gdy naprawdę go potrzebujesz.
| Scope | Pozwala |
|---|---|
| mailboxes:read | Listować i przeglądać skrzynki |
| mailboxes:write | Tworzyć, modyfikować i usuwać skrzynki |
| domains:read | Listować domeny i rekordy DNS |
| domains:write | Dodawać, weryfikować i usuwać własne domeny (w v1 tylko z FE) |
| email:read | Listować i przeglądać wiadomości ze skrzynek |
| email:send | Wysyłać pocztę wychodzącą przez API |
| webhooks:read | Listować endpointy webhooków |
| webhooks:write | Rejestrować, listować i odwoływać endpointy webhooków |
| * | Wieloznacznik — przyznaje wszystko. Stosuj oszczędnie. |
Skrzynki
Lista
GET /api/v1/pub/mailboxes
# scope: mailboxes:readPobierz jedną
GET /api/v1/pub/mailboxes/{id}
# scope: mailboxes:readUtwórz
POST /api/v1/pub/mailboxes
Content-Type: application/json
# scope: mailboxes:write
{
"localPart": "support",
"domain": "yourbrand.com",
"displayName": "Support team",
"forwardTo": "ops@yourbrand.com"
}Pole domain jest opcjonalne — pomiń je, a skrzynka zostanie utworzona w qmailing.com. Na własnej domenie domena musi być jednocześnie claimed ORAZ fullyVerified; nie gotowa domena zwraca 400, zamiast po cichu utworzyć nieroutowalną skrzynkę.
Domeny i DNS
Lista własnych domen
GET /api/v1/pub/domains
# scope: domains:readLista rekordów DNS
GET /api/v1/pub/domains/{id}/dns-records
# scope: domains:readZwraca pełną listę 8 rekordów (TXT challenge, MX, SPF, trzy CNAME DKIM, DMARC, opcjonalnie _amazonses) z polem status dla każdego wiersza: PENDING / FOUND / MISMATCH / NOT_REQUIRED. Agent może z niej skorzystać, by precyzyjnie powiedzieć użytkownikowi, co opublikować u jego dostawcy DNS.
Czytaj zawartość skrzynek i wysyłaj pocztę wychodzącą. Paginacja po folderach jest taka sama jak w wewnętrznym API frontendu — agenty migrujące z auth cookie-JWT na Bearer nie zobaczą zmian w kształcie odpowiedzi.
Listuj wg folderu
GET /api/v1/pub/email?folder=INBOX&offset=0&limit=25
# scope: email:read
# Optional query params:
# mailboxId filter to one mailbox (omit for all-mailboxes view)
# folder INBOX | SENT | DRAFTS | TRASH | STARRED | SPAM
# offset 0-based index, default 0
# limit 1..100, default 25folder domyślnie to INBOX; dozwolone wartości: INBOX, SENT, DRAFTS, TRASH, STARRED, SPAM. mailboxId jest opcjonalny — bez niego listowane są wszystkie skrzynki wywołującego. Paginacja przez offset + limit; limit jest ograniczony do zakresu 1–100 (domyślnie 25).
Pobierz jedną
GET /api/v1/pub/email/{id}
# scope: email:readZwraca pełny EmailDetailDto: treść HTML / tekst, listę etykiet, metadane załączników. Załączniki NIE są inline — pobierz każdy poniższym GET /{emailId}/attachments/{index}, który strumieniuje surowe bajty z oryginalną nazwą pliku i typem MIME.
Pobieranie załącznika
GET /api/v1/pub/email/{emailId}/attachments/{index}
# scope: email:read
# response: streamed bytes, Content-Type from the attachmentStrumieniuje surowe bajty załącznika (indeks od zera w liście załączników wiadomości). Identyczny kształt jak endpoint sesyjny: pass-through Content-Type, Content-Disposition wg RFC 6266 z oryginalną nazwą pliku (UTF-8 percent-encoded dla znaków Unicode). Agenci MCP wywołujący przez @qmailing/mcp-server używają narzędzia qmailing_get_attachment, które zwraca bajty zakodowane base64 (limit 5 MiB).
Wyślij
Endpoint wysyłki. Body multipart/form-data z jedną częścią JSON command (odbiorcy / temat / treść) oraz zerem lub więcej częściami-plikami attachments:
POST /api/v1/pub/email/send
Content-Type: multipart/form-data
# scope: email:send
--boundary
Content-Disposition: form-data; name="command"
Content-Type: application/json
{
"mailboxId": "11111111-2222-3333-4444-555555555555",
"to": ["alice@example.com"],
"cc": [],
"bcc": [],
"subject": "Order #1428 confirmed",
"bodyText": "Plain-text body",
"bodyHtml": "<p>Rich body</p>",
"replyToId": null
}
--boundary
Content-Disposition: form-data; name="attachments"; filename="receipt.pdf"
Content-Type: application/pdf
<binary bytes>
--boundary--Kształt JSON command:
{
"mailboxId": "uuid (required) — must be SES-verified",
"to": "string[] (required, max 50)",
"cc": "string[] (optional, max 50)",
"bcc": "string[] (optional, max 50)",
"subject": "string (max 998 chars)",
"bodyText": "string — plain-text body",
"bodyHtml": "string — HTML body",
"replyToId": "uuid | null — set for in-thread replies"
}Każde wywołanie liczy się do dziennego limitu wysyłki w planie, do limitu wysyłki SES dla skrzynki oraz do limitu pojemności użytkownika dla powstałej kopii w SENT. Odbiorcy są walidowani jako adresy RFC 5322; nieprawidłowe zwracają 400 z błędnym adresem. Skrzynka wskazana w mailboxId musi być zweryfikowana w SES — w przeciwnym razie 409 MailboxNotVerified z odsyłaczem do strony domen.
multipart/form-data, dzięki czemu format na łączu pozostaje identyczny.filename w części command jest opcjonalny — Node fetch automatycznie ustawia filename="blob" na każdym Blobie, klienci przeglądarkowi i Node mogą przekazywać command.json, a zgodne klienty curl --form mogą go pominąć. Wszystkie trzy formy są parsowane tak samo; liczy się tylko Content-Type: application/json na bajtach części.Scope'y: email:read dla listy i pobierania jednej, email:send dla wysyłki.
Webhooki
Zarejestruj endpoint HTTPS, a QMailing wykona POST podpisanego envelope JSON za każdym razem, gdy zostanie wyzwolone jedno z subskrybowanych zdarzeń. Dostarczanie działa — worker odpytuje kolejkę per-event co 10s, podpisuje każde żądanie HMAC-SHA256 i ponawia niepowodzenia z wykładniczym backoffem (1m / 5m / 15m / 1h / 6h), po czym parkuje wiersz w DLQ do ręcznego przeglądu.
Zarejestruj endpoint
POST /api/v1/pub/webhooks
Content-Type: application/json
# scope: webhooks:write
{
"url": "https://your-server.example.com/qmailing-events",
"label": "Production listener",
"events": ["email.received", "email.bounced", "domain.verified"]
}
# Response (201):
{
"endpoint": {
"id": "uuid",
"url": "https://your-server.example.com/qmailing-events",
"label": "Production listener",
"events": ["email.received", "email.bounced", "domain.verified"],
"secretPrefix": "whk_AbCd",
"createdAt": "2026-05-04T20:30:00Z",
"active": true
},
"plaintext": "whk_AbCd…<32 random chars>" // shown ONCE
}url musi być endpointem HTTPS (HTTP dozwolony tylko dla localhost w dev). Host jest sprawdzany względem allow-listy SSRF — adresy prywatne / loopback / cloud-metadata są odrzucane już przy rejestracji. Odpowiedź zawiera plaintext dokładnie raz; przechowuj go po stronie serwera i używaj do weryfikacji podpisów HMAC w przychodzących dostarczeniach.
Katalog zdarzeń
| Zdarzenie | Wyzwala się gdy |
|---|---|
| email.received | Przychodzący e-mail trafił do jednej z twoich skrzynek. |
| email.sent | Wychodzący e-mail został zaakceptowany przez SES. |
| email.bounced | Wychodzący e-mail zwrócił się (hard lub soft bounce). |
| domain.verified | Własna domena zakończyła pełną weryfikację DNS. |
Listuj endpointy
GET /api/v1/pub/webhooks
# scopes: webhooks:read OR webhooks:writeZwraca aktywne i odwołane endpointy, najnowsze pierwsze. Dostęp odczytu działa zarówno z webhooks:read, jak i webhooks:write — token inspektorski może w ten sposób uniknąć szerszego scope'a do zapisu.
Format dostarczenia
Każde dostarczenie to pojedynczy POST do Twojego endpointu z Content-Type: application/json. Body to envelope zdarzenia z polami event (nazwa zdarzenia), occurred_at (timestamp ISO) i blok data, którego kształt zależy od zdarzenia (patrz tabela wyżej). Trzy nagłówki niosą metadane routingu + weryfikacji:
POST https://your-server.example.com/qmailing-events
Content-Type: application/json
User-Agent: qmailing-webhook/1 (+https://qmailing.com)
X-Qmailing-Event: email.received
X-Qmailing-Delivery: 5f3b2a91-7c4d-4d52-9c3e-aa1bcd8a4f12
X-Qmailing-Signature: t=1685120800,v1=abc123def…
{
"event": "email.received",
"occurred_at": "2026-05-23T19:46:40Z",
"data": {
"email_id": "uuid",
"mailbox_id": "uuid",
"from": "alice@example.com",
"subject": "Hello",
"preview_text": "Just checking in…"
}
}Schemat podpisu
# Receiver side (Node example)
const crypto = require("node:crypto");
function verify(headers, body, secret) {
const sig = headers["x-qmailing-signature"]; // "t=1685120800,v1=abc123…"
const m = /t=(\d+),v1=([0-9a-f]+)/.exec(sig);
if (!m) return false;
const ts = Number(m[1]);
if (Math.abs(Date.now() / 1000 - ts) > 300) return false; // 5-min replay window
const expected = crypto
.createHmac("sha256", secret)
.update(`${ts}.${body}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(m[2]));
}Odrzuć żądanie, jeśli timestamp jest starszy niż 5 minut (okno anty-replay) lub jeśli przeliczony HMAC nie zgadza się z v1 hex — nasz podpisywacz BE wiernie odwzorowuje konwencje Stripe / GitHub, więc dowolna ze standardowych bibliotek weryfikacji zadziała od ręki.
Polityka ponawiania
Odpowiedzi inne niż 2xx (lub błędy transportu — DNS, TLS, odmowa połączenia, 10s timeout) inkrementują attempt_count i przesuwają next_attempt_at do kolejnego slotu w 1m / 5m / 15m / 1h / 6h. Po piątym niepowodzeniu dostarczenie przechodzi w FAILED (DLQ) — widoczne na /settings/developers, gdzie deweloper może je ręcznie ponowić. Idempotentność: każdy retry niesie ten sam UUID w X-Qmailing-Delivery — odbiorcy deduplikują po tym nagłówku.
Listuj ostatnie dostarczenia
GET /api/v1/pub/webhooks/{endpointId}/deliveries?limit=100
# scopes: webhooks:read OR webhooks:writeZwraca limit najnowszych dostarczeń pojedynczego endpointu (default 100, max 100), najnowsze pierwsze. Każdy wiersz zawiera status, licznik prób, ostatni kod HTTP, ostatni błąd i oryginalny payload JSON — zasila widok historii w panelu dewelopera.
Wyślij dostawę testową
POST /api/v1/pub/webhooks/{endpointId}/test
# scope: webhooks:write
# Response 202: WebhookDeliveryDto (enqueued, picked up next worker tick)Strzela syntetycznym zdarzeniem webhook.test w endpoint — ta sama ścieżka dispatchu co realne zdarzenia, przydatne do weryfikacji podpisu + TLS przed subskrypcją na żywy ruch.
Ponów dostarczenie
POST /api/v1/pub/webhooks/deliveries/{deliveryId}/retry
# scope: webhooks:write
# Response 200: WebhookDeliveryDto (status=PENDING, attempt=MAX-1)Ręcznie wstawia wiersz dostarczenia z powrotem do kolejki. Nie można ponowić wiersza już DELIVERED (409). Retry daje dokładnie jedną dodatkową próbę (licznik na MAX-1), więc trwale zepsuty endpoint nie zapętli się od powtarzanych kliknięć.
Usuń dostarczenie z historii
DELETE /api/v1/pub/webhooks/deliveries/{deliveryId}
# scope: webhooks:write
# Response: 204 No ContentHard-delete wiersza dostarczenia z tabeli historii. Log audytu compliance zapisuje samo usunięcie, więc traceability nie ginie, nawet gdy wiersz znika.
Odwołaj endpoint
DELETE /api/v1/pub/webhooks/{id}
# scope: webhooks:write
# Response: 204 No ContentIdempotentne — odwołanie już odwołanego endpointu kończy się cichym sukcesem. Wiersz jest zachowywany (z timestampem revoked-at), żeby logi audytu nie traciły referencji.
409 WebhookEndpointLimitExceeded / 400 InvalidWebhookEvent.Błędy
Każda odpowiedź spoza 2xx to ProblemDetail RFC-7807 ze stabilnym polem code, na które twój agent może reagować:
| HTTP | code | Kiedy |
|---|---|---|
| 400 | InvalidApiTokenScope | Nieznany ciąg scope w momencie tworzenia. |
| 400 | InvalidEmailAddress | Jeden z odbiorców nie przeszedł walidacji składni RFC 5322. |
| 400 | InvalidWebhookEvent | Lista zarejestrowanych zdarzeń zawiera nieznaną nazwę. |
| 401 | — | Brak nagłówka Bearer, jest niepoprawny, lub token został odwołany / wygasł. |
| 402 | PlanFeatureRequired | Wywołujący jest na planie, który nie obejmuje dostępu do API — wykonaj upgrade. |
| 403 | InsufficientScope | Token nie zawiera żadnego z wymaganych scope'ów. |
| 404 | ApiTokenNotFound | Cel odwołania nie istnieje lub należy do innego konta. |
| 409 | MailboxNotVerified | Domena skrzynki nadawczej nie jest jeszcze w pełni zweryfikowana — dokończ DNS w /settings/domains. |
| 409 | ApiTokenLimitExceeded | Konto osiągnęło maksymalną liczbę (20) aktywnych tokenów; najpierw odwołaj jeden. |
| 409 | WebhookEndpointLimitExceeded | Konto ma już 10 aktywnych endpointów webhooków; najpierw odwołaj jeden. |
| 429 | RateLimitExceeded | Token przekroczył 300 zapytań na minutę. Retry-After w sekundach. |
Limity zapytań
Każdy token ma swój własny kubełek 300 zapytań na minutę. Konfiguracje wieloagentowe (CLI + cron + wtyczka IDE) używające różnych tokenów dla tego samego konta działają niezależnie — nigdy nie konkurują o jeden wspólny kubełek. 429 RateLimitExceeded zawiera nagłówek Retry-After w sekundach.
Serwer MCP
Pakiet @qmailing/mcp-server działa lokalnie i udostępnia publiczne API jako narzędzia Model Context Protocol. Podepnij go do Claude Desktop, Cursora, Continue lub dowolnego klienta zgodnego z MCP.
@qmailing/mcp-server@0.2.0), jeśli nie chcesz automatycznych aktualizacji.{
"mcpServers": {
"qmailing": {
"command": "npx",
"args": ["-y", "@qmailing/mcp-server"],
"env": {
"QMAILING_API_TOKEN": "qm_live_your_token_here"
}
}
}
}Wbudowane narzędzia: qmailing_list_mailboxes, qmailing_get_mailbox, qmailing_create_mailbox, qmailing_list_domains, qmailing_get_dns_records, qmailing_list_emails, qmailing_get_email, qmailing_get_attachment, qmailing_send_email, qmailing_register_webhook, qmailing_list_webhooks, qmailing_delete_webhook. Katalog rośnie wraz z pojawianiem się nowych endpointów.