API für Agenturen Für Agenturen
Zustellstatus einzelner Mails, Bounces und Verbrauch aus der eigenen Anwendung abfragen, mit API-Schlüssel, lesend.
Mit der MailBridge-API fragst du aus deiner eigenen Anwendung ab, was mit einer versendeten Mail passiert ist: zugestellt, verzögert oder unzustellbar, inklusive der wörtlichen Antwort des Empfänger-Servers. Dazu kommen Bounce-Listen und der Verbrauch je Endkunde.
Die API ist lesend und für Server-zu-Server-Aufrufe gedacht. Ein API-Schlüssel gehört nie in eine Webseite oder App, die beim Nutzer läuft.
Schlüssel erstellen
Einstellungen → API-Schlüssel → Schlüssel erstellen. Vergib eine Bezeichnung, optional einen Endkunden (dann sieht der Schlüssel nur dessen Daten) und optional ein Ablaufdatum.
Der Schlüssel wird genau einmal angezeigt. Wir speichern nur eine Prüfsumme davon. Verloren heißt neu erstellen. Ein Schlüssel lässt sich jederzeit widerrufen; Anfragen damit werden dann sofort abgelehnt.
Authentifizierung
Jede Anfrage trägt den Schlüssel im Authorization-Header:
curl -H "Authorization: Bearer mbk_live_…" \
https://my.mailbridge.email/api/v1/me
Alternativ X-API-Key: mbk_live_…. Der erste Aufruf sollte immer /api/v1/me sein. Die Antwort sagt dir, welchem Konto der Schlüssel gehört, ob er auf einen Endkunden beschränkt ist, wie hoch dein Ratenlimit ist und wie viele Tage Versandhistorie verfügbar sind.
Eine Mail nachverfolgen
Damit du eine einzelne Mail später wiederfindest, brauchst du ihre Queue-ID. Die steht in der SMTP-Antwort, die dein Mailversand beim Absenden zurückbekommt: 250 2.0.0 Ok: queued as 4ZxK7p2Yz3.
Nodemailer gibt sie in info.response zurück:
const info = await transporter.sendMail({ /* … */ })
const queueId = info.response.match(/queued as (\S+)/)?.[1]
// queueId zusammen mit der Empfängeradresse in der eigenen DB speichern
Mit PHPMailer liest du sie aus $mail->getSMTPInstance()->getLastReply(), in anderen Bibliotheken heißt das Feld meist response oder smtp_reply.
Danach:
curl -H "Authorization: Bearer $KEY" \
https://my.mailbridge.email/api/v1/messages/4ZxK7p2Yz3
{
"queue_id": "4ZxK7p2Yz3",
"status": "delivered",
"sender": "info@baeckerei-brotzeit.de",
"recipients": ["kunde@example.com"],
"first_seen": "2026-07-31T09:12:04Z",
"last_seen": "2026-07-31T09:41:18Z",
"attempts": [
{ "status": "deferred", "occurred_at": "2026-07-31T09:12:04Z", "smtp_response": "451 4.7.1 try again later",
"reason": { "code": "4.7.1", "permanent": false, "provider_block": null, "key": "greylisted_throttled" } },
{ "status": "sent", "occurred_at": "2026-07-31T09:41:18Z", "target_server": "mx01.example.com",
"reason": null }
]
}
Warum mehrere attempts? Der Versandserver protokolliert jeden Zustellversuch je Empfänger. Eine Mail, die zuerst verzögert und dann zugestellt wurde, hat zwei Einträge. status ist das Ergebnis über alle Versuche:
status |
Bedeutung |
|---|---|
delivered |
Mindestens ein Versuch war erfolgreich, die Mail ist raus. |
deferred |
Noch in Zustellung, wird automatisch erneut versucht. |
bounced |
Dauerhaft unzustellbar (5.x.x). |
rejected |
Vom Empfänger-Server abgewiesen. |
auth_failed |
Anmeldung am Versandserver fehlgeschlagen. |
Das Feld reason
Bei jedem Versuch steht in reason, warum er so ausgegangen ist, außer wenn es geklappt hat:
"reason": null
null heißt: kein Grund nötig, die Mail ist zugestellt. Es ist kein fehlender Wert und kein Fehler beim Abruf. Nur status: "sent" liefert null.
Bei allen anderen Status ist reason gefüllt:
| Feld | Bedeutung |
|---|---|
code |
DSN-Status, den der Empfänger-Server gemeldet hat, z. B. 5.1.1 oder 4.7.1. |
permanent |
true = dauerhaft (5.x.x) → Adresse in die Unterdrückungsliste. false = vorübergehend (4.x.x) → der Versand läuft weiter, nicht sperren. |
provider_block |
Erkannte Sperre eines großen Anbieters (microsoft, google, yahoo, t-online, united-internet), sonst null. |
key |
Stabiler Maschinenschlüssel der Erklärung, z. B. unknown_recipient, mailbox_full, greylisted_throttled. Auf diesen Wert darfst du programmieren; die Texte im Portal können sich ändern. |
Reihenfolge in der eigenen Anwendung: erst status prüfen, dann reason lesen.
const res = await fetch(`${BASE}/api/v1/messages/${queueId}`, { headers })
const mail = await res.json()
if (mail.status === 'delivered') {
markDelivered(queueId) // reason ist hier null
}
else {
const last = mail.attempts.at(-1)
if (last.reason?.permanent) suppress(last.recipient) // dauerhaft: nicht mehr anschreiben
else scheduleRecheck(queueId) // vorübergehend: später nochmal nachsehen
}
Kennst du die Queue-ID nicht, frag über den Empfänger:
curl -H "Authorization: Bearer $KEY" \
"https://my.mailbridge.email/api/v1/messages?recipient=kunde@example.com&date_from=2026-07-01T00:00:00Z"
Weitere Filter: status, sender, queue_id, search (Freitext), date_from, date_to, client (Endkunde), account und page/per_page (max. 200).
Bounces
curl -H "Authorization: Bearer $KEY" \
"https://my.mailbridge.email/api/v1/bounces?per_page=100"
Hier ist reason immer gefüllt: jeder Eintrag ist ein Fehlschlag oder eine Verzögerung, der null-Fall kommt nur bei /messages vor. Entscheidend ist reason.permanent: bei true (5.x.x) sollte die Adresse in deine Unterdrückungsliste wandern, bei false (4.x.x) läuft der Versuch weiter, dann nicht sperren.
Verbrauch
GET /api/v1/usage liefert den laufenden Monat: versendete Mails, Planlimit, gebuchte Zusatzpakete, das aktuell wirksame Limit und die Aufteilung je Endkunde (clients[].sent, clients[].share_cents), die Basis für deine Weiterverrechnung. GET /api/v1/usage/history?months=12 gibt bis zu zwölf Monate zurück.
GET /api/v1/clients listet deine Endkunden mit ihren IDs; diese ID ist der Wert für den client-Filter.
Grenzen, die du kennen solltest
- Versandhistorie: 30 Tage. Einzelereignisse liegen auf den Versandservern und werden dort 30 Tage vorgehalten. Ältere Mails sind nicht mehr abfragbar, nur der Verbrauch reicht 12 Monate zurück.
- Kein Betreff, keine eigene Message-ID. Die Versandserver protokollieren Absender, Empfänger und Queue-ID, nicht den Inhalt. Speichere Queue-ID und Empfänger in deiner Anwendung, dann findest du jede Mail wieder.
degraded: War ein Versandserver beim Abruf nicht erreichbar, steht sein Name in diesem Feld, die Liste kann dann unvollständig sein. Auf[]prüfen, bevor du aus einem leeren Ergebnis Schlüsse ziehst.capped: Bei Konten mit vielen Zugängen sind sehr tiefe Seiten nicht exakt paginierbar. Grenze dann den Zeitraum ein oder frage je Endkunde ab.- Ratenlimit: 120 Anfragen pro Minute und Schlüssel. Jede Antwort trägt
X-RateLimit-Limit,X-RateLimit-RemainingundX-RateLimit-Reset; bei Überschreitung kommt429mitRetry-After.
Fehlercodes
| HTTP | message |
Ursache |
|---|---|---|
| 401 | api_key_missing |
Kein Authorization- oder X-API-Key-Header. |
| 401 | api_key_invalid |
Schlüssel unbekannt, widerrufen oder abgelaufen. |
| 403 | agency_required |
Die API gehört zur Agenturlinie. |
| 403 | api_access_blocked |
Zugriff für das Konto gesperrt, bitte Support kontaktieren. |
| 403 | subscription_inactive |
Kein aktives Abo. |
| 403 | trial_expired |
Die Testphase ist abgelaufen, ohne dass ein Abo abgeschlossen wurde. Sobald die Zahlung im Portal eingerichtet ist, funktionieren die bestehenden Schlüssel wieder. |
| 404 | message_not_found |
Queue-ID im Zeitraum nicht gefunden (älter als 30 Tage?). |
| 405 | method_not_allowed |
Die API ist lesend, erlaubt ist nur GET. |
| 429 | rate_limited |
Ratenlimit erreicht, Retry-After beachten. |
Noch kein Konto? 14 Tage kostenlos testen, Zahlungsdaten brauchst du erst danach.