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.

FeldTypPflichtBeschreibung
namestringjaName der anfragenden Person oder Organisation (max. 200 Zeichen)
emailstringjaE-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

StatusnameUrsache
422missing_required_fieldname oder email fehlt
422invalid_emailUngü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>).

FeldTypPflichtBeschreibung
fromstringjaAbsender-Adresse
tostring | string[]jaEmpfänger (ein oder mehrere)
subjectstringjaBetreff
htmlstringja*HTML-Body (*mind. eines von html / text / react_template)
textstringneinPlaintext-Fallback
react_templatestringneinName eines Server-Templates (statt html)
react_propsobjectneinProps für das React-Template
reply_tostringneinReply-To-Adresse
ccstring | string[]neinCC-Empfänger
bccstring | string[]neinBCC-Empfänger
headersobjectneinZusätzliche Mail-Header als Key-Value-Objekt
attachmentsarrayneinDateianhänge (siehe unten)

Attachment-Objekt

Jedes Element im attachments-Array ist ein Objekt mit folgenden Feldern:

FeldTypPflichtBeschreibung
filenamestringjaDateiname inkl. Endung (z.B. rechnung.pdf)
contentstringja*Base64-kodierter Dateiinhalt (*entweder content oder path)
pathstringja*Öffentliche URL zum Download (*entweder content oder path)
contentTypestringneinMIME-Typ — wird automatisch aus der Dateiendung abgeleitet, wenn nicht angegeben
Maximale Gesamtgröße aller Anhänge: 25 MB (konfigurierbar). Private/interne URLs werden aus Sicherheitsgründen blockiert (SSRF-Schutz).
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

StatusnameUrsache
401missing_required_fieldKein Authorization-Header
401invalid_api_keyKey ungültig oder widerrufen
422missing_required_fieldfrom, to oder subject fehlt; kein Body angegeben
422invalid_emailUngültige Adresse in from, reply_to, cc oder bcc
400Attachment ohne filename, ohne content/path, oder attachments kein Array
400Gesamtgröße der Anhänge überschreitet das Limit
400URL nicht erreichbar oder blockiert (SSRF-Schutz)
403domain_not_allowedFrom-Domain nicht verifiziert oder nicht erlaubt
403domain_health_brokenDNS-Records der Domain sind defekt — Senden gesperrt bis Records repariert
500internal_server_errorFehler 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.

FeldTypPflichtBeschreibung
namestringjaTemplate-Name. Nur a–z A–Z 0–9 - _ erlaubt.
codestringjaQuellcode des Templates (JSX/TSX)
extstringneinjsx (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

StatusnameUrsache
422missing_required_fieldname oder code fehlt
422invalid_template_nameName enthält unerlaubte Zeichen
422invalid_extensionext 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

StatusnameUrsache
404not_foundTemplate existiert nicht
422invalid_template_nameName 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
                                     greenneeds_actionbroken

Domain Status

StatusBedeutungSenden möglich?
pendingDomain registriert, weder SPF noch DKIM verifiziertNein
partially_verifiedEntweder SPF oder DKIM verifiziert, aber nicht beideNein
verifiedSPF + DKIM verifiziert — Health Monitoring beginntJa (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.

Tipp: Du kannst jederzeit 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.

HealthBedeutungSenden?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

FeldTypBeschreibung
statusstringvalid, invalid oder missing
foundstring | nullDer tatsächlich gefundene DNS-Record (nur bei Fund)
expected_prefixstring | nullErste 60 Zeichen des erwarteten Werts (nur bei record_mismatch)
expected_hoststringDNS-Hostname, an dem der Record erwartet wird
reasonstring | nullMaschinenlesbarer Fehlergrund (nur bei invalid oder missing)
Hinweis: Das 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

ReasonBetrifftBedeutungLö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.

Hinweis: Domains mit 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.

FeldTypPflichtBeschreibung
namestringjaDomain-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

StatusnameUrsache
422missing_required_fieldname fehlt; Admin ohne account_id
422invalid_domainUngültiger Domain-Name
422public_domainPublic Email-Domains nicht erlaubt
422domain_limitMax. 10 Domains pro Account erreicht
409domain_existsDomain 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"
    }
  ]
}
FeldBeschreibung
statusVerifikationsstatus: pending, partially_verified, verified
healthHealth-Status: green, needs_action, broken oder null
checked_atZeitpunkt 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

StatusnameUrsache
404not_foundDomain 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

StatusnameUrsache
404not_foundDomain nicht gefunden oder gehört anderem Account
410verification_expiredDeadline abgelaufen — Domain wurde gelöscht, erneut registrieren
503dns_temporary_failureTemporärer DNS-Fehler — bitte später erneut versuchen

Troubleshooting

Häufige Probleme und deren Lösungen:

ProblemUrsacheLö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" }