# QA procedure: transactional email templates ## Automated (Go, `test/`) This slice touches a use-case call site (a string literal) and two static template files; it has no new HTTP surface of its own, so no new handler/router wiring is exercised here. Reuse the existing fakes already in `test/` (`mockMailRepo` from `test/mail_test.go`, the fake `user.Repository` used by the registration and password-reset tests) rather than building new ones. 1. **Registration sends "verification".** In the existing registration test file (or its replacement after this slice), build `CreateUserUseCase` over an empty fake `user.Repository`, a real hasher, and `mockMailRepo`, call `CreateUser` with username `alice`, password `hunter2example12`, email `alice@example.com`, assert the call succeeds, then assert `mockMailRepo.SentEmails` has exactly one entry whose `Template` field equals `"verification"` (not `"mailValidation"`). 2. **Password reset sends "password-reset".** Build `RequestPasswordResetUseCase` over a fake `user.Repository` seeded with a user whose email is `alice@example.com`, a real hasher, `mockMailRepo` and a public URL, call `RequestPasswordReset("alice@example.com")`, assert it returns `nil`, then assert `mockMailRepo.SentEmails` has exactly one entry whose `Template` field equals `"password-reset"` (not `"passwordReset"`). 3. **render() produces the right subject, link and copy for each known template.** Construct a real `mailer.NewMailRepository(config.SmtpSettings{})` (SMTP fields are irrelevant since `DialAndSend` is never called here) and call its render step directly (test lives in package `test`, so this exercises the exported behavior surface of a real `*mailer.MailRepository` without opening a network connection): - `render("verification", )` → subject `"Verify your RenewCMS account"`, body contains `https://cms.example.com/register/validate?c=abc123`, `Verify my account`, `This link expires in 2 hours.` - `render("password-reset", )` → subject `"Reset your RenewCMS password"`, body contains `https://cms.example.com/register/reset/validate?c=abc123&email=alice%40example.com`, `Choose a new password`, `This link expires in 1 hour and works once.` (`html/template` auto-escapes `Email` in the query string, turning `@` into `%40`; assert the escaped form, matching what `MailRepository.Send` will actually produce.) 3b. **render() fallback and failure paths.** - `render("does-not-exist", nil)` returns an error (template file not found) and the returned subject is still the old default `"RenewCMS | Action required"`, with an empty body. - A `render(, )` call returns an error; assert `MailRepository.Send` with the same inputs returns an error too and never gets to `DialAndSend` (no network dependency needed to prove this: a failing `render` return is sufficient to prove `Send` short-circuits before dialing, since `Send`'s existing structure calls render, then returns immediately on its error). 4. **Old template files are gone.** `os.Stat("internal/infrastructure/mailer/templates/mailValidation.html")` and the `passwordReset.html` equivalent each return an error satisfying `os.IsNotExist`. 5. **Existing slice tests still pass with updated literals.** Run the full suite (`task test`) and confirm the tests backing `registration_gate_and_verification_email.feature` and `request_password_reset.feature` still pass now that their scenario text and any Go assertions inside their test files use `"verification"` and `"password-reset"` instead of the old strings. If those test files still contain the old literals as Go string constants (not just prose in the `.feature` files), they need the same rename; point this out to `coder` rather than silently doing a code edit here. ## Manual verification (not covered by the automated Go tests above) - **Rendered appearance in an actual mail client.** The bulletproof button (table-cell background + padded link), the hidden preheader text, and the `prefers-color-scheme: dark` overrides can only be judged by opening the rendered HTML in a real or preview mail client (or the board file `docs/design/source/RenewCMS Emails.dc.html` next to a manually triggered send through a real SMTP server) — this is not something `os.ReadFile` + `html/template.Execute` can verify. Confirm both light and dark rendering for both templates. - **Real SMTP delivery end to end.** Sending an actual email via `SMTP_HOST`/`SMTP_PORT` and eyeballing the arrived message (subject line, sender name "RenewCMS <...>", clickable button, monospace fallback URL) is a manual item; nothing here exercises `gomail.Dialer.DialAndSend`.