Skip to content

feat(2431): add email templates and send email test command#2479

Merged
ycanales merged 5 commits into
developfrom
cy/2431-email-templates
Jun 24, 2026
Merged

feat(2431): add email templates and send email test command#2479
ycanales merged 5 commits into
developfrom
cy/2431-email-templates

Conversation

@ycanales

@ycanales ycanales commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Issue: #2431

Summary & Context

Adds the templates and images (sync down from S3) for Email Confirmation and Password Reset.

Changes

  • Email templates (HTML and text)
  • Images uploaded to large_static S3 buckets
  • send_test_email command

‼️ Risks & Considerations ‼️

  • Should we move smaller images to the repo instead of S3?
  • [ ] Get email credentials so it's easier to test emails in local development.
  • To send some email tests just give me your email and I'll trigger the test command which is setup with my personal email sending API credentials.

Screenshots

Confirm email: HTML / Desktop

image

Confirm email: text

Welcome to Boost

Hi Vinnie,

Thanks for signing up for Boost. To activate your account, please confirm
your email address by visiting the link below.

This link expires in 24 hours.

Confirm my email:
https://www.boost.org/auth/confirm?token=test-confirm-token-abc123

If you didn't create a Boost account, you can safely ignore this email.

--
Boost.org is supported by grants from The C++ Alliance.

Want to change how you receive these emails?
You can update your preferences or unsubscribe from this list.

Confirm email: HTML mobile

image

Password reset: HTML Desktop

image

Password reset: HTML mobile

image

Password reset: text

Reset your password

Hi Vinnie,

We received a request to reset the password for your Boost account
(vinnie@cppalliance.org).

Click the link below to set a new password. This link will expire in 1 hour.

Reset my password:
https://www.boost.org/auth/reset?token=test-reset-token-xyz789

Didn't ask for this? Maybe someone typed your email by accident. You can
ignore this email safely - your password will stay the same.

--
Boost.org is supported by grants from The C++ Alliance.

Want to change how you receive these emails?
You can update your preferences or unsubscribe from this list.

Gmail

iOS

IMG_6213 image

Web

image image

Apple Mail

image image

Self-review Checklist

  • Tag at least one team member from each team to review this PR
  • Link this PR to the related GitHub Project ticket

Frontend

  • UI implementation matches Figma design
  • Tested in light and dark mode
  • Responsive / mobile verified
  • Accessibility checked (keyboard navigation, etc.)
  • Ensure design tokens are used for colors, spacing, typography, etc. – No hardcoded values

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a shared HTML base template and new transactional email templates for confirming email and resetting passwords (HTML + subject + plain-text variants).
    • Social icons are now included in the email footer.
    • Added a management command to send test transactional emails, with optional inline image embedding.
  • Chores

    • Updated email configuration to be fully environment-variable driven, including local development backend settings and Mailgun/Anymail options.

@julhoang julhoang linked an issue Jun 4, 2026 that may be closed by this pull request
@herzog0 herzog0 self-requested a review June 9, 2026 17:19
@jlchilders11 jlchilders11 self-requested a review June 9, 2026 18:16

@herzog0 herzog0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!
Tested with my own Mailtrap profile and got the same results.
Also did some research on the amount of emails of each domain in our db and found that most of the unsupported ones are not truly relevant to our user base.

@jlchilders11 jlchilders11 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of small nits, but otherwise looks good to me!

Comment thread templates/emails/base_email.html Outdated
Comment thread users/management/commands/send_test_emails.py Outdated
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 17eb8eb4-1a81-4e72-9956-08746be89ef4

📥 Commits

Reviewing files that changed from the base of the PR and between d30697c and 69feb65.

📒 Files selected for processing (3)
  • templates/emails/base_email.html
  • templates/emails/confirm_email.html
  • templates/emails/password_reset.html
🚧 Files skipped from review as they are similar to previous changes (3)
  • templates/emails/password_reset.html
  • templates/emails/base_email.html
  • templates/emails/confirm_email.html

📝 Walkthrough

Walkthrough

This PR adds a complete transactional email system to the Boost website, including environment-driven SMTP configuration, reusable Django email templates with block-based customization, concrete templates for email confirmation and password reset, and a management command for testing email rendering and delivery.

Changes

Transactional Email System

Layer / File(s) Summary
Email Configuration Setup
config/settings.py
SMTP parameters (EMAIL_HOST, EMAIL_PORT, EMAIL_HOST_USER, EMAIL_HOST_PASSWORD, EMAIL_USE_TLS) and DEFAULT_FROM_EMAIL are now read from environment variables with sensible defaults; EMAIL_BACKEND and ANYMAIL in LOCAL_DEVELOPMENT are similarly env-driven, allowing per-environment backend selection.
Email Template Infrastructure
templates/emails/base_email.html, templates/emails/_social_icon.html
Base template provides reusable HTML structure with overridable blocks for title, preheader, hero section, card title/body, CTA label, and after-card content; standardizes layout, styling, and context variable handling; social icon partial renders linked external icons for email footers.
Confirmation Email Templates
templates/emails/confirm_email.html, templates/emails/confirm_email.txt, templates/emails/confirm_email_subject.txt
Concrete email confirmation templates extend the base template with welcome messaging, confirmation button CTA, and optional-action footer; plain-text and subject variants support multi-format rendering.
Password Reset Email Templates
templates/emails/password_reset.html, templates/emails/password_reset.txt, templates/emails/password_reset_subject.txt
Concrete password reset templates extend the base template with personalized greeting, target email display, reset link CTA, expiration notice, and unsolicited-request disclaimer; plain-text and subject variants support multi-format rendering.
Test Email Management Command
users/management/commands/send_test_emails.py
Django management command renders and sends confirmation and/or password-reset templates via the configured email backend; supports inline image embedding (with SMTP multipart and API backend handling), template registry, CLI options for personalization (--to, --first-name, --user-email, --from-email, --base-url), and optional inter-message delay.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • boostorg/website-v2#2431: This PR implements the transactional email templates and infrastructure (base_email.html, confirm_email, password_reset, and _social_icon.html) and environment-driven email configuration that align with the objectives described in that issue.

Poem

🐰 Hop into the email queue,
With templates clean and base so true,
From env we read what backends do,
Confirm and reset, send them through! 📧

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes in the PR by mentioning email templates and a send email test command.
Description check ✅ Passed The PR description is well-structured with issue reference, clear summary, links to Figma designs, comprehensive changes list, risks/considerations, multiple screenshots, and a partially completed self-review checklist.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cy/2431-email-templates

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
users/management/commands/send_test_emails.py (1)

206-250: 💤 Low value

Consider wrapping connection usage in try/finally for clean resource release.

If an exception occurs during template rendering or sending (lines 217-248), connection.close() at line 250 won't be reached. For a dev-only tool this is low-impact, but a try/finally ensures the connection is always released.

♻️ Suggested improvement
     connection = get_connection()
+    try:
         is_smtp = "smtp" in settings.EMAIL_BACKEND
         # ... rest of sending logic ...
         click.secho(f"  sent: {key} — {subject!r}", fg="cyan")
 
-    connection.close()
+    finally:
+        connection.close()
     click.secho("Done.", fg="green")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@users/management/commands/send_test_emails.py` around lines 206 - 250, The
code uses connection = get_connection() and calls connection.close() after the
send loop, but if render_to_string, _send_inline, EmailMultiAlternatives, or
msg.send() raise an exception the close() won't run; wrap the for-loop and send
logic in a try/finally where the finally always calls connection.close() (or
checks connection is not None before closing) so get_connection()/connection is
reliably released even on errors when using TEMPLATES, render_to_string,
_send_inline, EmailMultiAlternatives, or msg.send().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@templates/emails/base_email.html`:
- Line 58: The template references email images via the custom tag large_static
(e.g., in templates/emails/base_email.html at the <img src="{% large_static
'img/emails/email-logo.png' %}"> usage), but those asset files are missing; fix
by adding the missing files to your project's static assets at img/emails
(create static/img/emails/email-logo.png, email-octopus.jpg, email-lock.png,
email-warning.png, email-social-x.png, email-social-bluesky.png,
email-social-mastodon.png, email-social-reddit.png, email-social-github.png,
email-social-linkedin.png) or alternatively change the large_static references
in base_email.html (and the other occurrences around lines 116–122) to point to
existing hosted URLs or to a fallback image path that is present in the repo so
emails won't break when assets are not provided externally.

In `@templates/emails/confirm_email.html`:
- Line 29: The templates templates/emails/confirm_email.html and
templates/emails/confirm_email.txt currently hardcode "This link expires in 24
hours."; change the text to use the allauth context variable (e.g., {{
EMAIL_CONFIRMATION_EXPIRE_DAYS }} or the exact allauth-supplied var) instead of
"24 hours", and ensure that the variable is supplied to the email template
context where the confirmation email is rendered (update the code path that
builds the email context—e.g., the allauth adapter/email-sending hook or the
function that calls render_to_string for confirm_email.html/confirm_email.txt—to
include EMAIL_CONFIRMATION_EXPIRE_DAYS derived from
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS or EMAIL_CONFIRMATION_EXPIRE_DAYS).

In `@templates/emails/password_reset.html`:
- Line 29: The templates currently hardcode "This link will expire in 1 hour." —
replace that hardcoded text in templates/emails/password_reset.html and .txt
with a dynamic placeholder (e.g. "This link will expire in {{ expiry }}.") and
update the code that renders/sends the password reset email (e.g. your
PasswordResetView/send_password_reset_email/send_mail helper or adapter that
calls render_to_string) to compute the actual expiry string from the configured
token lifetime (read from Django/allauth settings or fall back to the framework
default) and pass it into the template context as expiry; ensure formatting
(hours/days) matches the computed value so the user-facing message matches the
real token lifetime.

In `@users/management/commands/send_test_emails.py`:
- Around line 199-201: The code detects a missing URL scheme by splitting
base_url into scheme and host (variables scheme, host) but never updates
base_url, so later URL joins produce malformed URLs; fix by reconstructing
base_url when scheme was missing (e.g., set base_url = f"{scheme}://{host}"
right after assigning scheme="https" and host=base_url) so that subsequent uses
of base_url produce fully qualified URLs; update the logic in send_test_emails
(variables scheme, host, base_url) accordingly.

---

Nitpick comments:
In `@users/management/commands/send_test_emails.py`:
- Around line 206-250: The code uses connection = get_connection() and calls
connection.close() after the send loop, but if render_to_string, _send_inline,
EmailMultiAlternatives, or msg.send() raise an exception the close() won't run;
wrap the for-loop and send logic in a try/finally where the finally always calls
connection.close() (or checks connection is not None before closing) so
get_connection()/connection is reliably released even on errors when using
TEMPLATES, render_to_string, _send_inline, EmailMultiAlternatives, or
msg.send().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 58498630-4e2d-4125-8e9b-ee197b3b4f6b

📥 Commits

Reviewing files that changed from the base of the PR and between f71f5d2 and 0a60b82.

📒 Files selected for processing (10)
  • config/settings.py
  • templates/emails/_social_icon.html
  • templates/emails/base_email.html
  • templates/emails/confirm_email.html
  • templates/emails/confirm_email.txt
  • templates/emails/confirm_email_subject.txt
  • templates/emails/password_reset.html
  • templates/emails/password_reset.txt
  • templates/emails/password_reset_subject.txt
  • users/management/commands/send_test_emails.py

Comment thread templates/emails/base_email.html
Comment thread templates/emails/confirm_email.html Outdated
Comment thread templates/emails/password_reset.html Outdated
Comment on lines +199 to +201
scheme, _, host = base_url.partition("://")
if not host: # base_url given without a scheme
scheme, host = "https", base_url

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

base_url not reconstructed when scheme is missing, causing malformed URLs.

If the user passes --base-url www.boost.org (without a scheme), lines 200-201 correctly set scheme="https" and host="www.boost.org", but base_url itself is never reassigned. Lines 227-228 then produce URLs like "www.boost.org/account/preferences" missing the scheme prefix.

🐛 Proposed fix
     scheme, _, host = base_url.partition("://")
     if not host:  # base_url given without a scheme
         scheme, host = "https", base_url
+        base_url = f"{scheme}://{host}"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
scheme, _, host = base_url.partition("://")
if not host: # base_url given without a scheme
scheme, host = "https", base_url
scheme, _, host = base_url.partition("://")
if not host: # base_url given without a scheme
scheme, host = "https", base_url
base_url = f"{scheme}://{host}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@users/management/commands/send_test_emails.py` around lines 199 - 201, The
code detects a missing URL scheme by splitting base_url into scheme and host
(variables scheme, host) but never updates base_url, so later URL joins produce
malformed URLs; fix by reconstructing base_url when scheme was missing (e.g.,
set base_url = f"{scheme}://{host}" right after assigning scheme="https" and
host=base_url) so that subsequent uses of base_url produce fully qualified URLs;
update the logic in send_test_emails (variables scheme, host, base_url)
accordingly.

@kattyode kattyode left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QA Approved

@ycanales ycanales merged commit f3e5528 into develop Jun 24, 2026
5 checks passed
@ycanales ycanales deleted the cy/2431-email-templates branch June 24, 2026 22:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Webpage Integration: Email Templates

5 participants