Single-ingress webhook orchestrator (CONTEXT D-10).
Plugged at adopter-mounted paths via Mailglass.Webhook.Router
(Plan 05). Owns the full request lifecycle:
- Extract
raw_bodyfromconn.private[:raw_body](populated byMailglass.Webhook.CachingBodyReaderin the adopter'sPlug.Parsers:body_reader) - Dispatch to
Mailglass.Webhook.Providerimpl per route opts (provider: :postmark | :sendgrid) Provider.verify!/3— raises%SignatureError{}on failureMailglass.Tenancy.resolve_webhook_tenant/1(D-12) — runs AFTER verify (D-13)Mailglass.Tenancy.with_tenant/2BLOCK form — clean tenant cleanup on raise (Pitfall 7)Provider.normalize/2— pure, returns[%Event{}]Mailglass.Webhook.Ingest.ingest_multi/3— single Ecto.Multi insideRepo.transact/1(Plan 06, forward-declared)- Post-commit:
Mailglass.Outbound.Projector.broadcast_delivery_updated/3per matched delivery (Phase 3 D-04 — Plan 06 returns theevents_with_deliveries3-tuples for this loop) send_resp(conn, 200, "")
Response code matrix (CONTEXT D-10 + D-14 + D-21)
| Outcome | Status | Notes |
|---|---|---|
| Success | 200 | Normal happy path |
| Duplicate replay (UNIQUE collision) | 200 | Idempotent — provider sees no error |
| %SignatureError{} (any of 7+ atoms) | 401 | Logger.warning with provider + atom |
| %TenancyError{:webhook_tenant_unresolved} | 422 | Distinct from signature failure |
| %ConfigError{:webhook_caching_body_reader_missing} | 500 | Adopter wiring gap |
| %ConfigError{:webhook_verification_key_missing} | 500 | Missing provider secret |
| Ingest {:error, reason} | 500 | Logger.error with reason atom only |
Telemetry (CONTEXT D-22)
Emits [:mailglass, :webhook, :ingest, :start | :stop | :exception]
around the entire call/2 body via
Mailglass.Webhook.Telemetry.ingest_span/2 (Plan 08). Stop metadata
follows D-23 whitelist:
%{provider, tenant_id, status, event_count, duplicate, failure_reason}
— never IP, headers, or payload bytes.
Also emits [:mailglass, :webhook, :signature, :verify, :start | :stop | :exception] around Provider.verify!/3 via
Mailglass.Webhook.Telemetry.verify_span/2.
Failure log discipline (CONTEXT D-24)
Logger.warning on signature failure includes provider + atom
reason only. Never the source IP, headers, or payload excerpts.
Adopters wanting IP-based abuse triage attach their own telemetry
handler on [:mailglass, :webhook, :signature, :verify, :stop]
with status: :failed and pull conn.remote_ip from their own
plug lineage.
Forward-declared contracts
Mailglass.Webhook.Ingest.ingest_multi/3 is shipped by Plan 06
(Wave 3). This module references it directly; the @compile {:no_warn_undefined, ...} attribute below suppresses the warning
during mix compile --warnings-as-errors until Plan 06 lands.