This document is the canonical contract inventory for the shipped
mailglass_inbound slice.
For this package, stability is semantics-first. ExDoc visibility, generated documentation reachability, and module reachability do not define the contract by themselves. The contract is the explicit inventory in this file plus the documented command, telemetry, error, and testing semantics named here.
It answers four questions:
- Which runtime and operator semantics are stable now.
- Which test helpers are adopter-facing testing support.
- Which reachable modules are internal implementation details.
- Which capabilities remain deferred and must not be inferred from source reachability.
Contract Posture
stable
These surfaces are part of the documented inbound adopter contract:
MailglassInboundandMailglassInbound.version/0MailglassInbound.InboundMessageMailglassInbound.Ingress.PlugMailglassInbound.Ingress.CachingBodyReaderMailglassInbound.RouterMailglassInbound.MailboxMailglassInbound.PubSub.Topics- stable Mix task behavior for
mix mailglass.inbound.doctor,mix mailglass.inbound.replay, andmix mailglass.inbound.prune - PII-safe telemetry families under the
[:mailglass_inbound, ...]names documented below - stable structured errors
MailglassInbound.MIMEError,MailglassInbound.SignatureError, andMailglassInbound.S3FetchError - the documented storage boundary between canonical normalized rows and raw evidence used for replay and audit truth
Stable means adopters may rely on:
- one canonical
%MailglassInbound.InboundMessage{}value object - one explicit manual setup path with
Plug.Parsersbody-reader wiring MailglassInbound.Ingress.Plugprovider semantics forprovider: :postmark | :sendgrid | :mailgun | :ses- verify before tenant resolution, tenant work, or persistence
- canonical normalized row plus raw evidence row persisted before mailbox execution is dispatched
- duplicate acknowledgement from durable receive truth rather than mailbox outcomes
- replay remaining distinct from fresh provider receipt semantics
- replay remaining distinct from fresh receive semantics
- Task.Supervisor fallback being bounded best-effort only when Oban is absent
- router matching by recipient, subject, and header only
- one mailbox callback,
process/1, with the documented outcomes only - operator tasks at the command-behavior level only
- telemetry stability at event-family and PII-safe metadata-shape level
- closed
:typesets for the stable inbound error structs documented below
testing
These surfaces ship in lib/ for adopters to drive and assert inbound flows in
their own suites. They sit alongside the stable runtime contract as a distinct
testing surface; they are not internal and they are not runtime APIs:
MailglassInbound.Fixtures- builders for canonical%MailglassInbound.InboundMessage{}values and raw provider payloads that round-trip through real provider verify/normalize seams.MailglassInbound.Test.Ingress- drives the real synchronous persist, route, and execute write path and captures the outcome in the test process. Drive routing through the:routeroption with a compileduse MailglassInbound.Routermodule. The internalMailglassInbound.Router.Routestruct is not part of the testing contract.MailglassInbound.TestAssertions-assert_inbound_*matchers reading the captured outcome.MailglassInbound.MailboxCase- theExUnit.CaseTemplateadoptersuse, which imports assertions, checks out the adopter repo sandbox, sets tenancy, and resets process-global fixture state.
internal
These surfaces may be exported, visible in generated docs, reachable in source, or used by first-party packages, but they are implementation details:
MailglassInbound.OptionalDepsMailglassInbound.OptionalDeps.ObanMailglassInbound.ExecutionMailglassInbound.Execution.WorkerMailglassInbound.Ingress.ProviderMailglassInbound.Ingress.Providers.PostmarkMailglassInbound.Ingress.Providers.SendgridMailglassInbound.Ingress.Providers.MailgunMailglassInbound.Ingress.Providers.SESMailglassInbound.Ingress.PersistMailglassInbound.Internal.DoctorMailglassInbound.Internal.ReplayMailglassInbound.Internal.PruneMailglassInbound.Prune.WorkerMailglassInbound.Router.Route- package-local persistence modules under
MailglassInbound.InboundRecords.* - repo and schema helpers used to stamp package-owned storage
- queue names, retry tuning, worker args, direct Oban job shapes, and direct
Obanintegration details - Task.Supervisor startup and process wiring
- admin or operator UI implementation details, DOM, CSS, LiveView modules, components, assigns, route structs, and event names
MailglassInbound.OptionalDeps.Oban is intentionally reachable so the package
can branch on Oban availability without forcing direct Oban references into
adopter code. Availability checks through this module are supported, but worker
modules, Oban job structs, queue names, and enqueue internals are not part of
the stable contract. These details are not part of the stable contract.
MailglassInbound.Ingress.Provider and the
MailglassInbound.Ingress.Providers.* modules are provider implementation
support only. Provider support is stable through MailglassInbound.Ingress.Plug
options and behavior, not through provider module APIs.
Replay, doctor, and prune orchestration modules are also internal. The package preserves the command semantics listed here, but internal modules, job structs, queue contracts, and worker arguments remain maintainer-owned.
deferred
These capabilities are explicitly deferred:
- public replay API and public replay rerouting controls
- public provider extension API
- public worker or queue contracts
- matcher expansion beyond recipient, subject, and header matching
- lifecycle callbacks beyond
process/1 - multi-route fan-out
- synthetic inbound development UI
gen_smtplistener support- ecosystem integrations
- operator or admin UI APIs for inbound inspection and replay
- body matching, attachment matching, raw MIME matching, and boolean predicate combinators
Deferred means the package does not promise these capabilities in the current contract, even if an internal name, TODO, test fixture, or first-party implementation detail mentions adjacent work.
Stable Inventory
MailglassInbound
Stable root helper for package identity.
version/0
MailglassInbound.InboundMessage
Stable canonical normalized inbound value object.
Documented field promises in this slice:
- tenant scope
- provider provenance
- provider message reference
- RFC
Message-ID - envelope recipient
- normalized sender and recipient fields
- subject
- normalized headers
- sent and received timestamps
- normalized text and HTML body fields
- normalized attachment manifest without attachment bytes
The stable struct intentionally excludes raw evidence, verification facts, replay identifiers, worker metadata, storage paths, and provider-only extras.
MailglassInbound.Ingress.Plug
Stable first-party provider ingress seam for provider: :postmark,
provider: :sendgrid, provider: :mailgun, and provider: :ses.
Documented guarantees:
- verifies provider authenticity before tenant resolution or persistence
- resolves tenant scope only after verification succeeds
- normalizes only into the locked
%MailglassInbound.InboundMessage{} - persists a canonical row plus raw evidence row before mailbox execution is dispatched
- acknowledges provider retries from durable receive truth instead of mailbox outcomes
- treats duplicate receives as durable receive truth, not mailbox re-execution
- keeps replay distinct from fresh provider receipt
- maps explicit rejection, tenant failure, config failure, duplicate, control-plane, replay, and S3 fetch outcomes through documented response semantics
- keeps provider modules internal; adopters configure the plug, not
MailglassInbound.Ingress.Provideror provider module APIs
Postmark and Mailgun signed payloads use verify-first request handling. SendGrid raw MIME ingress uses basic auth and raw MIME parsing. SES SNS ingress verifies SNS authenticity before persisting the canonical message and raw evidence.
MailglassInbound.Ingress.CachingBodyReader
Stable package-local Plug.Parsers helper used by the ingress plug.
Documented guarantees:
- stores exact bytes in
conn.private[:raw_body] - remains path-local and opt-in
- supports verify-first request handling for provider ingress
- is required for Postmark raw-body verification and not required for SendGrid raw MIME delivery
MailglassInbound.Router
Stable router authoring seam with first-match-wins semantics.
Documented guarantees:
- routes are evaluated top to bottom
- multiple clauses on one route are logical
AND - recipient, subject, and header matching are the only stable matchers
- exact string and regex support only
:no_matchis explicit and non-exceptional
MailglassInbound.Mailbox
Stable mailbox callback contract.
Documented guarantees:
process/1is the only stable callback- valid outcomes are
:accept,:ignore,{:reject, reason}, and{:bounce, reason} - raises, throws, and exits are execution failures handled by internal runners, not semantic mailbox outcomes
- replay uses stored canonical and raw evidence truth, but replay orchestration remains internal rather than public API
MailglassInbound.PubSub.Topics
Stable PubSub topic builder for inbound subscribers.
Documented guarantees:
- topic strings are derived through this module, never hand-built by adopters
- every topic carries the
mailglass:prefix required by the project's PubSub-topic convention - topics are tenant-scoped where the subscribed resource is tenant-owned
Mix tasks
mix mailglass.inbound.doctor
Stable operator behavior:
- exit code
0means the checked inbound wiring passes - exit code
1means findings were detected - exit code
2means the doctor could not complete because configuration or environment preconditions are missing --strict,--format json, and--verboseare documented command options- JSON output is for operator automation; internal module shapes remain private
mix mailglass.inbound.replay
Stable operator behavior:
- requires
--tenant - replays stored canonical and raw evidence truth
- uses
[y/N]confirmation unless--yesis supplied - never treats replay as a new provider receipt
- does not promise public replay API, public rerouting controls, worker args, queue names, or job struct contracts
mix mailglass.inbound.prune
Stable operator behavior:
- requires typed
yesconfirmation unless--yesis supplied - performs destructive retention cleanup according to documented options
- runs synchronously with or without Oban
- treats
MailglassInbound.Prune.Workeras optional scheduling support and internal implementation detail
Telemetry families
The stable telemetry contract is event-family and metadata-shape based:
[:mailglass_inbound, :ingress, :request, :start | :stop | :exception][:mailglass_inbound, :route, :match, :start | :stop | :exception][:mailglass_inbound, :persist, :record, :start | :stop | :exception][:mailglass_inbound, :execution, :run, :start | :stop | :exception][:mailglass_inbound, :ingress, :rate_limit, :start | :stop | :exception][:mailglass_inbound, :ingress, :suppression_flag, :start | :stop | :exception][:mailglass_inbound, :prune, :sweep, :start | :stop | :exception]
Telemetry remains stable at the documented event-name family and PII-safe metadata level. Helper functions, internal emitters, handler wiring, and UI consumers are not promoted to stable API. Metadata must not include raw payloads, body text, HTML, headers, sender or recipient addresses, subject lines, or other PII.
MailglassInbound.MIMEError
Stable structured error for raw MIME parse failures, matched by struct and
:type, never by message string.
Closed :type set:
:inbound_mime_invalid:gen_smtp_unavailable
Documented guarantees:
- raised or returned when raw MIME source cannot be parsed into a canonical
%MailglassInbound.InboundMessage{} - carries a closed, documented
:typeset so callers pattern-match on the struct rather than the message - does not leak raw MIME bytes or recipient PII in its message
MailglassInbound.SignatureError
Stable, no-recovery structured error for inbound provider signature
verification failures, matched by struct and :type, never by message string.
Closed :type set:
:bad_signature:missing_header:malformed_header:timestamp_skew:subscribe_url_untrusted
Documented guarantees:
- raised when an inbound Mailgun HMAC or SES SNS X.509 signature fails to
verify, or when an SNS
SubscribeURLorSigningCertURLfails trust-policy validation - the ingress plug maps it to a 401 with no recovery path
- excludes
:causeand:providerfrom serializedJason.Encoderoutput so signing secrets and raw payload fragments never leak - is package-local and does not implement the core
Mailglass.Errorbehaviour
MailglassInbound.S3FetchError
Stable structured error for AWS SES inbound S3-object fetch failures, matched
by struct and :type, never by message string.
Closed :type set:
:s3_object_not_ready- boundedGetObjectretry exhausted; transient, SNS redelivers because the handler does not ack:s3_fetch_failed- non-retryable S3 error
Documented guarantees:
- raised or returned when a SES receipt-rule S3 action object cannot be fetched for MIME parsing
- carries a closed, documented
:typeset so callers pattern-match on the struct rather than the message - excludes
:causefrom serializedJason.Encoderoutput so raw S3 or error fragments never leak - is package-local and does not implement the core
Mailglass.Errorbehaviour
Inventory Notes
- Stable does not mean everything ExDoc renders.
- Exported does not mean stable.
- Hidden docs do not make a surface private.
- Module reachability is not a compatibility promise.
- If a new inbound seam is meant to be stable for adopters, add it here and add matching docs-contract assertions.