Mach aus eingehenden E-Mails einen Webhook
- 01Eine E-Mail-Quelle erstellen und ihre Adresse finden
- 02Der Umschlag, den dein Ziel empfängt
- 03Nach Absender oder Betreff weiterleiten
- 04Dedupe, Größenbudget und Anhänge
- 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.
Einrichten
- 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ältemailAddress, das bei jedem anderen Anbieternullist.curlcurl -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 daseventTypeFieldder Quelle und alleidempotencyKeys, denn beide beschrieben eine Payload-Form, die nicht mehr ankommt. - 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
Verbinde die Quelle mit einem Ziel
Wie bei jeder anderen Quelle. Gefiltert wird nach Absender oder Betreff, je nachdem, was das
eventTypeFieldder Quelle nennt, oder nachemail.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.
{ "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.
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.
# 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"] }
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.
Message-ID. Eine Wiederholung liefert duplicate: true und speichert nichts.maxPayloadSizeKb der Quelle, geteilt von Bodies und Anhängen. Zwischen 1 und 4.096 KB, Standard 256.null gesetzt, wenn Text plus HTML es immer noch sprengen würden, nie teilweise gekürzt. In beiden Fällen wird bodyTruncated: true gesetzt.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.rotate-mask ändert die Adresse, weil sie aus der maskId abgeleitet wird.Bewährte Praktiken
- Setze
eventTypeFieldaufbody.from, bevor du es brauchst. Filter, die gegenemail.receivedgeschrieben 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 eintruncated-Flag zu entdecken, ist der schlechtere Weg. - Lies
spamStatusundbouncein 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.