This document covers the release flow and maintenance protocols for Mailglass.
Release Flow
Mailglass uses Release Please to automate versioning and changelogs.
Before release work starts, run:
mix mailglass.repo.hygiene --checkThe release branch must start from a clean worktree with no local ahead/behind
drift from origin/main. If local work exists, preserve it on a named
preserve/* branch before release work continues.
- Merge feature branches into
mainusing Conventional Commits. - Release Please will open a "Release PR" with the version bump and updated
CHANGELOG.md. - Merging the Release PR creates the GitHub Release with
RELEASE_PLEASE_TOKENsorelease: publishedfan-out can trigger publish and smoke workflows. If downstream workflow fan-out does not happen,workflow_dispatchwith the core release tag (mailglass-v<version>) is the canonical maintainer fallback. - Publishing is hands-free after CI is green:
release-pleaseauto-merges the release PR,gate-ci-greenis the publish gate, and thehex-publishenvironment has no required reviewers.
Trust runner checkpoint handoff
Use mix verify.reference_host.journey as the canonical trust-runner command.
By default it writes the checkpoint artifact to
tmp/mailglass_trust_runner/checkpoint.json.
This trust-runner flow and reference-host evidence are usage-proof artifacts,
not API-contract truth. Stable guarantee semantics are defined by the canonical
stability inventories in docs/api_stability.md and
mailglass_inbound/docs/api_stability.md,
with executable contract truth enforced through
mix verify.stability_contract.
Checkpoint consumers should require these keys exactly:
schema_versionclaim_boundarycheckpoint_countcheckpoint_sha256checkpoints
Validate artifacts with
bash scripts/check_trust_runner_checkpoint.sh --checkpoint tmp/mailglass_trust_runner/checkpoint.json.
Phase 58 extends this contract with signed-negative webhook and non-happy-path diagnosis semantics; it does not redefine or rename the Phase 57 stage keys.
Snapshot Update Protocol
When the installer output or golden files change:
- Run
mix verify.installer.golden. - If the failure is expected, update the golden files in
test/fixtures/. - Commit the updated fixtures with a
chore: update installer golden filesmessage.
Publish Summary Snapshot Protocol
The files under .planning/publish/*-publish-summary.json are tracked release
proof snapshots, not scratch output.
- Refresh them with
mix mailglass.publish.checkfor the affected package(s). - Review the diff together with the paired
*-files.expectedallowlist diff. - Commit the snapshot update when the underlying package contents or version truth changed intentionally.
Do not gitignore these files: test/mailglass/stability_contract_test.exs
reads the inbound summary directly as part of the sibling-package release
contract.
JTBD Docs Refresh Protocol
The JTBD docs are a two-file system:
guides/jobs.mdis the public adopter ramp-up guide.planning/research/JTBD-COVERAGE.mdis the internal source of truth
Always refresh the internal map first, then project the stable Built rows into the public guide.
Refresh order
- Read the current:
guides/jobs.md.planning/research/JTBD-COVERAGE.mdREADME.mdCHANGELOG.md.planning/PROJECT.md.planning/ROADMAP.md.planning/REQUIREMENTS.md.planning/STATE.md
- Reconcile shipped behavior against live code. When planning artifacts
disagree, prefer live code, then
PROJECT.md, thenROADMAP.md, then phase summaries/verification, and treatSTATE.mdas last-resort bookkeeping. - Run a primary-source ecosystem sanity check before changing priority claims.
Current comparison set:
- Rails Action Mailer
- Rails Action Mailbox
- Anymail
- Laravel Mail
- Resend inbound docs
- Update
.planning/research/JTBD-COVERAGE.md:- refresh built/planned/deferred statuses
- refresh the active gap list
- refresh the priority ordering
- refresh the diminishing-returns line
- append a row to the refresh log
- Update
guides/jobs.mdfrom that map:- stable shipped jobs only
- keep the narrative, adopter-facing framing
- keep inbound summarized separately, noting its own independent
1.0contract and routing readers tomailglass_inbound/docs/api_stability.md
- Refresh dates in both files with exact calendar dates.
- Update README or docs navigation only if the JTBD docs became harder to discover.
- Run the docs contract tests before merging.
Guardrails
- Do not let
guides/jobs.mdbecome a roadmap doc. - Do not let
JTBD-COVERAGE.mdbecome feature-inventory churn; it is about adopter jobs, gaps, and priority. - If external research reveals only convenience asks, do not promote them above trust-proof or inbound-maturity work.
Required Checks
Before merging any PR, ensure:
mix verify.stability_contractscripts/verify_support_contract.shSupport Contract CoreSupport Contract AdminCompile No Optional Depsmix credo --strictmix dialyzermix docs --warnings-as-errors
The honest repo-root entrypoint is mix verify.stability_contract or
scripts/verify_support_contract.sh. They run the three required
branch-protection buckets plus the inbound sibling-package docs lane in sequence:
Support Contract CoreSupport Contract Adminmailglass_inbounddocs contract (mailglass_inbound/test/mailglass_inbound/docs_contract_test.exs)Compile No Optional Deps
Branch protection truth is narrower than "everything we like to run in CI". The exact required contexts are:
Support Contract Core (Elixir 1.18 / OTP 27)Support Contract Admin (Elixir 1.18 / OTP 27)Compile No Optional Deps (Elixir 1.18 / OTP 27)Trust Lane Repo Head (Elixir 1.18 / OTP 27)Installer Host Smoke(shift-left consumer-install smoke; promoted from advisory)
Release trust claims also require green trust evidence beyond the required
branch-protection contexts: the clean-baseline and published-version trust
journeys must complete, and the trust-runner-repo-head,
trust-runner-clean-baseline, and trust-runner-published checkpoint artifacts
must be present and valid.
Owner-applied branch protection:
GH_TOKEN=<admin-pat> ./scripts/setup_branch_protection.sh main
Read-only branch-protection verification:
./scripts/verify-branch-protection.sh --print-expected./scripts/verify-branch-protection.sh --print-expected-jsonGH_TOKEN=<admin-pat> ./scripts/verify-branch-protection.sh main
When those checks pass, they prove the current compatibility contract described
in guides/compatibility-and-deprecations.md:
runtime floors, matched sibling-package docs wiring for mailglass_inbound,
matched mailglass_admin release truth, and the required-vs-advisory split
below. Do not claim broader support than those repo artifacts prove.
The following checks are advisory signal, not branch-protection truth:
Format CheckCompile Warnings as ErrorsMix Task TestsInbound TestInbound Compile No Optional DepsOperator Browser GatePreview Capture AdvisoryCore Full Suite AdvisoryProvider Compatibility AdvisoryBranch Protection AdvisoryProvider Live Advisory
Required inbound release proof is deterministic repo/package/workflow evidence:
source and manifest parity, mix mailglass.publish.check --package mailglass_inbound,
publish-summary output, release workflow tag/package selection, and post-publish
Hex/HexDocs/smoke evidence when that publish phase runs.
Provider-live checks and ecosystem canaries remain advisory unless a specific
release claim explicitly depends on them.
Provider Live Advisory remains a cron and workflow_dispatch canary. It is not a merge blocker.
Bus Factor & Continuity
Mailglass is single-maintainer at v0.1. The release pipeline is intentionally
hands-free after the repo-proved gates pass: gate-ci-green checks the release
SHA and the hex-publish environment has no required reviewers. This is
documented honestly here rather than presented as a stronger human approval
control than it is. Multi-owner Hex transition is deferred to v0.5, when
production adopters exist (D-26 rationale: at v0.1 the asymmetry of a co-owner
being able to mix hex.publish from their own machine bypassing GitHub
governance is a worse footgun than the bus-factor risk it solves).
If szTheory is unreachable for more than 30 days, the community can request a
Hex.pm package transfer by opening a public issue titled
Maintainer-unreachable: requesting Hex transfer on
https://github.com/szTheory/mailglass/issues — Hex.pm's public maintainer-transfer
process can be initiated from there.
Retract Decision Tree
Five rules. Bias toward patch over retract — three retractions in your first six months tells evaluators "don't bet on this lib."
- Data-loss / security / signature bypass / fails to compile.
Run
mix hex.retire <pkg> <ver> security|invalid --message "<140 chars>"AND ship<ver+1>immediately. - User-visible breakage with workaround.
Do NOT retire. Patch within 7 days. Add a CHANGELOG entry.
If the fix changes a documented compatibility bridge or support claim, update
guides/compatibility-and-deprecations.mdin the same patch. - Cosmetic / docs / non-runtime. Do NOT retire. Roll into next planned patch.
- Published less than 60 minutes ago AND zero downloads.
Run
mix hex.publish --revert <ver>(only window where unpublish works — also bounded by Hex.pm's 24-hour initial-release window). - Already retired and false alarm.
Run
mix hex.retire <pkg> <ver> --unretire.
Security Response SLA
Single-maintainer numbers, written to be kept rather than aspired to.
- Acknowledgement of report: within 72 hours.
- Mitigation or workaround for critical issues: within 14 days.
- Public security advisory: published alongside the fix.
Critical issue classes are listed in SECURITY.md (## Critical Classes).
Reports go through the disclosure address documented there or via GitHub
Private Vulnerability Reporting if no email is reachable.
Release Runbook
Five steps. Step 4 has a literal 60-minute timer — that is the last revert window before the published artifact becomes permanent.
Use the Phase 38 release-day proof forms while running these steps:
.planning/milestones/v1.0-phases/38-release-rehearsal-and-proof-artifacts/38-03-RELEASE-CHECKLIST.md.planning/milestones/v1.0-phases/38-release-rehearsal-and-proof-artifacts/38-03-RELEASE-RECORD.md
For the inbound-only mailglass_inbound 1.0.0 slice, use the inbound-specific companion forms:
.planning/phases/73-inbound-1-0-publish-evidence/73-01-RELEASE-RECORD.md.planning/phases/73-inbound-1-0-publish-evidence/73-01-RELEASE-CHECKLIST.md
The archived Phase 38 forms remain the linked core/admin v1.0 record; the Phase 73 forms cover the inbound-only slice.
The checklist separates repo-proved gates from manual/external proof and forces explicit capture of the tag, workflow run URLs, approver identity, fallback usage, Hex/HexDocs checks, branch-protection result, and 60-minute outcome.
Verify CI green on
mainfor the SHA to be released. Checkactions/workflows/ci.yml— required because publish-hex.yml gates on this SHA via thegate-ci-greenjob (per Plan 08, D-16). The required release-truth buckets are:Support Contract Core (Elixir 1.18 / OTP 27)Support Contract Admin (Elixir 1.18 / OTP 27)Compile No Optional Deps (Elixir 1.18 / OTP 27)Trust Lane Repo Head (Elixir 1.18 / OTP 27)Installer Host Smoke(shift-left consumer-install smoke; promoted from advisory)- Phase 38 prepublish proof/export bundle (
38-01-PREPUBLISH-PROOF.md) - Phase 38 install/upgrade rehearsal artifact (
38-02-REHEARSAL-EVIDENCE.md) - Trust-runner checkpoint artifacts:
trust-runner-repo-head,trust-runner-clean-baseline, andtrust-runner-published
The post-publish trust journey is the EVID-03 sentinel. It must be green before milestone trust claims or v1.3 closeout language is accepted.
Merge the release-please PR. Squash-merge keeps the changelog history linear. Review the release PR diff before merge. This repo uses a custom mailglass_admin dep-pin sync step, so the generated PR is load-bearing. The current release path emits package tags such as
mailglass-v<version>andmailglass_admin-v<version>. If a broad milestone PR was squash-merged under a non-releasable subject and release-please skips the cut, recover with a tiny follow-up commit that carries aRelease-As: <intended-version>footer. Do not hand-edit.release-please-manifest.jsonto force the version.Monitor the hands-free publish fan-out. Review the pre-publish summary in the workflow run page (rendered by the
prepublish-summaryjob per D-15) aftergate-ci-greenpasses and the publish jobs fan out. Verify the file count, total size, CHANGELOG excerpt, and top files all match expectations. Record the tag, publish workflow run URL,gate-ci-greenresult, and publish fan-out status in38-03-RELEASE-RECORD.md.- Package order: The workflow guarantees
mailglass(core) publishes first, thenmailglass_inbound, thenmailglass_admin. Admin waits on inbound to avoid sibling-package Hex indexing races. - Idempotency: All three publish steps check
mix hex.infofirst and skip the publish command if the version is already live, making the workflow safe to retry. - Fallback path: If the Release Please tag/release exists but
publish-hexdid not fan out, dispatch.github/workflows/publish-hex.ymlmanually (withpackage=allanddry_run=false). Do not dispatch frommain. Always use the reviewed release tag for the package being recovered so the publish run is pinned to the exact commit Release Please tagged. For an inbound-onlymailglass_inbound-v1.0.0publish or recovery, dispatchpackage=mailglass_inboundpinned to themailglass_inbound-v1.0.0tag; the fan-out skipspublish-coreand does NOT triggerpublish-admin, so nomailglass/mailglass_adminrelease is forced. Thepublish-inbound/publish-adminsuccess/skipped gating is a security control — do not loosen it.
- Package order: The workflow guarantees
Within 60 minutes of publish: smoke-install in a fresh Phoenix app. Set a literal timer when approving the deployment. Run:
mix archive.install hex phx_new --force mix phx.new sandbox --no-ecto --no-mailer --install cd sandbox# add {:mailglass, "~> 1.3"}, {:mailglass_admin, "~> 1.3"}, {:mailglass_inbound, "~> 1.0"} to deps
mix deps.get && mix mailglass.install && mix compile --warnings-as-errors mix phx.server # visit http://localhost:4000/dev/mail/If anything fails AND the publish was less than 60 minutes ago AND zero downloads have happened, the Retract Decision Tree rule 4 (
mix hex.publish --revert) is reachable. After 60 minutes the only options are retire-then-patch (rule 1) or patch-only (rule 2).
Keep the published support story honest: if the smoke or support-contract
checks reveal a mismatch with the documented matrix or upgrade posture, fix
the guide and package metadata together rather than carrying split truth.
For inbound-slice changes, rerun mix verify.stability_contract so the
repo-root lane proves the canonical mailglass_inbound docs and support
posture before you publish.
If you need to reproduce the v0.2 codemod or rollback story during this
window, do it in a disposable fixture or git-clean worktree only. The
public rollback contract is git-based review/revert of the upgrade diff,
not cleanup of arbitrary dirty repositories.
The post-publish-smoke workflow (.github/workflows/post-publish-smoke.yml,
Plan 09) runs the same smoke automatically — but it does not respect the
60-minute window. Run the manual smoke during the window regardless.
If publish succeeds but smoke does not fan out, use workflow_dispatch on
.github/workflows/post-publish-smoke.yml with that same core tag.
Record the post-publish smoke run URL, whether fallback dispatch was used,
Hex/HexDocs URLs, and the final 60-minute decision in the Phase 38 release
record.
- Post the release link to Elixir Forum #libraries section (post-publish, optional — performed by maintainer on their own cadence; not gated by Phase 07.1's milestone-shipped marker per CONTEXT line 14 / line 351). Body equals the GitHub Release narrative (CHANGELOG entry verbatim plus one framing paragraph for 0.x.0 minor bumps; verbatim CHANGELOG only for patches).