I've attached a patch to the issue on the tracker. The patch splits up the mail module into a package and provides the backend functionality. Documented functions and classes are of course available in the top-level module. Undocumented methods like make_msgid() are not available in django.core.mail anymore, but an additional import could be added easily.
Two demo backends are attached too. One implements a backend for sending mails through App Engine's mail API, the other implements a backend for django-mailer. In case someone is interested, I've made the full examples of both backends available on bitbucket: http://bitbucket.org/andialbrecht/django_mailbackend_examples/ Andi On Sat, Aug 22, 2009 at 9:15 AM, Russell Keith-Magee<[email protected]> wrote: > > On Sat, Aug 22, 2009 at 1:34 PM, Andi > Albrecht<[email protected]> wrote: >> >> On Fri, Aug 21, 2009 at 1:45 PM, Russell >> Keith-Magee<[email protected]> wrote: >>> >>> On Fri, Aug 21, 2009 at 5:53 PM, Zachary >>> Voase<[email protected]> wrote: >>>> >>>> Hi Andi, >>>> >>>> On 21 Aug 2009, at 05:34, Andi Albrecht wrote: >>>> >>>>> Hi, >>>>> >>>>> I'm interested in working on #10355 "Add support for email backends." >>>>> >>>>> IMHO it's an good idea to make the email backend configurable. There >>>>> are at least two use cases I can think of. The first is to send email >>>>> with other services than SMTP, like App Engine as noted in the >>>>> ticket's description. The second is to deliver email asynchronously, >>>>> like the django-mailer application does already. >>>> >>>> I wholeheartedly agree. >>>> >>>>> The ticket currently needs a design decision, so my question is what >>>>> the actual concerns to change this are. >>>>> >>>>> I would propose the following changes. It's a very simplistic approach >>>>> that tries to keep the current API as much as possible: >>>>> >>>>> Add a new setting EMAIL_BACKEND. A string that can be resolved to a >>>>> class. Default should be the current SMTP implementation. >>>>> >>>>> Provide a base class for mail backends. A mail backend must provide >>>>> the method send_messages(email_messages) and must return the number of >>>>> messages sent to provide backward compatibility. The constructor of a >>>>> mail backend should take at least the keyword argument fail_silently >>>>> (default: False). What I'm a bit unsure about are additional >>>>> constructor arguments. Currently the SMTP backend allows in addition >>>>> host, port, username, password and use_tls. Those are very >>>>> SMTP-specific, but only username and password are used by the >>>>> mail.send_mail* APIs. It would be an agreement to allow username and >>>>> password in addition to fail_silently to not break the send_mail* API. >>>>> The SMTP backend could accept host, port and use_tls as extra keywords >>>>> again to provide backward compatibility for code that directly uses >>>>> SMTPConnection (within Django SMTPConnection is not used outside >>>>> django.core.mail). I would suggest to rename SMTPConnection to >>>>> SMTPBackend, but only if this would break too much third-party code as >>>>> SMTPConnection is mentioned in the docs. >>>> >>>> This I disagree with slightly. My main concern is the single-backend >>>> architecture; many websites will probably want to use more than one >>>> method for sending e-mail. >>> >>> I'm not sure I agree with your assertion of "many"."Some" might be >>> accurate. "Your" is probably more accurate :-) >>> >>> I've got many websites in the field, and not one of them has needed >>> anything more than trivial email handling. We've managed to get to >>> v1.1 and AppEngine support is the first time that pluggable email >>> backends have really been raised as an issue. >>> >>> This is hardly surprising. After all, email is email. You have an SMTP >>> server, you connect to it, you send your mail. AppEngine is a weird >>> case in that they provide an email-sending API rather than using SMTP, >>> but that's an artefact of the platform. Once you have one email >>> sending capability, I find it hard to believe that most people will >>> need a second. >>> >>> I don't doubt that there are applications that will require more than >>> one mail server, but I'm comfortable calling them edge cases. If you >>> have highly specialized mail requirements, then it makes sense that >>> you should have a highly specialized mail server handling. >>> >>> That said, there isn't really that much difference between the simple >>> and complex case - it's just a matter of defaults. >>> >>> Django needs to have a default Email backend, guaranteed available. >>> >>> EmailMessage.send() uses the 'default' backend - essentially just >>> calling backend.send_messages([msg]) >>> >>> backend.send_messages() also exists as a direct call. >>> SMTPConnection().send_messages() is really just a shortcut for >>> instantiating and using an SMTP connection with the default settings. >>> >>> You're not compelled to use the default connection though. You could >>> instantiate multiple instances of different backends, and use them to >>> call other_backend.send_messages(). >>> >>> I see this working almost exactly as the Cache backend works right >>> now. There is a base interface for caching. There are several cache >>> backends; dummy and locmem are handy for testing, but if you're >>> serious, the only one that gets used is memcached. There is a default >>> cache instantiated as a result of the CACHE_BACKEND setting. For most >>> people, this is all you will ever need. However, if you want to >>> instantiate (and use) multiple caches (e.g., if you want to get really >>> fancy about cache overflow and expiry policies), you can. >>> >>>> In addition, if mail backends only need to >>>> implement one method, why not just have EMAIL_BACKEND refer to a >>>> callable instead? >>> >>> Persistence of settings. s1 = Backend(host, port, username password) >>> can be configured once, then s1 can be reused whenever needed. >> >> There's another reason for having classes: The EmailMesage has a >> "connection" instance attribute. It's documented as "An SMTPConnection >> instance. Use this parameter if you want to use the same connection >> for multiple messages. If omitted, a new connection is created when >> send() is called." >> >> So persistence is needed here to allow multiple calls to >> EmailMessage.send() to share the same connection. I'm reading >> "connection" here as "network connection", otherwise it wouldn't make >> much sense as the creation of SMTPConnection, and possible other >> backends likewise, is pretty cheap. >> >> Actually the documentation lacks some explanation on this part - and >> therefore it took me a while to understand the connection attribute :) >> After reading the docs I've had expected that the following example >> uses a single network connection to send mails: >> >> conn = SMTPConnection() >> msg1 = EmailMessage(connection=conn).send() >> msg2 = EmailMessage(connection=conn).send() >> >> But each call to send() opens and closes a new network connection. To >> have a single network connection you'll have to do the following: >> >> conn = SMTPConnection() >> conn.open() >> msg1 = EmailMessage(connection=conn).send() >> msg2 = EmailMessage(connection=conn).send() >> conn.close() >> >> If we want to have an instance of the mail backend on module level >> similar to the backends in the cache or db module those calls to >> open() and close() become nasty as we've might get problems with >> thread-safety here (assuming that the relation between backend and >> network connection is 1:1). >> >> I would propose that backends have those open() and close() methods >> that do nothing in the base implementation. Only backends that require >> a connection on network level should overwrite those methods to manage >> network connections. I think in most cases (that is mail.send_mail*, >> backend.send_messages and EmailMessage.send without setting the >> connection instance attribute) the backend handles network connections >> (if needed) internally. The open() and close() methods are just needed >> for the example outlined above and the docs should be updated >> accordingly. (A use case for this pattern is if you want to use a >> single network connection and if you want to do things after each >> message is sent. backend.send_message([mgs1, msg2]) is not an option >> then.) >> >> Instead of having a backend instance on module level, the mail module >> should provide a get_backend(fail_silently=False) function that >> returns an backend instance. This would avoid problems with concurrent >> calls to open/close on a global backend instance. > > It sounds like you've got a good handle on the problem. I can't see > anything fundamentally wrong with what you are proposing. You are > correct that the 'instance' problem doesn't really apply to the cache, > so different handling is appropriate here. > > My only issue is one of nomenclature. While you will be defining an > SMTP backend and dummy backend, etc, and those classes should be > referred to as Backends, it doesn't make much sense to me to talk > about obtaining an instance of a backend or get_backend(); > get_connection() would seem more appropriate terminology. > > So, the public interface in django.core.mail will look a little like: > > def get_connection(*args, **kwargs): > module = <resolve backend module from settings> > return module.EmailBackend(*args, **kwargs) > > SMTPConnection = smtp.EmailBackend > > and the get_connection call in EmailMessage, send_mail and > send_mass_mail would make a call to the get_connection utliity, rather > than instantiating SMTPConnection directly as they do now. At the end > of the day, it's actually quite a low impact change. > > Yours, > Russ Magee %-) > > > > --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Django developers" group. To post to this group, send email to [email protected] To unsubscribe from this group, send email to [email protected] For more options, visit this group at http://groups.google.com/group/django-developers?hl=en -~----------~----~----~----~------~----~------~--~---
