Drei Aufrufe, und nichts einzurichten
Die gesamte Schnittstelle besteht aus drei Endpunkten unter https://grabmail.io/api/v1, dazu einer Adresse für Anhänge, die die anderen bereits fertig mitliefern. Es gibt keinen Aufruf, der ein Postfach anlegt, und dieses Fehlen ist keine Lücke: Eine Adresse beginnt zu existieren, sobald Mail dort ankommt – für einen solchen Aufruf gibt es also nichts zu tun.
| Aufruf | Was er beantwortet | Was Sie übergeben |
|---|---|---|
GET /mailbox | Alles, was an einer Adresse wartet, neueste zuerst. | address und optional limit sowie before |
GET /message/{id} | Eine vollständige Nachricht: der Klartextteil, der HTML-Teil und jeder Anhang mit einer bereits fertigen URL. | mailbox |
DELETE /message/{id} | Entfernt sie sofort, statt auf das Ende des Aufbewahrungszeitraums zu warten. | mailbox |
GET /attachment/{id} | Die Bytes einer Datei, genau so, wie sie angekommen ist. | mailbox |
Jede Antwort ist JSON, auch jeder Fehler. Jede Zeitangabe ist UTC im RFC-3339-Format. Nachrichten-IDs sind opak: geben Sie sie unverändert zurück, zerlegen Sie sie nie.
Der erste Aufruf, und was eine leere Adresse antwortet
Wählen Sie einen Namen, setzen Sie eine der öffentlichen Domains dahinter, und lesen Sie sie aus. Nichts muss vorher existieren, und nichts wird durch das Nachfragen angelegt.
$ curl -sG https://grabmail.io/api/v1/mailbox \
--data-urlencode "address=k7fq2m@grabmail.io"{
"address": "k7fq2m@grabmail.io",
"alias": "q4v8n2mt7xkd@example.net",
"count": 1,
"next": null,
"messages": [
{
"id": "3QK7ZB5M9WVXR2HD4TNFJ0PC6A",
"from": "no-reply@example.com",
"from_name": "Example",
"subject": "Your verification code",
"preview": "Your code is 481920. It expires in 10 minutes.",
"has_html": false,
"date": "2026-08-29T09:14:02Z",
"seen": false,
"attachments": 0,
"expires_at": "2026-09-03T09:14:02Z"
}
]
}Fünf Felder, und zwei davon sind interessanter, als sie aussehen:
count- Wie viele Nachrichten in dieser Antwort stehen – nicht, wie viele das Postfach enthält. Sobald Sie
limitübergeben, sind das zwei verschiedene Zahlen. next- Der Cursor für die Seite nach dieser, oder
null, wenn danach nichts mehr kommt. Es ist die ID der letzten Nachricht, die Sie gerade erhalten haben – deshalb kostet das Blättern keinen zusätzlichen Aufruf, um sie herauszufinden. messages- Die Liste selbst, neueste zuerst. Jeder Eintrag enthält bereits
subject,from,date,seen, eine kurzepreviewdes Texts, ob es einen HTML-Teil gibt und wie viele Anhänge vorhanden sind. alias- Eine zweite Adresse, die hierher zustellt und nichts über diese hier verrät. Geben Sie sie statt der echten Adresse in ein Formular ein; wer auch immer damit endet und sie in diesen Dienst eintippt, findet ein leeres Postfach vor.
address- Die Adresse so, wie sie verstanden wurde: klein geschrieben und getrimmt. Vergleichen Sie sie mit dem, was Sie gesendet haben, wenn Sie die Adresse aus Teilen zusammensetzen.
Über die ersten fünfzig hinaus lesen
Ein Aufruf liefert standardmäßig höchstens fünfzig Nachrichten, im äußersten Fall zweihundert. Ein stark frequentiertes Catch-all übertrifft beide Werte an einem einzigen Nachmittag, und der Teil, den man dabei falsch einschätzt, ist das, was als Nächstes kommt – denn es ist keine Seitenzahl.
limit- Wie viele dieser Aufruf zurückgibt, 1 bis 200. Werte außerhalb dieses Bereichs werden begrenzt statt abgelehnt, sodass
limit=5000Ihnen stillschweigend 200 liefert. before- Die ID der ältesten Nachricht, die Sie schon haben. Sie erhalten die Nachrichten danach. Geben Sie zurück, was die vorherige Antwort in
nextgeliefert hat. nextnullbedeutet, dass Sie das Ende des Postfachs erreicht haben. Es ist das einzige verlässliche Signal für das Listenende: Eine kurze Seite ist keins, denn eine Seite ist nur dann kurz, wenn der Server das so entscheidet.
limit begrenzt eine Antwort, next benennt, wo diese Antwort endete, und before fragt nach dem, was danach kommt.ADDR="k7fq2m@grabmail.io"
CURSOR=""
while :; do
PAGE=$(curl -fsG https://grabmail.io/api/v1/mailbox \
--data-urlencode "address=$ADDR" \
--data-urlencode "limit=200" \
${CURSOR:+--data-urlencode "before=$CURSOR"})
printf '%s' "$PAGE" | jq -c '.messages[]'
CURSOR=$(printf '%s' "$PAGE" | jq -r '.next // empty')
[ -n "$CURSOR" ] || break
sleep 1
doneSolange schleifen, wie next nicht null ist, und Sie haben das ganze Postfach, egal wie groß es geworden ist. Jeder Aufruf ist ein Bereichslesevorgang auf einem Index statt auf einem Offset, sodass die tausendste Seite genauso viel kostet wie die erste.
Ein Cursor aus einem anderen Postfach, oder einer, der inzwischen abgelaufen ist, ist kein Fehler: Sie erhalten eine leere Seite und next: null. Das ist die richtige Antwort – die neueste Seite stattdessen zu wiederholen, würde einem Skript Mail geben, die es schon verarbeitet hat –, bedeutet aber auch, dass ein veralteter Cursor genau wie das Ende der Liste aussieht.
Eine Nachricht öffnen, und wann Sie das nicht müssen
Die ID aus der Auflistung plus das Postfach, an das sie zugestellt wurde, liefert die Nachricht selbst. Beides ist erforderlich: Eine ID, die aus einem Postfach durchgesickert ist, lässt sich nicht zum Lesen eines anderen verwenden, denn jede Abfrage ist auch an die Adresse gebunden.
$ curl -sG https://grabmail.io/api/v1/message/3QK7ZB5M9WVXR2HD4TNFJ0PC6A \
--data-urlencode "mailbox=k7fq2m@grabmail.io"{
"id": "3QK7ZB5M9WVXR2HD4TNFJ0PC6A",
"from": "no-reply@example.com",
"to": "k7fq2m@grabmail.io",
"subject": "Your verification code",
"date": "2026-08-29T09:14:02Z",
"expires_at": "2026-09-03T09:14:02Z",
"text": "Your code is 481920. It expires in 10 minutes.",
"html": null,
"attachments": []
}text- Der Klartextteil. Parsen Sie diesen, wenn er vorhanden ist: Er ist stabil, enthält kein Markup, und ein sechsstelliger Code darin ist ein sechsstelliger Code.
html- Der HTML-Teil, oder
null, wenn der Absender keinen gesendet hat. Bestätigungslinks gibt es oft nur hier. attachments- Ein Eintrag pro Datei, jeweils mit der bereits fertigen URL zum Abrufen. Eine leere Liste, kein
null, wenn es keine gibt. expires_at- Wann diese Nachricht gelöscht wird, im selben RFC-3339-Format wie
date. Lesen Sie es aus, statt es zu berechnen – der Aufbewahrungszeitraum ist keine Einstellung, auf die Sie sich von außen verlassen können.
Sehr oft können Sie diesen Aufruf ganz überspringen. Die Auflistung liefert bereits Betreff, Absender, Datum und eine kurze Vorschau des Texts – genug, um zu entscheiden, dass eine Nachricht nicht die ist, auf die Sie warten. Jede Nachricht eines Postfachs abzurufen, nur um festzustellen, dass keine davon gebraucht wurde, ist der häufigste Grund, warum ein Skript langsam wird.
Eine Datei herausholen
Jeder Anhang trägt seine eigene url, und das Detail, das sich zu kennen lohnt, bevor Sie die Schleife schreiben, ist, dass es sich um einen Pfad auf diesem Origin handelt statt um eine absolute Adresse – mit dem Parameter mailbox bereits darin. Setzen Sie den Origin davor, rufen Sie ihn ab, und es gibt nichts weiter zu übergeben und nichts zu autorisieren.
ADDR="k7fq2m@grabmail.io"
ID="3QK7ZB5M9WVXR2HD4TNFJ0PC6A"
curl -fsG https://grabmail.io/api/v1/message/$ID \
--data-urlencode "mailbox=$ADDR" \
| jq -r '.attachments[] | "\(.url)\t\(.filename)"' \
| while IFS=$'\t' read -r path name; do
curl -fs "https://grabmail.io$path" -o "$name"
doneEs antwortet immer mit application/octet-stream und Content-Disposition: attachment, unabhängig davon, was der Absender als Dateityp angegeben hat. Das ist Absicht – ein fremdes text/html unverändert zurückzugeben, würde einen Anhang als Seite auf diesem Origin laufen lassen –, also liest ein Skript, dem der Typ wichtig ist, ihn aus dem Nachrichten-JSON, wo er Daten und keine Anweisung ist.
Die gesamte Nachricht, mit allen Dateien, ist auf 5 MB begrenzt. Was diese Obergrenze bedeutet, sobald Base64 mit einer Binärdatei fertig ist, ist ein eigenes Thema, für das es eine Anleitung zu Anhängen gibt.
Zwei Ratenbudgets, nicht eines
Das ist der Teil, den man kennen sollte und leicht übersieht: Das Auflisten einer Adresse und das Lesen daraus werden getrennt gemessen, weil es nicht dasselbe Risiko ist. Wer eine Adresse kennt, kann ihre Auflistung pollen; das Lesen einer Nachricht braucht eine ID, und die lässt sich nicht erraten.
| Was Sie aufrufen | Das Budget | Was das in der Praxis bedeutet |
|---|---|---|
GET /mailbox | Eine Anfrage pro Sekunde, pro Adresse | Der vorgesehene Polling-Rhythmus, in diesem Tempo nie gedrosselt. Schneller wird abgelehnt und hätte ohnehin nichts gebracht. |
GET /message/{id}, GET /attachment/{id}, DELETE | Deutlich großzügiger, pro Adresse | Eine Seite Nachrichten in einem Rutsch leeren, ohne Pausen dazwischen. Deshalb kann eine Oberfläche eine Nachricht in derselben Sekunde öffnen, in der ein Poll lief. |
| Alles zusammengerechnet | 1200 Anfragen pro Minute, pro Client | Zwanzig Adressen, einmal pro Sekunde gepollt – komfortabel jenseits jeder echten Automatisierung, und eine Bremse für einen einzelnen Host, der zehntausend Adressen durchläuft. |
Überschreiten Sie eines davon, erhalten Sie 429 mit der Wartezeit in Sekunden im Retry-After-Header. Halten Sie sich daran, statt mit einer selbst ausgedachten Zahl zurückzuweichen: Der Server sagt Ihnen damit genau, wann er Ja sagen wird.
read_box() {
local wait
while :; do
BODY=$(curl -s -D /tmp/gm.h -G https://grabmail.io/api/v1/mailbox \
--data-urlencode "address=$1")
grep -qi '^HTTP/[0-9.]* 429' /tmp/gm.h || { printf '%s' "$BODY"; return 0; }
wait=$(awk 'tolower($1) == "retry-after:" { print $2 + 0 }' /tmp/gm.h)
sleep "${wait:-1}"
done
}Wie man auf eine Nachricht wartet, die noch nicht angekommen ist – eine Frist statt einer Anzahl von Versuchen, und was zu tun ist, wenn sie verstreicht – ist das Thema der Anleitung zum Testen von Verifizierungsabläufen. Die Schleife dort ist dieselbe Schleife, die auch ein geplanter Job braucht.
Löschen, und die feste Grenze darunter
Eine Nachricht, mit der Sie fertig sind, kann sofort weg, statt ihren Aufbewahrungszeitraum abzusitzen. Der Aufruf ist idempotent: Dieselbe ID zweimal zu löschen, antwortet beide Male mit 200, sodass eine wiederholte Anfrage nie wie ein Fehlschlag aussieht.
$ curl -s -X DELETE -G https://grabmail.io/api/v1/message/3QK7ZB5M9WVXR2HD4TNFJ0PC6A \
--data-urlencode "mailbox=k7fq2m@grabmail.io"- Löschen, sobald Sie haben, wofür Sie gekommen sind
- Ein Skript, das eine Nachricht verarbeitet und liegen lässt, verarbeitet sie beim nächsten Lauf erneut, sofern es nicht selbst Buch führt über das, was es schon gesehen hat. Löschen ist die billigere Buchführung.
- Verlassen Sie sich dabei nicht auf Privatsphäre
- Zwischen Ankunft und Löschung konnte jeder, der die Adresse kennt, sie gelesen haben. Löschen schließt das Zeitfenster; es macht es nicht ungeschehen.
- Alles ist nach 5 Tagen weg, ohne Ausnahme
- Gelesen oder ungelesen, gelöscht oder nicht: Eine Nachricht ist 5 Tage nach ihrer Ankunft verschwunden. Das ist eine feste Grenze und keine Einstellung, und kein Parameter verlängert sie.
Die Slugs, auf die man verzweigen sollte, und das Feld, das man nie lesen sollte
Jeder Fehlschlag ist JSON mit denselben zwei Feldern. error ist ein stabiler, maschinenlesbarer Slug; message ist für Menschen gedacht und kann jederzeit umformuliert werden. Auf das zweite Feld zu verzweigen ist die Art, wie ein Skript an einem Tag kaputtgeht, an dem sich nichts geändert hat.
| Status und Slug | Was passiert ist | Was ein Skript tun sollte |
|---|---|---|
400 invalid_address | Die Adresse fehlt, oder sie hat nicht die Form einer Adresse. | Sofort fehlschlagen. Kein noch so häufiges Wiederholen behebt einen Tippfehler. |
400 bad_cursor | before ist keine Nachrichten-ID. | Sofort fehlschlagen, und prüfen, ob Sie wirklich next zurückgeben und nicht etwas selbst Gebautes. |
404 unknown_domain | Diese Domain wird hier nicht gehostet. | Sofort fehlschlagen. Bei einer eigenen Domain liegt es am MX-Eintrag – siehe eine Domain verbinden. |
404 not_found | Keine solche Nachricht in diesem Postfach, oder ihr Aufbewahrungszeitraum ist abgelaufen. | Als verschwunden behandeln. Das bekommen Sie auch für eine gültige ID, die gegen das falsche Postfach gelesen wird. |
429 rate_limited | Eines der Budgets von oben. | Retry-After Sekunden schlafen und weitermachen. Nie als fehlgeschlagenen Lauf zählen. |
Ein Job, der eine Adresse stündlich leert
Setzt man die Teile zusammen, wird ein geplanter Job kurz. Dieser hier nimmt jede Nachricht, die an einer Adresse wartet, schreibt sie als JSON auf die Festplatte und löscht sie – sodass der nächste Lauf mit einem leeren Postfach beginnt und niemals dieselbe Nachricht zweimal verarbeiten kann.
#!/usr/bin/env bash
set -euo pipefail
ADDR="orders@example.com"
OUT="/var/lib/mailsink"
API="https://grabmail.io/api/v1"
mkdir -p "$OUT"
while :; do
page=$(curl -fsG "$API/mailbox" \
--data-urlencode "address=$ADDR" \
--data-urlencode "limit=200")
ids=$(printf '%s' "$page" | jq -r '.messages[].id')
[ -n "$ids" ] || break
for id in $ids; do
curl -fsG "$API/message/$id" \
--data-urlencode "mailbox=$ADDR" > "$OUT/$id.json"
curl -fs -X DELETE -G "$API/message/$id" \
--data-urlencode "mailbox=$ADDR" > /dev/null
done
sleep 1
done17 * * * * /usr/local/bin/drain.shVier Eigenschaften lohnt es sich zu benennen, denn sie unterscheiden einen Job, den man einfach laufen lassen kann, von einem, den man beobachten muss:
- Er lässt sich gefahrlos zweimal ausführen. Zwei gleichzeitig gestartete Kopien erledigen dieselbe Arbeit in anderer Reihenfolge und löschen dieselben Nachrichten; die zweite findet ein leeres Postfach vor und stoppt.
- Er schreibt, bevor er löscht. Ist die Festplatte voll oder wird der Prozess beendet, ist die Nachricht beim nächsten Lauf noch im Postfach. Die umgekehrte Reihenfolge verliert Mail genau an dem Tag, an dem es darauf ankommt.
- Er leert, statt nur zu lesen. Weil jede Nachricht verschwindet, sobald sie sicher auf der Festplatte liegt, liefert die nächste Auflistung die nächsten zweihundert – ein Postfach, das zwischen zwei Läufen vierhundert Nachrichten erhalten hat, wird also vollständig geleert, nicht nur bis auf die neuesten fünfzig.
- Er scheitert laut. Ein Exit-Code ungleich null ist es, der cron dazu bringt, Ihnen die Ausgabe zu schicken. Ein Job, der seine eigenen Fehler verschluckt, ist ein Job, der schon seit einem Monat kaputt ist.
Was diese API nicht für Sie tut
Vier Dinge, die sie nicht tut, jedes davon absichtlich, und keins davon kommt später noch dazu. Besser, jetzt darum herum zu planen, als es an einem Skript zu entdecken, das still vor sich hin halb funktioniert hat:
- Sie versendet nie
- Nur Empfang. Es gibt keinen Endpunkt, der eine Nachricht auf die Leitung legt – deshalb lässt sich hiermit nichts von einer Adresse aus versenden, die Ihnen nicht gehört.
- Sie schiebt nie etwas zu
- Keine Webhooks und keine Callbacks: Sie fragen, sie antwortet. Ein KI-Agent, der lieber blockiert, bis die Mail ankommt, hat stattdessen
wait_for_messageüber MCP – siehe die Anleitung für Agenten. - Sie sucht nie
- Es gibt keinen Query-Parameter für einen Absender oder einen Betreff. Filtern passiert auf Ihrer Seite, über die Auflistung – ein Grund, warum die Auflistung eine Vorschau mitliefert.
- Sie authentifiziert nie, auf einer öffentlichen Domain
- Wer die Adresse kennt, liest das Postfach. Die Adresse ist das gesamte Geheimnis – behandeln Sie sie auch so: Leiten Sie sie nie aus dem Namen eines Kunden ab, und schicken Sie nichts an eine gemeinsam genutzte Domain, das Sie nicht laut vorgelesen hören wollten.
Die Antwort auf den letzten Punkt ist eine eigene Domain. Richten Sie deren MX auf smtp.grabmail.io, und jede Adresse darauf antwortet auf denselben drei Endpunkten, ohne zweite API zu lernen und ohne Schlüssel zu rotieren – und, auf Anfrage, geschlossen, sodass nur ein Bearer-Token sie öffnet. Eine Domain verbinden braucht einen einzigen DNS-Eintrag.
10 smtp.grabmail.io
Bevor Sie es laufen lassen
Sechs Dinge, die sich zu prüfen lohnen, bei einem Job, der läuft, ohne dass Sie zusehen:
- Nicht schneller als einmal pro Sekunde pro Adresse pollen, und
Retry-Afterbeachten, wenn Sie zum Warten aufgefordert werden. nextbis zum Ende folgen, statt anzunehmen, ein Aufruf sei schon das ganze Postfach.- Auf den Statuscode und
errorverzweigen, nie aufmessage. - Schreiben Sie alles, was Sie behalten müssen, bevor Sie es löschen, und denken Sie daran, dass 5 Tage eine feste Grenze sind, die sich nicht verschieben lässt.
- Verankern Sie, was Sie extrahieren, an Ihrer eigenen Vorlage. Ein bloßes sechsstelliges Muster passt bereitwillig auch auf eine Jahreszahl, einen Preis oder eine Bestellnummer, die zuerst ankam.
- Gehen Sie davon aus, dass die Adresse öffentlich ist, außer sie liegt auf einer Domain, die Sie kontrollieren – und legen Sie alles, worauf es ankommt, auf eine solche.
Nichts davon braucht ein Konto. Wachsen Sie über die öffentlichen Domains hinaus, ändert sich nur die Domain in der Adresse – die drei Aufrufe oben bleiben exakt, wie sie sind.
Fragen
Brauche ich einen API-Key?
Nein. Auf den öffentlichen Domains gibt es kein Konto, kein Token und nichts zu registrieren, und eine Domain, die Sie hierher zeigen lassen, antwortet auf denselben Endpunkten ebenfalls ohne Schlüssel. Die einzige Ausnahme ist eine Domain, die wir auf Anfrage geschlossen haben – die wird mit einem Authorization: Bearer-Header gelesen.
Wie schnell darf ich pollen?
Einmal pro Sekunde pro Adresse für die Auflistung – das ist der vorgesehene Rhythmus und wird nie gedrosselt. Das Lesen einer Nachricht oder eines Anhangs wird getrennt und deutlich großzügiger gemessen, sodass Sie eine Seite Nachrichten in einem Schub abarbeiten können. Alles zusammen ist auf 1200 Anfragen pro Minute pro Client begrenzt.
Woran erkenne ich, dass ich das ganze Postfach gelesen habe?
Wenn next als null zurückkommt. Schließen Sie nicht von einer kurzen Seite darauf: Der Server entscheidet, was eine Seite ist, und eine Seite, die kürzer als limit ist, ist allein noch nicht das Ende.
Kann ich das aus einem Browser aufrufen?
Ja. Antworten tragen Access-Control-Allow-Origin: *, sodass eine Seite auf jedem Origin die Endpunkte direkt aufrufen kann, ohne eigenen Proxy dazwischen. Autorisierung ist hier nie ein Cookie, sodass es nichts kostet, das so weit zu öffnen.
Was passiert, wenn ich nach einer abgelaufenen Nachricht frage?
404 mit not_found, genau wie bei einer ID, die es nie gab. Alles wird 5 Tage nach der Ankunft gelöscht, gelesen oder nicht, und kein Parameter verlängert das.
Kann ich einen Webhook bekommen, wenn Mail ankommt?
Nein – die REST-API funktioniert nach dem Prinzip Fragen und Antworten, ohne Callbacks. Wenn Sie Code brauchen, der blockiert, bis die Nachricht ankommt, hat der MCP-Server wait_for_message, das genau das tut und für Agenten gedacht ist.
Ist es sicher, eine öffentliche Adresse in Produktion zu verwenden?
Nur für Dinge, bei denen es Ihnen nichts ausmachen würde, wenn ein Fremder sie liest. Wer die Adresse kennt, kann ihr Postfach lesen, über die API genauso wie über die Website. Für alles andere richten Sie eine eigene Domain hierher – die Aufrufe ändern sich dadurch nicht.
Warum erscheint eine Nachricht, die ich nie geöffnet habe, als gelesen?
Weil etwas anderes sie geöffnet hat. Das Lesen einer Nachricht über die API setzt ihr seen-Flag, und das Flag wird mit jedem geteilt, der auf diese Adresse schaut. Ein Skript und ein Mensch, die dasselbe Postfach beobachten, werden sich gegenseitig ständig überraschen – filtern Sie deshalb nach bereits verarbeiteten IDs statt nach seen.
Muss ich Nachrichten löschen?
Nein – alles läuft nach 5 Tagen von selbst ab. Löschen lohnt sich in einem geplanten Job trotzdem, weil ein geleertes Postfach die einfachstmögliche Aufzeichnung dessen ist, was Sie bereits verarbeitet haben.


