# Contributing to Mailglass
We welcome contributions! Mailglass is developed using a phase-based roadmap found in [`.planning/PROJECT.md`](.planning/PROJECT.md).
## Local Setup
1. Clone the repo.
2. Install dependencies: `mix deps.get`.
3. Setup the test database: `mix ecto.setup` (or `mix ecto.create -r Mailglass.TestRepo`).
4. Run tests: `mix test`.
## Development Workflow
1. Create a branch.
2. Implement your changes and add tests.
3. Run the full verification suite: `mix verify.phase_07`.
4. Submit a PR.
## Commit Guidelines
Use Conventional Commits:
- `feat: ...` for new features
- `fix: ...` for bug fixes
- `docs: ...` for documentation changes
- `chore: ...` for maintenance
## PR Expectations
- All CI checks must pass.
- New features must include documentation and tests.
- Maintain atomic commits.
## Why we sed mix.exs after release-please runs
Release Please's `extra-files` generic updater silently no-ops on a `mix.exs`
already managed by the `elixir` release-type. The `{:mailglass, "== <ver>"}`
pin in `mailglass_admin/mix.exs` therefore never gets rewritten by the action
itself.
`.github/workflows/release-please.yml` syncs the pin via a `sed` step on the
release-please PR branch after the action runs. This is the **steady-state
mitigation** rather than authoring a TypeScript plugin (which would violate
the "no Node toolchain anywhere" engineering DNA) or refactoring to
`version.exs` (which adds Hex tarball + `Code.eval_file` load-order risk).
**Recursion-safety guarantee:** the sync push uses `GITHUB_TOKEN`, which by
GitHub's anti-recursion guarantee does NOT trigger further workflow runs.
**Sed-anchor stability:** `mailglass_admin/test/mailglass_admin/mix_config_test.exs`
asserts the dep tuple in `mailglass_admin/mix.exs` matches the literal
`{:mailglass, "== <semver>"}` shape the sed regex anchors on. Any future
rename of the dep tuple form will fail this test loudly — update the sed
regex (in `release-please.yml`) and this section together.
**Pointer:** see `.planning/todos/pending/2026-04-26-release-please-extra-files-no-op-on-managed-mix-exs.md`
for the empirical observation history.
## One-time setup: branch protection automation
`main` is protected with required status checks (`Tests`, `Credo Strict`,
`Dialyzer`, `actionlint`, `PR title (semantic)`). This protection is
configured idempotently by `scripts/setup_branch_protection.sh` and
re-asserted daily by `.github/workflows/branch-protection-drift.yml`.
To enable the drift-detection workflow, add a repo secret
`BRANCH_PROTECTION_PAT`:
1. Generate a fine-grained PAT scoped to `szTheory/mailglass` with
**Administration: Read and write** permission. (Settings → Developer
settings → Personal access tokens → Fine-grained tokens.)
2. Add it as a repo secret named `BRANCH_PROTECTION_PAT`. (Repo settings
→ Secrets and variables → Actions → New repository secret.)
3. Run `Branch Protection Drift` once via the Actions tab to confirm.
Without the secret, the drift workflow no-ops and posts a notice in its
workflow summary. Without it, you can still call the script directly:
```bash
GH_TOKEN=<admin-PAT> scripts/setup_branch_protection.sh main
```
## Verifying the Tests gate blocks failing PRs
`scripts/check_tests_gate.sh` runs in CI's `actionlint` job and fails
if `continue-on-error: true` is reintroduced on the Tests job. Static
guard.
For an end-to-end check, run the `Gate Self-Test` workflow via the
Actions tab. It creates a temporary branch with a synthetic
`assert false`, opens a draft PR, polls until the Tests check
finishes, asserts FAILED, then closes the PR and deletes the branch.
~5 minutes round-trip.