django / django/new-features

Better hooks to customize PasswordResetForm email sending

Open
#175 1 comment 3 reactions 0 assignees View on GitHub
Dominant language
No language data
Stars
188
Forks
7
PR merge metrics
No merged PRs in 30d

Description

### Code of Conduct

- [x] I agree to follow Django's Code of Conduct

### Feature Description

Break up `PasswordResetForm.send_mail()` to allow custom subclasses to override email rendering and email sending independently, without having to duplicate all its existing logic.

### Problem

With the introduction of `MAILERS` in Django 6.1, projects may wish to use a non-`"default"` mailer config for sending password reset emails. (E.g., to use a special high-priority queue.)

The current [`PasswordResetForm.send_mail()`](https://github.com/django/django/blob/stable/6.1.x/django/contrib/auth/forms.py#L394-L421) implementation combines rendering and composing the email with sending the resulting message. A subclass that overrides it to change to `email_message.send(using="priority")` would need to also duplicate all of the rendering code—and then carefully track any future changes in the base class.

[DEP 0018 suggested](https://github.com/django/deps/blob/main/accepted/0018-mailers.md#future-password-reset-mailer) (as `MAILERS` follow-on work) adding a `PasswordResetForm.email_using` property to simplify this. But that wouldn't handle cases where the mailer choice needs to be dynamic based on other factors, like the user's region: `email_message.send(using="eu" if context["user"].gdpr_applies() else "default")`.

Similarly, there may be cases where a project wants to adjust password reset email rendering without duplicating the sending code. (Adding to the context, ensuring the email uses the user's locale rather than the request's, etc.)

### Request or proposal

proposal

### Additional Details

_No response_

### Implementation Suggestions

Suggestion:

```python
class PasswordResetForm(forms.Form):
...

email_using = None

def send_mail(
self,
subject_template_name,
email_template_name,
context,
from_email,
to_email,
html_email_template_name=None,
):
email_message = self.build_email_message(
subject_template_name=subject_template_name,
email_template_name=email_template_name,
html_email_template_name=html_email_template_name,
context=context,
from_email=from_email,
to_email=to_email,
)

try:
self.send_email_message(
email_message=email_message, context=context
)
except Exception:
logger.exception(
"Failed to send password reset email to %s", context["user"].pk
)

def build_email_message(
self,
*,
subject_template_name,
email_template_name,
html_email_template_name,
context,
from_email,
to_email,
):
subject = loader.render_to_string(subject_template_name, context)
# Email subject *must not* contain newlines
subject = "".join(subject.splitlines())
body = loader.render_to_string(email_template_name, context)

email_message = EmailMultiAlternatives(subject, body, from_email, [to_email])
if html_email_template_name is not None:
html_email = loader.render_to_string(html_email_template_name, context)
email_message.attach_alternative(html_email, "text/html")
return email_message

def send_email_message(self, *, email_message, context):
email_message.send(using=self.email_using)

...
```

The `email_using` property is there to simplify non-dynamic cases, but isn't strictly required. (It's called `email_using` to avoid any potential confusion with databases' `using`.)

`context` is passed to `send_email_message()` to give access to the user, domain (site), etc. (The `from_email` and `to_email` are available in attributes of `email_message`, so aren't repeated as separate args.)

Contributor guide

No contributing guide indexed for this repository

Research direction

Start in django/contrib/auth/forms.py by reading PasswordResetForm.send_mail() and the surrounding email-sending logic. Compare the proposal's build_email_message() and send_email_message() hooks with the current behavior, then verify that subclasses can customize rendering or the mailer independently without duplicating the existing flow.

Written by the indexing model from the issue text.

Assessment

Tech stack
django, python
Domain
authentication, backend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.