Use the README as the primary setup lane. This guide keeps the Postmark-specific details focused: raw-body verification, configuration, duplicate behavior, and the exact receive truth the package stores.
Mount Path
Mount the package-local plug on one obvious route:
forward "/inbound/:tenant_id/postmark",
MailglassInbound.Ingress.Plug,
provider: :postmark,
router: MyApp.MailglassInboundRouterThe ingress plug is verify-first:
- read exact request bytes from
conn.private[:raw_body] - verify Postmark Basic Auth and optional IP allowlist
- resolve tenant scope
- normalize into
%MailglassInbound.InboundMessage{} - persist one canonical row plus one raw evidence row
- dispatch mailbox execution only for newly inserted records
Plug.Parsers Wiring
Your endpoint must wire the package body reader:
plug Plug.Parsers,
parsers: [:json],
pass: ["*/*"],
json_decoder: Jason,
body_reader: {MailglassInbound.Ingress.CachingBodyReader, :read_body, []}Without that body_reader, the ingress plug returns a config error instead of
attempting verification on reconstructed payload state.
Configuration
Configure the Postmark verification seam through :mailglass_inbound:
config :mailglass_inbound, :postmark,
basic_auth: {"postmark-user", "postmark-pass"},
ip_allowlist: []Basic auth is the default protection seam. IP allowlisting is optional and fails closed when enabled.
Persistence Semantics
A verified inbound request writes two truths:
- one canonical normalized row in
mailglass_inbound_records - one linked raw evidence row in
mailglass_inbound_evidence
The raw evidence row carries payload JSON, selected raw headers, verification
facts, parse warnings, and attachment blobs. raw_mime stays nil unless
Postmark provides a trustworthy raw artifact directly. The package does not
reconstruct raw MIME from parsed fields.
If the record is new and the router finds a mailbox, execution is dispatched after persistence commits. Oban-backed execution is the durable path. Without Oban, Task.Supervisor fallback is bounded best-effort only.
Route Compatibility
The route compatibility contract stays narrow:
- mailbox matching is evaluated against the stored canonical record
:no_matchremains explicit and non-exceptional- duplicate ingress does not create new routing or replay state
Duplicate Handling
Postmark retries and manual retries collapse on the canonical idempotency anchor:
(tenant_id, provider, provider_message_id)
The plug returns an explicit duplicate success outcome instead of pretending a
new inbound receive occurred, and it does not dispatch mailbox execution again.
Replay Honesty
Replay operates on stored canonical plus raw evidence truth. It is not a fresh provider receive, it does not silently reroute to a different mailbox, and it remains an internal replay recovery path rather than a public API.