Dispatch API v0.1
HTTP API zum Versand transaktionaler E-Mails via SMTP.
Authentifizierung
Alle Endpunkte außer POST /keys und GET /health erfordern einen Authorization-Header mit Bearer Token.
Authorization: Bearer dsp_...
Einen Key beantragen: POST /keys — nach Admin-Freigabe wird der Key per Mail zugestellt.
POST /keys — API Key beantragen
Stellt eine Anfrage für einen neuen API Key. Kein Auth erforderlich. Nach Eingang wird der Admin benachrichtigt und genehmigt den Key manuell. Der fertige Key wird per Mail zugestellt.
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
name | string | ja | Name der anfragenden Person oder Organisation (max. 200 Zeichen) |
email | string | ja | E-Mail-Adresse, an die der Key zugestellt wird |
curl -X POST https://dispatch.knerpi.de/keys \
-H "Content-Type: application/json" \
-d '{ "name": "Max Mustermann", "email": "max@example.com" }'
// Antwort (201 Created)
{
"id": "4e564315-f1c8-4004-a489-9af1e50e4872",
"status": "pending"
}
Fehlercodes
| Status | name | Ursache |
|---|---|---|
| 422 | missing_required_field | name oder email fehlt |
| 422 | invalid_email | Ungültige E-Mail-Adresse |
POST /emails — E-Mail senden
Sendet eine E-Mail. Entweder html, text oder react_template muss angegeben werden.
From-Domain: Ohne verifizierte Domains darf nur dispatch.knerpi.de als Absender-Domain verwendet werden. Mit verifizierten Domains muss eine davon genutzt werden — die Mail wird dann mit dem eigenen DKIM-Key signiert.
Das from-Feld unterstützt sowohl einfache Adressen (user@domain.com) als auch RFC-5322-Format ("Name" <user@domain.com>).
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
from | string | ja | Absender-Adresse |
to | string | string[] | ja | Empfänger (ein oder mehrere) |
subject | string | ja | Betreff |
html | string | ja* | HTML-Body (*mind. eines von html / text / react_template) |
text | string | nein | Plaintext-Fallback |
react_template | string | nein | Name eines Server-Templates (statt html) |
react_props | object | nein | Props für das React-Template |
reply_to | string | nein | Reply-To-Adresse |
cc | string | string[] | nein | CC-Empfänger |
bcc | string | string[] | nein | BCC-Empfänger |
headers | object | nein | Zusätzliche Mail-Header als Key-Value-Objekt |
attachments | array | nein | Dateianhänge (siehe unten) |
Attachment-Objekt
Jedes Element im attachments-Array ist ein Objekt mit folgenden Feldern:
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
filename | string | ja | Dateiname inkl. Endung (z.B. rechnung.pdf) |
content | string | ja* | Base64-kodierter Dateiinhalt (*entweder content oder path) |
path | string | ja* | Öffentliche URL zum Download (*entweder content oder path) |
contentType | string | nein | MIME-Typ — wird automatisch aus der Dateiendung abgeleitet, wenn nicht angegeben |
curl -X POST https://dispatch.knerpi.de/emails \
-H "Authorization: Bearer dsp_..." \
-H "Content-Type: application/json" \
-d '{
"from": "noreply@dispatch.knerpi.de",
"to": "empfaenger@example.com",
"subject": "Hallo Welt",
"html": "<p>Das ist meine erste Mail.</p>",
"text": "Das ist meine erste Mail."
}'
// Antwort (200 OK)
{ "id": "3c4a1b2e-...@dispatch.knerpi.de" }
// Mit React-Template
{
"from": "noreply@dispatch.knerpi.de",
"to": "empfaenger@example.com",
"subject": "Willkommen",
"react_template": "welcome",
"react_props": { "name": "Arne" }
}
// Mit Attachment (Base64)
{
"from": "noreply@dispatch.knerpi.de",
"to": "empfaenger@example.com",
"subject": "Ihre Rechnung",
"html": "<p>Im Anhang finden Sie Ihre Rechnung.</p>",
"attachments": [
{
"filename": "rechnung.pdf",
"content": "JVBERi0xLjQg..."
}
]
}
// Mit Attachment (URL)
{
"from": "noreply@dispatch.knerpi.de",
"to": "empfaenger@example.com",
"subject": "Dokument",
"html": "<p>Siehe Anhang.</p>",
"attachments": [
{
"filename": "bericht.pdf",
"path": "https://example.com/files/bericht.pdf"
}
]
}
Fehlercodes
| Status | name | Ursache |
|---|---|---|
| 401 | missing_required_field | Kein Authorization-Header |
| 401 | invalid_api_key | Key ungültig oder widerrufen |
| 422 | missing_required_field | from, to oder subject fehlt; kein Body angegeben |
| 422 | invalid_email | Ungültige Adresse in from, reply_to, cc oder bcc |
| 400 | — | Attachment ohne filename, ohne content/path, oder attachments kein Array |
| 400 | — | Gesamtgröße der Anhänge überschreitet das Limit |
| 400 | — | URL nicht erreichbar oder blockiert (SSRF-Schutz) |
| 403 | domain_not_allowed | From-Domain nicht verifiziert oder nicht erlaubt |
| 403 | domain_health_broken | DNS-Records der Domain sind defekt — Senden gesperrt bis Records repariert |
| 500 | internal_server_error | Fehler beim Versand |
GET /templates — Templates auflisten
Gibt alle auf dem Server verfügbaren React-Templates zurück.
curl https://dispatch.knerpi.de/templates \
-H "Authorization: Bearer dsp_..."
// Antwort (200 OK)
{ "templates": ["welcome"] }
POST /templates — Template hochladen
Lädt ein React-Template (JSX oder TSX) auf den Server. Überschreibt ein bestehendes Template gleichen Namens. Das Template ist sofort nutzbar — kein Neustart erforderlich.
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
name | string | ja | Template-Name. Nur a–z A–Z 0–9 - _ erlaubt. |
code | string | ja | Quellcode des Templates (JSX/TSX) |
ext | string | nein | jsx (Standard) oder tsx |
curl -X POST https://dispatch.knerpi.de/templates \
-H "Authorization: Bearer dsp_..." \
-H "Content-Type: application/json" \
-d '{
"name": "bestellung",
"ext": "jsx",
"code": "import { Html, Text } from \"@react-email/components\";\nexport default function({ name }) {\n return <Html><Text>Hallo {name}</Text></Html>;\n}"
}'
// Antwort (200 OK)
{ "name": "bestellung", "path": "templates/bestellung.jsx" }
Fehlercodes
| Status | name | Ursache |
|---|---|---|
| 422 | missing_required_field | name oder code fehlt |
| 422 | invalid_template_name | Name enthält unerlaubte Zeichen |
| 422 | invalid_extension | ext ist nicht jsx oder tsx |
DELETE /templates/:name — Template löschen
Löscht ein Template vom Server. Der Name wird als URL-Parameter übergeben.
curl -X DELETE https://dispatch.knerpi.de/templates/bestellung \
-H "Authorization: Bearer dsp_..."
// Antwort (200 OK)
{ "deleted": "bestellung" }
Fehlercodes
| Status | name | Ursache |
|---|---|---|
| 404 | not_found | Template existiert nicht |
| 422 | invalid_template_name | Name enthält unerlaubte Zeichen |
Sending Domains
Eigene Domains registrieren, um E-Mails mit benutzerdefiniertem DKIM zu versenden. Ohne verifizierte Domains kann nur die Shared Domain dispatch.knerpi.de als Absender genutzt werden.
Dispatch prüft die DNS-Records deiner Domains nicht nur einmalig bei der Registrierung, sondern überwacht sie kontinuierlich (Dispatch Radar). So wird sichergestellt, dass deine Mails immer korrekt signiert und zustellbar sind.
Domain-Lifecycle
Jede Domain durchläuft nach der Registrierung einen Verifikationsprozess. Erst nach erfolgreicher Verifikation kann über die Domain gesendet werden.
Registrierung
│
▼
pending ────────────────────────────────────── 72h Deadline
│ │
▼ ▼
partially_verified (SPF oder DKIM OK) Domain gelöscht
│
▼
verified (SPF + DKIM OK) ──→ Health Monitoring aktiv
green ↔ needs_action ↔ broken
Domain Status
| Status | Bedeutung | Senden möglich? |
|---|---|---|
pending | Domain registriert, weder SPF noch DKIM verifiziert | Nein |
partially_verified | Entweder SPF oder DKIM verifiziert, aber nicht beide | Nein |
verified | SPF + DKIM verifiziert — Health Monitoring beginnt | Ja (abhängig von Health) |
Onboarding-Ablauf: Nach der Registrierung müssen drei DNS-Records (SPF, DKIM, DMARC) innerhalb von 72 Stunden gesetzt werden. Dispatch prüft automatisch alle 15 Minuten. Sobald SPF und DKIM korrekt sind, wird die Domain verified. Domains die nach Ablauf der Deadline nicht verifiziert sind, werden automatisch gelöscht.
POST /domains/:id/verify aufrufen, um die Prüfung sofort auszulösen, statt auf den nächsten automatischen Check zu warten.Health Monitoring
Nach erfolgreicher Verifikation überwacht Dispatch die DNS-Records verifizierter Domains kontinuierlich. Bei Änderungen oder Problemen wird der Health-Status aktualisiert.
| Health | Bedeutung | Senden? | Was tun? |
|---|---|---|---|
green |
SPF, DKIM und DMARC korrekt | Ja | Nichts — alles in Ordnung |
needs_action |
SPF + DKIM OK, aber DMARC fehlt oder zu schwach | Ja | DMARC-Record mit p=quarantine oder p=reject setzen |
broken |
SPF oder DKIM fehlerhaft (nach 2 aufeinanderfolgenden Checks) | Nein | DNS-Records prüfen und korrigieren, dann /verify aufrufen |
null |
Noch kein Health-Check durchgeführt | Ja | /verify aufrufen oder auf automatischen Check warten |
Wie funktioniert die Erkennung? Wenn bei einem Check SPF oder DKIM nicht korrekt sind, setzt Dispatch den Health-Status zunächst auf needs_action. Erst wenn auch der nächste automatische Check das Problem bestätigt, wechselt der Status zu broken. So werden kurzzeitige DNS-Probleme toleriert.
Sobald SPF + DKIM wieder korrekt sind, wird broken sofort aufgehoben — entweder beim nächsten automatischen Check oder durch manuelles Auslösen via /verify.
Check-Ergebnisse
Jeder Check prüft drei DNS-Records: SPF, DKIM und DMARC. Die Ergebnisse werden im checks-Objekt zurückgegeben (in GET /domains/:id und POST /domains/:id/verify).
Wichtig — dns_records vs checks:
dns_records zeigt, welche Records gesetzt werden sollen (Soll-Werte). checks zeigt, was bei der letzten Prüfung tatsächlich im DNS gefunden wurde (Ist-Zustand). Vergleiche beide, um Abweichungen zu erkennen.
Felder pro Check
| Feld | Typ | Beschreibung |
|---|---|---|
status | string | valid, invalid oder missing |
found | string | null | Der tatsächlich gefundene DNS-Record (nur bei Fund) |
expected_prefix | string | null | Erste 60 Zeichen des erwarteten Werts (nur bei record_mismatch) |
expected_host | string | DNS-Hostname, an dem der Record erwartet wird |
reason | string | null | Maschinenlesbarer Fehlergrund (nur bei invalid oder missing) |
checks-Objekt ist erst vorhanden, nachdem mindestens eine Prüfung stattgefunden hat. Frisch registrierte Domains ohne bisherigen Check haben kein checks-Feld.Beispiel: Check mit Fehler
// SPF korrekt, DKIM stimmt nicht, DMARC fehlt
{
"checks": {
"spf": {
"status": "valid",
"found": "v=spf1 include:dispatch.knerpi.de -all",
"expected_host": "example.com"
},
"dkim": {
"status": "invalid",
"found": "v=DKIM1; k=rsa; p=ALTE_FALSCHE_DATEN...",
"expected_prefix": "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMI...",
"expected_host": "dispatch._domainkey.example.com",
"reason": "record_mismatch"
},
"dmarc": {
"status": "missing",
"expected_host": "_dmarc.example.com",
"reason": "no_record_found"
}
}
}
Reason-Referenz
| Reason | Betrifft | Bedeutung | Lösung |
|---|---|---|---|
no_record_found |
SPF, DKIM, DMARC | Kein DNS-Record am erwarteten Host gefunden | TXT-Record anlegen — den erwarteten Host und Wert findest du in dns_records |
record_mismatch |
SPF, DKIM | Record vorhanden, aber der Wert stimmt nicht | Vergleiche found mit expected_prefix und korrigiere den Record-Wert |
multiple_records |
SPF | Mehrere SPF-Records gefunden (nur einer erlaubt per RFC) | Doppelte SPF-Records entfernen, nur einen behalten |
policy_too_weak |
DMARC | DMARC-Policy ist p=none |
Policy auf p=quarantine oder p=reject ändern |
syntax_error |
DMARC | DMARC-Record vorhanden, aber ungültig | Record muss mit v=DMARC1 beginnen und eine gültige p= Policy enthalten |
Sending Enforcement
Dispatch blockiert den E-Mail-Versand über Domains mit health: broken. Anfragen an POST /emails mit einer betroffenen Absender-Domain erhalten:
// 403 Forbidden
{
"name": "domain_health_broken",
"message": "Sending blocked: domain 'example.com' has broken DNS records."
}
So wird die Sperre aufgehoben:
1. Prüfe die fehlerhaften Records via GET /domains/:id (siehe checks).
2. Korrigiere die DNS-Records bei deinem DNS-Provider.
3. Rufe POST /domains/:id/verify auf — bei erfolgreicher Prüfung wird der Health-Status sofort aktualisiert und das Senden wieder freigegeben.
health: needs_action oder health: green sind vom Senden nicht betroffen. Nur broken blockiert den Versand.POST /domains — Domain registrieren
Registriert eine neue Sending Domain. Generiert DKIM-Schlüsselpaar und gibt die erforderlichen DNS-Records zurück. Max. 10 Domains pro Account.
Die Domain startet mit status: pending und health: null. Setze die drei DNS-Records (SPF, DKIM, DMARC) innerhalb von 72 Stunden, dann löse die Verifikation via POST /domains/:id/verify aus oder warte auf den automatischen Check.
Admin: Muss ?account_id=... als Query-Parameter angeben, um die Domain einem Account zuzuordnen.
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
name | string | ja | Domain-Name (z.B. example.com). Keine Public-Domains (gmail.com etc.). |
curl -X POST https://dispatch.knerpi.de/domains \
-H "Authorization: Bearer dsp_..." \
-H "Content-Type: application/json" \
-d '{ "name": "example.com" }'
// Antwort (201 Created)
{
"id": "a1b2c3d4-...",
"name": "example.com",
"status": "pending",
"verification_deadline": "2026-03-01T12:00:00.000Z",
"dns_records": {
"spf": { "type": "TXT", "host": "example.com", "value": "v=spf1 ip4:168.119.233.28 -all" },
"dkim": { "type": "TXT", "host": "dispatch._domainkey.example.com", "value": "v=DKIM1; k=rsa; p=MIIBIjAN..." },
"dmarc": { "type": "TXT", "host": "_dmarc.example.com", "value": "v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com;" }
}
}
Fehlercodes
| Status | name | Ursache |
|---|---|---|
| 422 | missing_required_field | name fehlt; Admin ohne account_id |
| 422 | invalid_domain | Ungültiger Domain-Name |
| 422 | public_domain | Public Email-Domains nicht erlaubt |
| 422 | domain_limit | Max. 10 Domains pro Account erreicht |
| 409 | domain_exists | Domain ist bereits registriert |
GET /domains — Domains auflisten
Gibt alle Domains des Accounts zurück. Mit health und checked_at siehst du auf einen Blick, welche Domains Aufmerksamkeit brauchen.
curl https://dispatch.knerpi.de/domains \
-H "Authorization: Bearer dsp_..."
// Antwort (200 OK)
{
"domains": [
{
"id": "a1b2c3d4-...",
"name": "example.com",
"status": "verified",
"health": "green",
"checked_at": "2026-03-01T18:00:00.000Z",
"created_at": "2026-02-26T10:30:00.000Z"
},
{
"id": "e5f6a7b8-...",
"name": "shop.example.com",
"status": "verified",
"health": "broken",
"checked_at": "2026-03-01T18:00:00.000Z",
"created_at": "2026-02-27T08:15:00.000Z"
}
]
}
| Feld | Beschreibung |
|---|---|
status | Verifikationsstatus: pending, partially_verified, verified |
health | Health-Status: green, needs_action, broken oder null |
checked_at | Zeitpunkt der letzten DNS-Prüfung (ISO 8601) oder null |
GET /domains/:id — Domain-Details
Gibt vollständige Details einer Domain zurück: DNS-Soll-Werte (dns_records), Prüfergebnisse (checks), Health-Status und Verifikationsstatus.
curl https://dispatch.knerpi.de/domains/a1b2c3d4-... \
-H "Authorization: Bearer dsp_..."
// Antwort (200 OK)
{
"id": "a1b2c3d4-...",
"name": "example.com",
"status": "verified",
"health": "green",
"checked_at": "2026-03-01T18:00:00.000Z",
"verification_deadline": "2026-03-01T12:00:00.000Z",
"created_at": "2026-02-26T10:30:00.000Z",
"dns_records": {
"spf": { "type": "TXT", "host": "example.com", "value": "v=spf1 include:dispatch.knerpi.de -all" },
"dkim": { "type": "TXT", "host": "dispatch._domainkey.example.com", "value": "v=DKIM1; k=rsa; p=MIIBIjAN..." },
"dmarc": { "type": "TXT", "host": "_dmarc.example.com", "value": "v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com;" }
},
"checks": {
"spf": {
"status": "valid",
"found": "v=spf1 include:dispatch.knerpi.de -all",
"expected_host": "example.com"
},
"dkim": {
"status": "valid",
"found": "v=DKIM1; k=rsa; p=MIIBIjAN...",
"expected_host": "dispatch._domainkey.example.com"
},
"dmarc": {
"status": "valid",
"found": "v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com;",
"expected_host": "_dmarc.example.com"
}
}
}
dns_records — Soll-Werte: die DNS-Records, die bei deinem Provider gesetzt werden müssen. Diese bleiben immer gleich.
checks — Ist-Zustand: was bei der letzten Prüfung tatsächlich im DNS gefunden wurde. Nur vorhanden, wenn mindestens eine Prüfung stattgefunden hat.
Fehlercodes
| Status | name | Ursache |
|---|---|---|
| 404 | not_found | Domain nicht gefunden oder gehört anderem Account |
DELETE /domains/:id — Domain löschen
Löscht eine registrierte Domain. Mails können danach nicht mehr über diese Domain versendet werden.
curl -X DELETE https://dispatch.knerpi.de/domains/a1b2c3d4-... \
-H "Authorization: Bearer dsp_..."
// Antwort (200 OK)
{ "deleted": "example.com" }
POST /domains/:id/verify — Verifikation auslösen
Prüft sofort, ob die DNS-Records korrekt gesetzt sind (SPF, DKIM, DMARC). Kann sowohl für Onboarding als auch für Health-Checks bereits verifizierter Domains verwendet werden.
Wenn die Verifikations-Deadline abgelaufen ist (nur für nicht-verifizierte Domains), wird die Domain automatisch gelöscht und muss neu registriert werden.
curl -X POST https://dispatch.knerpi.de/domains/a1b2c3d4-.../verify \
-H "Authorization: Bearer dsp_..."
// Antwort (200 OK)
{
"id": "a1b2c3d4-...",
"name": "example.com",
"status": "verified",
"health": "green",
"checks": {
"spf": { "status": "valid", "found": "v=spf1 include:dispatch.knerpi.de -all", "expected_host": "example.com" },
"dkim": { "status": "valid", "found": "v=DKIM1; k=rsa; p=MIIBIjAN...", "expected_host": "dispatch._domainkey.example.com" },
"dmarc": { "status": "valid", "found": "v=DMARC1; p=quarantine; ...", "expected_host": "_dmarc.example.com" }
}
}
Fehlercodes
| Status | name | Ursache |
|---|---|---|
| 404 | not_found | Domain nicht gefunden oder gehört anderem Account |
| 410 | verification_expired | Deadline abgelaufen — Domain wurde gelöscht, erneut registrieren |
| 503 | dns_temporary_failure | Temporärer DNS-Fehler — bitte später erneut versuchen |
Troubleshooting
Häufige Probleme und deren Lösungen:
| Problem | Ursache | Lösung |
|---|---|---|
Domain bleibt pending |
DNS-Records noch nicht gesetzt oder DNS-Propagation läuft noch | Records beim Provider setzen, einige Minuten warten, dann POST /domains/:id/verify aufrufen |
Domain ist partially_verified |
Nur SPF oder nur DKIM korrekt, der andere Record fehlt oder ist falsch | checks via GET /domains/:id prüfen — der fehlende Record hat status: missing oder invalid |
health: needs_action |
DMARC-Record fehlt oder hat eine zu schwache Policy (p=none) |
DMARC-Record mit mindestens p=quarantine setzen (siehe dns_records.dmarc) |
health: broken obwohl Records stimmen |
DNS-Propagation nach Änderung dauert noch an | POST /domains/:id/verify aufrufen — bei Erfolg wird Health sofort aktualisiert |
403 domain_health_broken beim Senden |
SPF oder DKIM der Absender-Domain sind defekt | DNS-Records korrigieren, dann /verify aufrufen um die Sperre aufzuheben |
503 dns_temporary_failure bei Verify |
DNS-Server vorübergehend nicht erreichbar | Einige Minuten warten und erneut versuchen — kein Problem mit deinen Records |
checks fehlt in der Response |
Domain wurde noch nie geprüft | POST /domains/:id/verify aufrufen um den ersten Check auszulösen |
GET /health — Status prüfen
Gibt den aktuellen Status des Servers zurück. Kein Auth erforderlich.
curl https://dispatch.knerpi.de/health
// Antwort (200 OK)
{ "status": "ok" }