Added an admin page for sending emails #461
No reviewers
Labels
No labels
Kestra
bug
enhancement
someday
subtask
☁️ api
🎛️ infrastructure
🐞 sentry
📆 2025 Season
📝 pages
allpicks
📝 pages
picks
📝 pages
standings
🚀 performance
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
johnsturgeon/tgfp-web!461
Loading…
Reference in a new issue
No description provided.
Delete branch "create-an-admin-page-where-i-can-email-everybody-my-own-text-445"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Resolves #237
Resolves #445
I have enough to write the review. Let me verify one detail about the markdown preset behavior from documentation knowledge and finalize.
Based on my review of the code and surrounding context, here is the review:
Review: Admin page for sending emails
This is a well-structured, well-tested feature: the mailer refactor into a shared
email_base.j2+_footer_body()removes real duplication, and the route's per-recipient send-and-report loop is clean. Most of my notes are about server-side validation and one non-obvious markdown default; nothing here is a hard blocker.Worth fixing
MarkdownIt("commonmark")passes raw HTML through —app/mailer.py:70. Thecommonmarkpreset setshtml: True(unlike thedefaultpreset), so raw HTML embedded in the composed markdown is emitted verbatim and then rendered with{{ body_html | safe }}inemail_custom.j2. The UI (cheat sheet, "Some markdown." placeholder) implies markdown-only input, so this is almost certainly unintended. Severity is low — the composer is an authenticated admin and the on-page preview is a sandboxed iframe — but if the intent is "markdown only," construct withMarkdownIt()(thedefaultpreset,html=False) or passMarkdownIt("commonmark", {"html": False}). Worth confirming the actualhtmloption value against the pinnedmarkdown-it-py>=4.2.0before merging, since I couldn't execute it in this sandbox.Subject/body are only validated client-side —
app/routers/v2.py:557-568. The empty-recipients case is guarded on the server, but emptysubject/bodyare only stopped by the JSsubmithandler inadmin_email.j2. A JS-disabled browser or a crafted POST will send blank-subject/blank-body emails to real players. Consider mirroring the recipients check server-side (redirect back with a flash) rather than relying on the alert.A full-pool send is N sequential SMTP round-trips inside one request —
app/routers/v2.py:571-581. Each selected player triggers a separatesend_custom_email→FastMail(conf).send_message(new connection per email), awaited one at a time. For a large active roster this can make the request run for many seconds and risk a gateway/proxy timeout — and there's no idempotency, so a client retry re-sends. The per-recipient split is the right call for privacy and personalization, but this reads like a good candidate for the existing scheduler/background-job pattern (seeresend_welcomeand the jobs infra) rather than doing it inline in the handler.Nits
The "What went out" preview greets the admin, not the recipients —
app/routers/v2.py:588rendersemail_custom.j2withcustom_email_body(body, admin.first_name), so the bounce page shows "Hi {admin}!" even though each actual email used the recipient's first name. Fine as a representative preview, but slightly misleading given the "exactly as it went out" framing in the docstring.No test covers the empty subject/body path — the suite thoroughly covers recipient selection, partial failure, and markdown rendering, but since subject/body validation is client-only there's nothing pinning server behavior for a blank composition. If you add the server-side check above, add a test with it.
Version bump 2.23.2 → 2.25.0 skips a minor; harmless, just noting it's a two-step jump for one PR per the CLAUDE.md "bump once per PR" convention.