Hi Ronny, django-pony-express looks really interesting, thanks. (I'm going to add a link in django-anymail's "you probably don't need proprietary ESP templates <https://anymail.dev/en/stable/tips/django_templates/>" docs.)
I think django-pony-express is very complementary to this proposal, both as a third-party library and if some version of class-based emails finds its way into Django core. Here's my mental model of Django's email sending functionality: - A django.core.mail.message.EmailMessage (or EmailMultiAlternatives*) is a list of ingredients for building an email message—it's mostly just a "bucket of properties" that specifies what should end up in the message.** - An EmailBackend is responsible for transmitting that EmailMessage to a server that will actually send an email—an SMTP server, an email service provider's HTTP API, etc. Each backend implements its own recipe to bake the Django EmailMessage ingredients into the form expected by its server—an RFC 2822+ message for SMTP, a JSON API payload for an ESP, etc. - Because a Django EmailMessage can be "a pain" (you're not wrong!), there are convenience APIs that simplify constructing and sending it. Some come with Django: send_mail(), send_mass_mail(), mail_admins(). Some come from third party libraries: django-pony-express, django-templated-mail, etc. *Many* get built (and repeatedly re-built) by developers in individual Django projects. To stretch your class-based view analogy, Django's EmailMessage is roughly akin to Django's HttpResponse. You can write a view function that builds up an HttpResponse from scratch. For a lot of common cases, though, it's much easier to work one of Django's helper functions or class-based View subclasses. But however you go about it, every view eventually has to return an HttpResponse that Django can hand off to its "http backend" (wsgi/asgi). Similarly, there are several ways to build a Django EmailMessage. At the end of the day, though, all of these need to return a django.core.mail.EmailMessage that Django can hand off to an email backend for sending. - Mike _____ * I suspect the EmailMultiAlternatives/EmailMessage distinction is no longer helpful. I'm fairly certain it's confusing to users, and I *know* it adds complexity for third-party email backends. Given that modern Python email convenience <https://docs.python.org/3/library/email.message.html#email.message.EmailMessage.add_alternative> methods <https://docs.python.org/3/library/email.message.html#email.message.EmailMessage.add_attachment> handle all the multipart restructuring, I'll propose collapsing EmailMultiAlternatives/EmailMessage into a single class as part of this work. ** Along with the "bucket of properties," Django's EmailMessage also implements serialization to RFC 2822+ format in as_message(). This is used by many—but certainly not all—email backends. And (just to come full circle here in the footnotes), the bulk of work in this proposal is actually about updating as_message() and the private helpers it calls. On Tuesday, June 25, 2024 at 7:23:42 AM UTC-7 Ronny V. wrote: > Hi all! > > Jakob Rief pointed this discussion out to me. I've been going around > lately to make some advertisement for my idea of class-based emails. > > I've implemented a package called "django-pony-express" which in a > nutshell provides to things: > > * A class-based way of creating new emails (very similar to class-based > views) > * A test suite for easy unit-testing of emails (we are currently working > on bringing this to Django core) > > Here's a documentation link: > https://django-pony-express.readthedocs.io/en/latest/features/introduction.html#create-a-single-email > > > The idea is to create emails like you create views. All important stuff is > encapsulated but you can overwrite everything you need. There are a bunch > of examples in the docs. > > Apart from not having to deal with low-level email API stuff (which is a > pain IMHO), it provides lots of neat improvements I had to (re-)invent in > every project I was working on. > > What do you people think about this? I got quite good feedback in Vigo and > in my opinion, the solution is very Django-esque. > > I'd be happy to go into more detail if required but wanted to keep this > first commend brief. > > Best from Cologne > Ronny > Mike Edmunds schrieb am Montag, 24. Juni 2024 um 21:14:47 UTC+2: > >> > Would this be designed to be compatible with "Proposal 14: Background >> Workers"? >> >> I wouldn't expect this to impact background workers one way or the other. >> The same goes for the async email proposal(s) floating around. >> >> Virtually all of this work would occur in django.core.mail.message, where >> Python's `email` APIs are used. A goal is to avoid changes that would break >> existing email backends (Django or third-party). >> >> The background workers proposal will implement a new background SMTP >> EmailBackend (in django.core.mail.backends). The existing SMTP EmailBackend >> doesn't directly use Python's `email` APIs, and there should be no reason >> for background SMTP to be any different. (It might be helpful to know that >> Python's `email` library isn't involved in *sending* email; that's >> handled by Python's `smtplib`, which Django uses only in the SMTP >> EmailBackend.) >> >> The existing SMTP EmailBackend *does* use one function I expect will >> become deprecated: django.core.mail.message.sanitize_address(). I haven't >> yet investigated whether that use is still necessary, or whether it's there >> to get around past bugs/limitations in Python's smtplib. If any of it is >> still needed for SMTP, I'd probably want to move that code into the SMTP >> EmailBackend(s). >> >> - Mike >> On Sunday, June 23, 2024 at 10:22:03 PM UTC-7 Arthur Pemberton wrote: >> >>> Would this be designed to be compatible with "Proposal 14: Background >>> Workers"? >>> >>> >>> >>> On Sun, Jun 23, 2024 at 10:46 PM Mike Edmunds <medm...@gmail.com> wrote: >>> >>>> I want to propose updating django.core.mail to replace use of Python's >>>> legacy email.message.Message (and other legacy email APIs) with >>>> email.message.EmailMessage (and other modern APIs). >>>> >>>> If there's interest, I can put together a more detailed proposal and/or >>>> ticket, but was hoping to get some initial feedback first. (I searched for >>>> relevant discussions in django-developers and the issue tracker, and >>>> didn't >>>> find any. Apologies if this has come up before.) >>>> >>>> [Note: I maintain django-anymail >>>> <https://github.com/anymail/django-anymail>, which implements Django >>>> integration with several transactional email service providers.] >>>> >>>> >>>> *Background* >>>> >>>> Since Python ~3.6, Python's email package has included two largely >>>> separate implementations: >>>> >>>> - a "modern (unicode friendly) API" based on >>>> email.message.EmailMessage and email.policy.default >>>> - a "legacy API" providing compatibility with older Python >>>> versions, based on email.message.Message and email.policy.compat32 >>>> >>>> (See https://docs.python.org/3/library/email.html, especially toward >>>> the end.) >>>> >>>> django.core.mail currently uses the legacy API. >>>> >>>> >>>> *Why switch?* >>>> >>>> There are no plans to deprecate Python's legacy email API, and it's >>>> working, so why change Django? >>>> >>>> - >>>> >>>> *Fewer bugs:* The modern API fixes a *lot* of bugs in the legacy >>>> API. My understanding is legacy bugs will generally not be fixed. (And >>>> in >>>> fact, there are some cases where the legacy API deliberately replicates >>>> earlier buggy behavior.) >>>> >>>> Django #35497 <https://code.djangoproject.com/ticket/35497> is an >>>> example of a legacy bug (with a problematic proposed workaround) which >>>> is >>>> just fixed in the modern API. >>>> - >>>> >>>> *Simpler, safer code:* A substantial portion >>>> >>>> <https://github.com/django/django/blob/stable/5.1.x/django/core/mail/message.py#L32-L190> >>>> of >>>> django.core.mail's internals implements workarounds and security fixes >>>> for >>>> problems in the legacy API. This would be greatly simplified—maybe >>>> eliminated completely—using the modern API. >>>> >>>> Examples: the modern API prevents CR/NL injections in message >>>> headers. It serializes and folds address headers properly—even ones >>>> with >>>> unicode names. It enforces single-instance headers where appropriate. >>>> - >>>> >>>> *Easier to work with:* The modern API adds some nice conveniences >>>> for developers working in django.core.mail, third-party library >>>> developers, >>>> and (depending on what we choose to expose) users of Django's mail APIs. >>>> >>>> Examples: populating the "Date" header with a datetime or an >>>> address header with Address >>>> >>>> <https://docs.python.org/3/library/email.headerregistry.html#email.headerregistry.Address> >>>> objects—without >>>> needing intricate knowledge of email header formats. Using >>>> email.policy to generate a 7-bit clean serialization (without >>>> having to muck about with the MIME parts >>>> >>>> <https://github.com/anymail/django-anymail/blob/v11.0/anymail/backends/amazon_ses.py#L168-L179> >>>> ). >>>> >>>> >>>> *Concerns & risks* >>>> >>>> Compatibility and security, of course… >>>> >>>> - >>>> >>>> *Backwards compatibility (for API users):* django.core.mail largely >>>> insulates callers from the underlying Python email package. There are a >>>> few >>>> places where this leaks (e.g., attachments >>>> >>>> <https://docs.djangoproject.com/en/5.0/topics/email/#django.core.mail.EmailMessage:~:text=send()%20is%20called.-,attachments,-%3A%20A%20list%20of> >>>> allows >>>> legacy email MIMEBase >>>> <https://docs.python.org/3/library/email.mime.html> objects), but >>>> in general the switch should be transparent. (And I have some ideas for >>>> supporting the other cases.) >>>> - >>>> >>>> *Backwards compatibility (for third-party libraries):* Some >>>> libraries may use internals I'd propose removing (e.g., SafeMIME and >>>> friends); we'd handle this through deprecation. >>>> - >>>> >>>> *Backwards compatibility (bug-level):* There's probably some code >>>> out there that unintentionally depends on legacy email bugs (or the >>>> specific ways Django works around them). I don't have any examples, but >>>> I >>>> also don't have a good solution for when they surface. Plus, while >>>> Python's >>>> modern email API is pretty mature at this point, there are still new >>>> bugs >>>> being reported against it. Email is complicated. >>>> - >>>> >>>> *Security:* As noted above, the modern API should be more secure >>>> than the legacy one. But we also have a nice collection of email >>>> security >>>> tests—which *mostly* don't depend on internal implementation. We'd >>>> keep these. >>>> >>>> -- >>>> >>> You received this message because you are subscribed to the Google >>>> Groups "Django developers (Contributions to Django itself)" group. >>>> To unsubscribe from this group and stop receiving emails from it, send >>>> an email to django-develop...@googlegroups.com. >>>> To view this discussion on the web visit >>>> https://groups.google.com/d/msgid/django-developers/f410ad79-a034-4275-88a7-22e7626c06fdn%40googlegroups.com >>>> >>>> <https://groups.google.com/d/msgid/django-developers/f410ad79-a034-4275-88a7-22e7626c06fdn%40googlegroups.com?utm_medium=email&utm_source=footer> >>>> . >>>> >>> -- You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group. To unsubscribe from this group and stop receiving emails from it, send an email to django-developers+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/0042a5af-d483-4c8e-8ea7-388f0f39c7f4n%40googlegroups.com.