Add SMTP2GO delivery webhook#139
Merged
Merged
Conversation
Feeds SMTP2GO's delivery/bounce/spam events into the same deliverability lifecycle as the SES and SendGrid handlers, so bounce suppression and soft-bounce self-healing work when sending via SMTP2GO over the smtp backend. Event mapping (pure, unit-tested): delivered clears transient soft-bounce state; bounce maps SMTP2GO's hard/soft classification, with an unclassified bounce defaulting to soft (conservative - soft doesn't block until the threshold, so a misclassification can't lock anyone out); spam is a complaint. reject is ignored - SMTP2GO emits it when refusing an already-flagged address, so it carries no new state. SMTP2GO doesn't sign payloads, so the endpoint is guarded by a shared secret in the URL (SMTP2GO_WEBHOOK_SECRET; 404 when unset, constant-time compared). Accepts either webhook output type - JSON (single object or array) or form-encoded (single event) - so a webhook left on the wrong output type still works instead of silently rejecting every event. Recipient is read from SMTP2GO's `rcpt` field.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fast-follow to the email deliverability lifecycle (#138). Wires SMTP2GO's webhook into the same machinery so bounce suppression and soft-bounce self-healing work when sending via SMTP2GO over the
smtpbackend - needed before migrating off SendGrid.Event mapping
POST /v1/webhooks/smtp2go/eventsdispatches into the existingapply_bounce/apply_complaint/apply_deliveredtransitions:delivered-> clears transient soft-bounce state (the greylist self-heal)bounce-> hard/soft from SMTP2GO'sbounceclassification field. An unclassified/missing classification defaults to soft - the conservative choice, since soft doesn't block until the threshold, so a misclassification can't wrongly lock anyone out.spam-> complaintreject/open/click/unsubscribe/resubscribe/processed-> ignored.rejectspecifically is SMTP2GO refusing to send to an address that already hard-bounced/complained/unsubscribed, so it carries no new state.Recipient is read from SMTP2GO's
rcptfield (notemail).Auth and payload format
SMTP2GO doesn't sign payloads (confirmed against their docs - no HMAC), so the endpoint is guarded by a shared secret in the URL:
SMTP2GO_WEBHOOK_SECRET, constant-time compared against thetokenquery param, 404 when unset, 403 on mismatch. Pair with SMTP2GO's source-IP allowlist at the proxy for defence in depth, since the secret travels in the URL.SMTP2GO's output type is operator-configurable (JSON or form-encoded), so the endpoint accepts both rather than silently 400ing a webhook left on the wrong type - JSON may be a single object or an array, form-encoded is a single event. Parsing is a pure
parse_smtp2go_payloadhelper.Tests
tests/test_webhooks_smtp2go.py- 14 unit tests: thesmtp2go_event_actionmapper andparse_smtp2go_payload(JSON object, JSON array, content-type sniffing, form-encoded, empty/garbage -> error). Theapply_*transitions are covered bytest_email_events.py. ruff clean.Note
The endpoint is exercised here at the mapper/parser level; an end-to-end test against a live SMTP2GO event fire (and confirming whether SMTP2GO batches events into a JSON array vs one-per-POST) is worth doing when wired up - the code handles both shapes regardless.