SolidHooksSolidHooks
Loslegen
AnwendungsfälleWebhooks per E-Mail empfangen

Mach aus eingehenden E-Mails einen Webhook

Viele Systeme werden dir nie einen Webhook senden. Ein Lieferant schickt eine Mitteilung per E-Mail, eine Bank eine Meldung, ein internes Tool einen nächtlichen Bericht. Gib jedem davon eine SolidHooks-Adresse, und was dort ankommt, wird zu einem gespeicherten Event mit geparstem Umschlag, das über dieselben Verbindungen weitergeleitet und nach demselben Zeitplan wiederholt wird wie HTTP-Traffic.
Eine Adresse pro QuelleDedupe über Message-IDGleiche Wiederholungen und Belege
Was dieser Leitfaden abdeckt
  1. 01Eine E-Mail-Quelle erstellen und ihre Adresse finden
  2. 02Der Umschlag, den dein Ziel empfängt
  3. 03Nach Absender oder Betreff weiterleiten
  4. 04Dedupe, Größenbudget und Anhänge
  5. 05Die eine Regel: Eine Quelle macht E-Mail oder HTTP, nie beides

Wie es in die Pipeline passt

E-Mail ist ein Anbieter an einer Quelle, ausgewählt wie GitHub oder Stripe, kein separates Produkt. Sobald die Mail geparst ist, ist sie ein Event wie jedes andere. Die Teile von SolidHooks, die du schon kennst, funktionieren also weiter: Verbindungen gleichen es ab, Zustellungen wiederholen es, die Aufbewahrung lässt es ablaufen, und dein Consumer empfängt wie gewohnt JSON über HTTP.

Sender
any mail client
Source address
<maskId>@sdhk.events
Stored event
parsed envelope, typed
Destination
HTTP POST
Dein Consumer spricht nie SMTP. Er empfängt denselben JSON-POST, den er von einem Webhook-Anbieter bekäme.

Einrichten

  1. 1

    Erstelle eine Quelle mit dem E-Mail-Anbieter

    Übergib beim Erstellen provider: "email" oder stelle eine bestehende Quelle per PATCH darauf um. Die Antwort enthält emailAddress, das bei jedem anderen Anbieter null ist.

    curl
    curl -s "$API/v1/sources" \  -H "authorization: Bearer $SDHK_KEY" \  -H 'content-type: application/json' \  -d '{ "name": "Supplier notices", "provider": "email",       "verifierConfig": {} }' → { "id": "src_...", "maskId": "Kb3xk29ZtQm7Rv1A",    "emailAddress": "Kb3xk29ZtQm7Rv1A@sdhk.events", ... }

    Eine Quelle umzustellen, die schon eine Verifizierung hat, braucht verifierType: "noop" im selben PATCH. Eingehende E-Mails signiert niemand, daher weigert sich die API, die gespeicherte Zugangsberechtigung mitzunehmen, statt einen Schlüssel zu behalten, den sie nie lesen würde. Derselbe PATCH leert auch das eventTypeField der Quelle und alle idempotencyKeys, denn beide beschrieben eine Payload-Form, die nicht mehr ankommt.

  2. 2

    Gib die Adresse an den Absender weiter

    Füge sie dort ein, wo das andere System einen Empfänger erwartet. Es gibt keinen Verifizierungsschritt und auf deiner Seite nichts im DNS zu konfigurieren.

  3. 3

    Verbinde die Quelle mit einem Ziel

    Wie bei jeder anderen Quelle. Gefiltert wird nach Absender oder Betreff, je nachdem, was das eventTypeField der Quelle nennt, oder nach email.received, wenn du es leer gelassen hast. Weiterleiten nach Absender oder Betreff wird unten behandelt.

Was dein Ziel empfängt

Die gespeicherte Payload ist ein normalisierter Umschlag statt eines rohen MIME-Blobs, dein Handler braucht also keinen Mail-Parser. Adressen werden aufgeteilt, beide Bodies getrennt, und Anhänge kommen als base64 mit ihren Metadaten an.

payload
{  "from": "billing@supplier.example",  "to": ["Kb3xk29ZtQm7Rv1A@sdhk.events"],  "cc": [],  "subject": "Invoice #42",  "messageId": "<abc123@mail.supplier.example>",  "date": "Tue, 14 Jul 2026 09:00:00 +0000",  "text": "plain-text body",  "html": "<p>html body</p>",  "spamStatus": "NotSpam",  "bounce": false,  "attachments": [    { "name": "invoice.pdf", "contentType": "application/pdf", "size": 12345, "data": "<base64>" }  ]}

from ist der From:-Header der Nachricht, reduziert auf die reine Adresse. Acme Billing <billing@supplier.example> kommt also als billing@supplier.example an. Erst das macht es als Filterschlüssel brauchbar. Der Anzeigename ändert sich, und der Return-Path darunter ist bei allem, was über einen Mail-Dienst gesendet wird, eine Bounce-Adresse pro Nachricht. Dieser Umschlag-Absender wird nur als letzter Ausweg verwendet, wenn eine Nachricht gar keinen From:-Header hat.

Der rohe Body des Events behält die komplette ursprüngliche Payload des Mail-Anbieters, einschließlich aller oben nicht aufgeführten Felder, wobei attachments[].data auf null gesetzt wird. Die Bytes der Anhänge liegen nur im Umschlag, damit ein großes PDF nicht doppelt gespeichert wird.

Eine Quelle macht das eine oder das andere

Bei einer E-Mail-Quelle liefert ein HTTP-POST an ihre Ingest-URL 400 BAD_REQUEST mit der Meldung, dass die Quelle E-Mails empfängt. Das ist Absicht. Mails und Webhooks kommen über unterschiedliche Wege an, und nur einer davon erzeugt einen Umschlag. Eine Quelle, die beides annimmt, würde deinem Consumer also zwei Payload-Formen unter einem Event-Typ liefern.

Folge für die Idempotenz
idempotencyKeys wird bei einer E-Mail-Quelle mit 400 abgelehnt. Die Deduplizierung läuft dort über die Message-ID, und diesen Pfad kannst du nicht wählen.

Nach Absender oder Betreff weiterleiten

Der Event-Typ einer E-Mail kommt aus dem eventTypeField der Quelle, derselben Einstellung, die auch eine HTTP-Quelle nutzt. Richte es auf body.from, und jede Nachricht wird mit der Absenderadresse typisiert, die ein Verbindungsfilter dann wie jeden anderen Event-Typ abgleicht. Der Abgleich ignoriert Groß- und Kleinschreibung, weil Mailsysteme die Schreibweise eines From:-Headers nicht erhalten. Eine einmal geschriebene Allowlist passt also weiter, auch wenn ein Absender später als Billing@Supplier.example ankommt. Beim erneuten Abspielen wird nach dem Typ weitergeleitet, der beim Ingest gespeichert wurde, Filter verhalten sich also beim zweiten Mal identisch.

Zwei Pfade stehen zur Verfügung und keine anderen: body.from und body.subject. Das sind die Felder, die eine Nachricht tatsächlich von einer anderen unterscheiden. Das Dashboard bietet dieselben zwei an, sodass eine über die API konfigurierte Quelle im SolidHooks Dashboard bearbeitbar bleibt.

curl
# Type each email with its sender instead of email.receivedcurl -s -X PATCH "$API/v1/sources/src_..." \  -H "authorization: Bearer $SDHK_KEY" \  -H 'content-type: application/json' \  -d '{ "data": { "eventTypeField": "body.from" } }' # Then a connection takes only that supplier:#   "filterRules": { "eventTypes": ["billing@supplier.example"] }
Lässt du es leer, bekommst du einen Typ
Ohne eventTypeField wird jede angenommene E-Mail als email.received typisiert. Sie ist weiterhin filterbar, nur nicht nach Absender trennbar. Derselbe Rückfall gilt pro Nachricht: Eine E-Mail ohne Betreff an einer body.subject-Quelle landet bei email.received, statt aus jedem Filter herauszufallen.

Dedupe und das Größenbudget

Mails sind größer und unordentlicher als Webhook-JSON. Es lohnt sich also, die Regeln dafür zu kennen, was erhalten bleibt, bevor um 3 Uhr nachts ein 20-MB-Anhang ankommt. Nichts wird abgelehnt, weil es zu groß ist. Es wird passend gekürzt, und das Budget, in das es passen muss, liegt bei höchstens 4.096 KB.

DedupeÜber Message-ID. Eine Wiederholung liefert duplicate: true und speichert nichts.
BudgetDas maxPayloadSizeKb der Quelle, geteilt von Bodies und Anhängen. Zwischen 1 und 4.096 KB, Standard 256.
Text zuerstKlartext wird zuerst angerechnet und gekürzt, wenn er allein das Budget sprengt.
HTML danachKomplett auf null gesetzt, wenn Text plus HTML es immer noch sprengen würden, nie teilweise gekürzt. In beiden Fällen wird bodyTruncated: true gesetzt.
AnhängeMetadaten bleiben erhalten, data wird null, und truncated: true wird gesetzt. Bytes werden nie teilweise gekürzt. Angerechnet wird das base64, plane also etwa ein Drittel mehr ein als die Dateigröße.
Rotationrotate-mask ändert die Adresse, weil sie aus der maskId abgeleitet wird.

Bewährte Praktiken

  • Setze eventTypeField auf body.from, bevor du es brauchst. Filter, die gegen email.received geschrieben sind, musst du neu schreiben, sobald ein zweiter Absender an derselben Adresse auftaucht.
  • Erhöhe maxPayloadSizeKb, bevor du es brauchst, wenn der Absender PDFs anhängt. Die Obergrenze liegt bei 4.096 KB, und base64 kostet etwa ein Drittel mehr als die Datei selbst. Das Budget erst über ein truncated-Flag zu entdecken, ist der schlechtere Weg.
  • Lies spamStatus und bounce in deinem Handler aus. Sie stehen im Umschlag, weil Mails von Absendern kommen, die du nicht kontrollierst.
  • Behandle die Adresse wie eine Zugangsberechtigung. Wer sie hat, kann an dieser Quelle Events erzeugen. Rotiere sie also, wenn sie irgendwo öffentlich landet.

Fragen

Gib den Systemen, die dir nur mailen, einen Webhook

Wähle E-Mail als Anbieter, gib die Adresse weiter und behandle das Ergebnis wie jedes andere Event.