Skip to content

Use minijinja templates for emails #11420

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

Turbo87
Copy link
Member

@Turbo87 Turbo87 commented Jun 24, 2025

This PR refactors our email sending code to use minijinja templates instead of format!() and string concatenation. In the long term this even enables us to add dedicated templates for HTML emails too, or use template inheritance to use consistent email footers, etc.

Before:

impl Email for UserConfirmEmail<'_> {
    fn subject(&self) -> String { /* ... */ }
    fn body(&self) -> String { /* ... */ }
}

emails.send(recipient, user_confirm_email).await?;

After (with templates):

let email_message = EmailMessage::from_template("user_confirm", context! {
    user_name => user_name,
    domain => domain,
    token => token.expose_secret()
})?;

emails.send(recipient, email_message).await?;
Hello {{ user_name }}! Welcome to crates.io. Please click the
link below to verify your email address. Thank you!

https://{{ domain }}/confirm/{{ token }}

@Turbo87 Turbo87 added C-internal 🔧 Category: Nonessential work that would make the codebase more consistent or clear A-backend ⚙️ labels Jun 24, 2025
@Turbo87 Turbo87 requested a review from a team June 24, 2025 12:45
@Turbo87 Turbo87 force-pushed the jinja-emails branch 3 times, most recently from 9ec4a0d to 0f87495 Compare June 25, 2025 13:12
@Turbo87
Copy link
Member Author

Turbo87 commented Jun 25, 2025

rebased to include the new email type from #11419

Copy link
Contributor

@eth3lbert eth3lbert left a comment

Choose a reason for hiding this comment

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

A quick glance, and overall LGTM, thanks! I've also left a few nitpicks more related to unified coding style.

@Turbo87
Copy link
Member Author

Turbo87 commented Jun 26, 2025

Thanks for the review! All of the feedback should be addressed now :)

Comment on lines +297 to +300
let email = EmailMessage::from_template("new_token", context);
let email = email.context("Failed to render email template")?;
let result = emails.send(recipient, email).await;
result.context("Failed to send email")
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: use chaining, as is done with others.

Suggested change
let email = EmailMessage::from_template("new_token", context);
let email = email.context("Failed to render email template")?;
let result = emails.send(recipient, email).await;
result.context("Failed to send email")
let email = EmailMessage::from_template("new_token", context)
.context("Failed to render email template")?;
emails
.send(recipient, email)
.await
.context("Failed to send email")

Copy link
Member Author

Choose a reason for hiding this comment

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

Suggested change
let email = EmailMessage::from_template("new_token", context);
let email = email.context("Failed to render email template")?;
let result = emails.send(recipient, email).await;
result.context("Failed to send email")
let email = EmailMessage::from_template("new_token", context)
.context("Failed to render email template")?;
emails.send(recipient, email).await
.context("Failed to send email")

I'd be okay with this formatting, but rustfmt seems very keen on giving .await its own line, which makes it look a bit ugly IMHO 😅

Copy link
Contributor

Choose a reason for hiding this comment

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

maybe change other chained statements in other files to separate statements? I'm fine if they all follow the same style.

Copy link
Contributor

Choose a reason for hiding this comment

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

These are all just nitpicks, not blockers, so feel free to merge if you want! :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-backend ⚙️ C-internal 🔧 Category: Nonessential work that would make the codebase more consistent or clear
Projects
Status: For next meeting
Development

Successfully merging this pull request may close these issues.

2 participants