DEV Community

Jonathan
Jonathan

Posted on

Git Diffs for Inbox Contract Reviews

Email API changes often look harmless in a pull request. A subject line moves, one new CTA appears, a retry path adds a second template, and the code diff still feels small. Then QA says the verification email no longer matches the test, support sees a mismatched link, and everyone starts re-reading snapshots instead of reviewing the change directly.

That is why I like keeping a tiny inbox contract beside the code and reviewing it with Git like any other artifact. It turns "did the message shape drift?" into a diffable question. For teams that already live in APIs and Developer Tools all day, this is one of the cheapest workflow upgrades you can make.

Why email API reviews stay fuzzy in Git

Most repos already version controllers, queue handlers, and test scripts. The email message itself is where process gets loose. One team stores giant HTML fixtures. Another saves screenshots in tickets. Another just reruns staging until somebody says "looks okay". The outcome is predictably messy.

I keep seeing this in signup and reset flows using a burner email address during checks. The app logic is reviewed carefully, but the actual inbox expectations are spread across shell scripts, mocks, and tribal memory. That is also where weird phrases like tem email or tepm mail com show up in old notes, and no one is fully sure which fixture was the source of truth.

The fix is not a bigger framework. It is a smaller review unit.

Reading scheduled inbox contracts reminded me how much better things go when inbox expectations are named and explicit. You do not need a fancy DSL. You need one file that tells reviewers what changed.

The inbox contract file I ask teams to diff

My preferred contract is plain JSON or YAML with only the fields that matter:

  • template or flow name
  • expected sender
  • expected subject pattern
  • one or two required body assertions
  • link host or path rule
  • retention note for the test enviroment

That is enough for useful review without dragging giant payloads into Git history.

Here is the shape I usually start with:

{
  "flow": "signup-verification",
  "from": "no-reply@example.com",
  "subject_contains": "Verify your account",
  "must_include": [
    "verification code",
    "expires in 15 minutes"
  ],
  "link_host": "app.example.com",
  "ttl_minutes": 20
}
Enter fullscreen mode Exit fullscreen mode

What I like about this file is that a reviewer can spot risky drift in seconds. If link_host changes, that deserves attention. If the subject changes from "Verify your account" to "Complete sign in", somebody should ask whether mobile and web are still aligned. The contract gives the diff some teeth.

A tiny Git workflow that surfaces risky changes

The workflow is intentionally boring:

  1. Keep one contract file per email flow.
  2. Update the contract in the same pull request as the mailer or API change.
  3. Add a CI step that compares the contract against one captured message in test.
  4. Fail the check when the contract changed but the sample assertion did not pass.

In practice I often wire the review step with a tiny shell helper:

git diff --name-only origin/main...HEAD \
  | rg 'contracts/email/.*\\.(json|ya?ml)$' \
  | while read -r file; do
      ./scripts/check-email-contract.sh "$file"
    done
Enter fullscreen mode Exit fullscreen mode

This pattern works well because it matches how developers already think. Review the changed file, run the check, read a short verdict, move on. No dashboard spelunking, no screenshot archaeology, no "can you resend that test email again?" loop. It is not magic, but it is especialy good for teams where API ownership rotates a lot.

I also pair this with run folder debugging habits. When a check fails, the run folder keeps the message metadata, extracted links, and contract verdict together. That makes handoff much smoother when the original author is offline.

How I keep fixtures readable during review

The biggest trap is over-modeling. Once the contract file tries to mirror the full HTML email, reviews get noisy again. I trim hard and keep only the assertions that protect user-visible behavior or routing.

A few rules have held up pretty well for me:

  • Prefer subject_contains over a full subject snapshot
  • Assert one core CTA, not every paragraph
  • Store normalized text, not rendered screenshots
  • Keep test-only IDs out of the committed contract
  • Note the intended TTL if a temporary inbox is part of the flow

This last part matters even when your team uses odd internal shorthand like tp mail so for a disposable inbox source. The repo should not depend on everyone remembering the shorthand. The contract should explain the expectation clearly enough that a new teammate can review it on day one, wich is a much better standard.

If you want one more shortcut, teach reviewers to comment on the contract file first, not the mailer code first. That simple habit changes the conversation from implementation detail to user-facing output. I have seen it reduce back-and-forth quite a bit, and it makes hidden copy or routing regressions easier to catch before merge.

Q&A

Should the contract live with tests or with the mailer?

Usually with tests, but near the flow it protects. The goal is discoverability, not ideological purity.

What if product changes copy frequently?

Use partial assertions. Lock the meaning, not every sentence. Otherwise the contract becomes a chore and people stop trusting it.

Is this only useful for large teams?

No. Small teams benefit fast because one teammate forgets less context between releases. The review trail also gets a lot cleaner.

When email checks are hard to review, teams start relying on memory and reruns. A small contract file turns that into a normal Git habit instead. That is a modest change, but it pays off surprsingly fast.

Top comments (0)