Opened 13 years ago

Last modified 4 months ago

#17193 new New feature

Send templated email.

Reported by: Tom Christie Owned by: julianapplebaum
Component: Core (Mail) Version:
Severity: Normal Keywords:
Cc: Chris Streeter, artem.rizhov@…, chuck-norris@…, cmawebsite@… Triage Stage: Accepted
Has patch: yes Needs documentation: yes
Needs tests: yes Patch needs improvement: yes
Easy pickings: no UI/UX: no

Description (last modified by Tim Graham)

If your sending email it's likely that you want to render the body text from template, but there's currently no shortcut to send an email based on a template.

The attached patch is based on a stripped down version of https://github.com/bradwhittington/django-templated-email

It adds django.shortcuts.send_templated_mail, which mirrors the existing send_mail, but which renders the subject and body of the mail from a template, rather than taking their values explicitly. It also supports multipart html/plaintext emails.

The docs will look something like this...

  **send_templated_mail(template_name, from_email, recipient_list, dictionary=None, context_instance=None, fail_silently=False, auth_user=None, auth_password=None, connection=None):**
    Sends a mail, rendering the subject and body of the email from a template.

    The template should contain a block named 'subject', and either/both of a 'plain' and/or 'html' block.

    If only the 'plain' block exists, a plaintext email will be sent.

    If only the 'html' block exists, the plaintext component will be automatically generated from the html, and a multipart email will be sent.

    If both the 'plain' and 'html' blocks exist, a multipart email will be sent.

    **Required arguments:**

    `template_name` - The template that should be used to render the email.

    `from_email` - The sender's email address.

    `recipient_list` - A list of reciepient's email addresses.

    **Optional arguments:**

    `dictionary` - The context dictionary used to render the template. By default, this is an empty dictionary.

    `context_instance` - The Context instance used to render the template. By default, the template will be rendered with a Context instance (filled with values from dictionary).

    `fail_silently` - As in `send_mail`.

    `auth_user`     - As in `send_mail`.

    `auth_password` - As in `send_mail`.

    `connection`    - As in `send_mail`.

Attachments (5)

send_templated_mail.diff (5.2 KB ) - added by Tom Christie 13 years ago.
send_templated_mail.2.diff (10.5 KB ) - added by Tom Christie 13 years ago.
send_templated_mail.3.diff (11.2 KB ) - added by Tom Christie 13 years ago.
send_templated_mail_4.diff (8.8 KB ) - added by julianapplebaum 13 years ago.
email.py (2.1 KB ) - added by Carl Meyer 12 years ago.
Simple ex ampleimplementation (would need integration as Django patch, tests & docs)

Download all attachments as: .zip

Change History (40)

by Tom Christie, 13 years ago

Attachment: send_templated_mail.diff added

comment:1 by Tom Christie, 13 years ago

Needs documentation: set
Needs tests: set

comment:2 by Aymeric Augustin, 13 years ago

Triage Stage: UnreviewedAccepted

Yes, I think Django could provide this feature.

comment:3 by julianapplebaum, 13 years ago

I'm new to to the django dev community, and was hoping to write some unit tests for this shortcut. Should I claim the ticket before doing that? Sorry if this isn't the right place to be asking that.

Thanks,
Julian Applebaum

Version 1, edited 13 years ago by julianapplebaum (previous) (next) (diff)

comment:4 by Aymeric Augustin, 13 years ago

Yes, you should assign the ticket to yourself if you intend to work on it.

You'll find more information about our development process in the contributing guide.

by Tom Christie, 13 years ago

Attachment: send_templated_mail.2.diff added

comment:5 by Tom Christie, 13 years ago

Needs documentation: unset

Updated the patch to include documentation.

Also tweaked the API slightly - the email connection and message sending has been decoupled from the message generation, and I've made some tweaks to the arguments that send_templated_mail takes.

I think this should pretty much hit the sweet spot of making sure the shortcut interface isn't overly complicated, whilst still providing for the common case.

Last edited 13 years ago by Tom Christie (previous) (diff)

by Tom Christie, 13 years ago

Attachment: send_templated_mail.3.diff added

comment:6 by Tom Christie, 13 years ago

Added some extra documentation in the existing email section, referring to the shortcut.

in reply to:  4 comment:7 by julianapplebaum, 13 years ago

Owner: changed from nobody to julianapplebaum
Status: newassigned

Replying to aaugustin:

Yes, you should assign the ticket to yourself if you intend to work on it.

You'll find more information about our development process in the contributing guide.

Thanks, and will do. Should have the patch up by Sunday night.

comment:8 by anonymous, 13 years ago

Quick status update:

It took me and the group I'm with longer than anticipated to get everything up and running. We've written a few unit tests for _render_mail, and aim to finish the rest this coming week. We apologize for the delay.

Julian

comment:9 by Tom Christie, 13 years ago

We apologize for the delay.

Nothing to apologize for, it's a community effort - everything's appreciated. :)

comment:10 by tevans.uk@…, 13 years ago

I have a few doubts about how this has been designed. With both html and plain 'templates' actually just being blocks of a single template, it is not possible to use template inheritance to design consistent emails, which is a common use of the template system. Without this, styles would need to be replicated across multiple templates.

In our send_mail wrapper at $JOB, we allow the user to pass in the stub name of a template. The utility then attempts to render the templates stub+'.html' and stub+'.txt', and uses the results of these to determine whether we send a text/plain, text/html or multipart/alternative.

Instead of extracting the subject from a template, we allow it to be passed in directly. We also add a language argument, which is used to activate the appropriate translation prior to rendering the emails, or coercing the subject to unicode. This allows us to send fully translated emails appropriate for the user receiving the email - which may not be the same as the language currently activated, if there is any, or the language that has been activated for the current view.

Cheers

Tom (one of these days I'll remember my trac login)

comment:11 by Tom Christie, 13 years ago

It's not quite true that you can't use template inheritance with this style, although I've come around to thinking that it probably is a an abuse of the 'block' tags to change their semantics for email templates.

I don't like the stub+'.txt', stub+'.html' for 2 reasons:

Firstly it's very easy for the two separate files to get out of sync, with no easy way of noticing that's happened.
Secondly the stub + extension style isn't used anywhere else.

I think a better solution would be a single template and a flag to switch between plaintext only, or html + autogenerated plaintext.

It's not impossible that I might get a spare couple of hours between now and the 1.4 checkin deadline, but it's not looking all that likely, so may have to defer this to 1.5.

by julianapplebaum, 13 years ago

Attachment: send_templated_mail_4.diff added

comment:12 by julianapplebaum, 13 years ago

Hey Everyone

Just uploaded the diff file for my group's unit tests. Let me know if you run into any issues with the tests/the diff file, etc.

Best,
Julian

comment:13 by anonymous, 13 years ago

Thanks Julian,

There may be some redesign needed of this feature, but either way those tests look fairly comprehensive - should come in useful.

Tom

comment:14 by julianapplebaum, 13 years ago

Needs tests: unset

comment:15 by Julien Phalip, 13 years ago

Triage Stage: AcceptedDesign decision needed

Thank you all for your great work on this patch. However, I'm struggling a bit to see the value of adding this much new code for something that is already quite easily achievable using render_to_string() and separate templates.

I do agree that creating html+plain emails is still a little too hard in Django, but in my opinion this could be simplified by introducing a new helper class wrapping around EmailMultiAlternatives and providing a simpler API and sensible defaults. For example, something like: HTMLEmailMessage(body_plain='...', body_html='...')

In any case, I personally think that any template rendering should be left to the user to do separately (for example by using render_to_string()).

For now I'm marking this ticket as DDN instead of wontfixing so that more discussion can occur.

comment:16 by anonymous, 13 years ago

Agreed that this isn't quite the right way to approach it.

I still think a send_templated_email, makes a lot of sense, but I've not had time to revisit this yet and propose something simpler.

I'll take it to django-developers if and when that happens.
If anyone else takes this to the group first, please link to the discussion from this ticket.

comment:17 by Chris Streeter, 13 years ago

Cc: Chris Streeter added

in reply to:  15 comment:18 by artem.rizhov@…, 13 years ago

Cc: artem.rizhov@… added

Hi there!

Replying to julien:

I do agree that creating html+plain emails is still a little too hard in Django, but in my opinion this could be simplified by introducing a new helper class wrapping around EmailMultiAlternatives and providing a simpler API and sensible defaults. For example, something like: HTMLEmailMessage(body_plain='...', body_html='...')

My implementation is based on EmailMultiAlternatives :) It's available at GitHub https://github.com/artemrizhov/django-mail-templated

Actuall, for me it
I've emailed to django-developers mailing list and proposed to merge it into Django. However my proposition was declined. http://groups.google.com/group/django-developers/browse_frm/thread/4728f20dd8023dd9

I'd be glad to continue work on it, but there is no any reason to polish it more for a while. :( At least unless I need some additional functionality, or some activity is registered on the github project. :) Propositions and suggestions are welcome.

comment:19 by artem.rizhov@…, 13 years ago

Ooops..
s/Actuall, for me it //
Sorry for trash in the message.

comment:20 by chuck-norris@…, 12 years ago

Cc: chuck-norris@… added

comment:21 by Carl Meyer, 12 years ago

Needs tests: set
Patch needs improvement: set
Triage Stage: Design decision neededAccepted

Rendering a template is simple, but there are some minor gotchas in templated email (like remembering to remove newlines from the rendered subject template) that would be reasonable to put in a convenience function in Django rather than requiring everyone to implement them independently. And the EmailMultiAlternatives API is just enough boilerplate to always require looking up in the docs.

IMO the blocks approach implemented above (both in the patch and the linked external project) is too complex, too much code, and too unusual a use of templates (blocks are for template inheritance, not a way to combine multiple templates in one file.) I would rather favor a simple extension naming convention (i.e. give path/to/myemail and it looks for templates path/to/myemail.txt, path/to/myemail.html, and path/to/myemail.subject.txt). I've attached a sample implementation of this approach (not yet integrated as a patch to Django); it really isn't very much code. (This implements a two-layer API, with both a send_multipart function that accepts strings, and a send_templated_multipart that does the template-finding and rendering.)

I guess I'm personally +0 or +0.5 on including something like this in Django. I think it's a very common pattern that's just irritating enough to re-implement to be worth considering for inclusion, but I won't mind at all if another core dev decides to close this wontfix.

by Carl Meyer, 12 years ago

Attachment: email.py added

Simple ex ampleimplementation (would need integration as Django patch, tests & docs)

comment:22 by Carl Meyer, 12 years ago

Needs documentation: set

comment:23 by artem.rizhov@…, 12 years ago

too unusual a use of templates (blocks are for template inheritance, not a way to combine multiple templates in one file.)

Maybe you are right. However 3 files per message is too many. It may be hard and annoying to maintain so many files. And somebody may forget to change one of templates.

And why you say "multiple templates"? Imho this is single template of multipart message :) Let's add this at the top of template:

{% extends 'email/multipart.html' %}

Does it look better now? ;) We also may add 'email/text.html', 'email/html.html' and so on. This will make the template code looking more usual.

comment:24 by Carl Meyer, 12 years ago

If the implementation requires Python code pulling out the blocks from the rendered template for individual specialized handling, then IMO it's a misuse of template blocks and it should be separate templates instead. If the hypothetical email/multipart.html base actually contained a full template for a multipart message, that would be a reasonable use of templates (but still not necessarily a good idea, as the template would then be re-implementing things already handled by EmailMultiAlternatives).

I don't buy the idea that maintaining three files is too hard. But for people who feel that way, alternative approaches would always be available as third-party packages. (Or we can just wontfix this and let all implementations stay outside core, I'm fine with that too.)

comment:25 by anonymous, 12 years ago

Or we can just wontfix this

Ok, you won :)

I hope it is possible to make both html and text vesrions not required.

comment:26 by Tim Graham, 11 years ago

Resolution: duplicate
Status: assignedclosed

Marking this as a duplicate of #20817 which added an html_message parameter to send_mail().

comment:27 by anonymous, 11 years ago

timo, does html_message parameter allow django templates to be used?

comment:28 by Simon Charette, 11 years ago

@anon yes, you could use the render_to_string function to build the html_message kwarg passed to send_mail.

comment:29 by Marc Tamlyn, 11 years ago

Resolution: duplicate
Status: closednew

No, this does not close the original OPs request, and concept as mentioned by Carl.

This ticket is to introduce a helper function of some sort to handle send_mail(render_to_string(...)). It's just a helper function, it's achievable without, but it's an exceedingly common pattern and personally I'm +1 on having this. A significant number of projects I've worked on have included a utility function named something like render_to_send_mail().

The exact API is up for discussion still, which is probably the blocker for this ticket. As mentioned in #20817, it may be better as extension around the EmailMessage or EmailMultiAlternatives APIs rather than just as a helper function. I can see something like the following API being useful:

message = TemplatedEmail(
     from_email=...,
     to=[...],
     subject_template='my_email_subject.txt',
     body_template='my_email_template.txt',
     html_body_template='my_enhanced_email_template.html',
     context=context,
)
message.send()

A functional approach to this could be added as well as a shortcut function, though this is already significantly easier than the existing APIs to do this. If we take Carl's earlier suggestion about a naming convention, it becomes simpler, alternatively we could add complexity by also supporting kwargs for subject, body and html_body which can take rendered strings as alternatives.

comment:30 by Collin Anderson, 10 years ago

Cc: cmawebsite@… added

comment:31 by Tim Graham, 10 years ago

Description: modified (diff)

comment:32 by Carl Meyer, 10 years ago

With the addition of the html_message parameter to send_mail, the reasons for adding this have been reduced to:

  1. Saving the need to type render_to_string two or three times.
  2. Saving the need to collapse newlines in the rendered subject (though I'm not actually sure off-hand what the consequence of failing to do that is, would need to check).

That starts to look a bit thin as rationale, but it is a common enough need that I'd probably still be at least +0 on adding it.

I think the naming-convention suggestion is probably a bit too implicit for Django's usual API style; that's something probably better left to a third-party solution.

So I think if we do this, the API should probably look something like what Marc proposed.

comment:33 by Carl Meyer, 10 years ago

Also, one more argument against the "single template and pull out blocks" suggestion -- a single file with some portions that are HTML and some portions that are text does not work well with editor modes and syntax highlighting.

comment:34 by Jacob Rief, 3 years ago

Just in case someone wants to implement a template rendering system for the email backend, I have added a templated based email renderer to https://github.com/ui/django-post_office

comment:35 by Mike Edmunds, 5 months ago

There are a number of third-party Django packages that provide templated email, with varying approaches for converting templates to email (and in varying states of maintenance):

[I'm updating this comment periodically as I come across other libraries. Email templating seems to be a popular area for exploration.]

Last edited 4 months ago by Mike Edmunds (previous) (diff)
Note: See TracTickets for help on using tickets.
Back to Top