# Maintaining Mailglass
This document covers the release flow and maintenance protocols for Mailglass.
## Release Flow
Mailglass uses [Release Please](https://github.com/googleapis/release-please) to automate versioning and changelogs.
1. Merge feature branches into `main` using Conventional Commits.
2. Release Please will open a "Release PR" with the version bump and updated `CHANGELOG.md`.
3. Merging the Release PR should trigger the `publish-hex` workflow from the
published GitHub Release. If downstream workflow fan-out does not happen,
`workflow_dispatch` with the core release tag (`mailglass-v<version>`) is the canonical maintainer
fallback.
4. The `publish-hex` workflow is environment-gated and requires manual approval in the GitHub Actions UI.
## Snapshot Update Protocol
When the installer output or golden files change:
1. Run `mix verify.installer.golden`.
2. If the failure is expected, update the golden files in `test/fixtures/`.
3. Commit the updated fixtures with a `chore: update installer golden files` message.
## Required Checks
Before merging any PR, ensure:
- `mix verify.stability_contract`
- `scripts/verify_support_contract.sh`
- `Support Contract Core`
- `Support Contract Admin`
- `Compile No Optional Deps`
- `mix credo --strict`
- `mix dialyzer`
- `mix 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 Core`
- `Support Contract Admin`
- `mailglass_inbound` docs contract (`mailglass_inbound/test/mailglass_inbound/docs_contract_test.exs`)
- `Compile No Optional Deps`
When those checks pass, they prove the current compatibility contract described
in [`guides/compatibility-and-deprecations.md`](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:
- `Core Full Suite Advisory`
- `Provider Compatibility Advisory`
- `Provider Live Advisory`
`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 gated on a GitHub
Environment (`hex-publish`) with a single required reviewer (`szTheory`). When a
GitHub Environment has only one reviewer, GitHub silently disables the
`prevent_self_review` setting — the gate is effectively a one-eye pause, not a
two-eyes review. This is documented honestly here rather than presented as a
stronger 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."
1. **Data-loss / security / signature bypass / fails to compile.**
Run `mix hex.retire <pkg> <ver> security|invalid --message "<140 chars>"`
AND ship `<ver+1>` immediately.
2. **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.md` in the same patch.
3. **Cosmetic / docs / non-runtime.**
Do NOT retire. Roll into next planned patch.
4. **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).
5. **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/phases/38-release-rehearsal-and-proof-artifacts/38-03-RELEASE-CHECKLIST.md`
- `.planning/phases/38-release-rehearsal-and-proof-artifacts/38-03-RELEASE-RECORD.md`
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.
1. **Verify CI green on `main` for the SHA to be released.**
Check `actions/workflows/ci.yml` — required because publish-hex.yml gates
on this SHA via the `gate-ci-green` job (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)`
- Phase 38 prepublish proof/export bundle (`38-01-PREPUBLISH-PROOF.md`)
- Phase 38 install/upgrade rehearsal artifact (`38-02-REHEARSAL-EVIDENCE.md`)
2. **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>`
and `mailglass_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 a `Release-As: <intended-version>` footer. Do not hand-edit
`.release-please-manifest.json` to force the version.
3. **Approve the `hex-publish` deployment in the GitHub Environment UI.**
Review the pre-publish summary in the workflow run page (rendered by the
`prepublish-summary` job per D-15) BEFORE clicking Approve. Verify the
file count, total size, CHANGELOG excerpt, and top files all match
expectations.
Record the tag, publish workflow run URL, approver identity, and approval
timestamp in `38-03-RELEASE-RECORD.md`.
- **Package order:** The workflow guarantees `mailglass` (core) publishes first; once core is indexed, `mailglass_admin` and `mailglass_inbound` publish in parallel against the newly live core.
- **Idempotency:** All three publish steps check `mix hex.info` first 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-hex` did not fan out, dispatch `.github/workflows/publish-hex.yml` manually (with `package=all` and `dry_run=false`). **Do not dispatch from `main`**. Always use the reviewed release tag (for `1.0.0`: `mailglass-v1.0.0`) so the publish run is pinned to the exact commit Release Please tagged.
4. **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.0.0"}, {:mailglass_admin, "== 1.0.0"}, {:mailglass_inbound, "== 0.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.
5. **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).