Giter Club home page Giter Club logo

django-anymail's Introduction

Anymail: Django email integration for transactional ESPs

Anymail lets you send and receive email in Django using your choice of transactional email service providers (ESPs). It extends the standard django.core.mail with many common ESP-added features, providing a consistent API that avoids locking your code to one specific ESP (and making it easier to change ESPs later if needed).

Anymail currently supports these ESPs:

  • Amazon SES
  • Brevo (formerly SendinBlue)
  • MailerSend
  • Mailgun
  • Mailjet
  • Mandrill (MailChimp transactional)
  • Postal (self-hosted ESP)
  • Postmark
  • Resend
  • SendGrid
  • SparkPost
  • Unisender Go

Anymail includes:

  • Integration of each ESP's sending APIs into Django's built-in email package, including support for HTML, attachments, extra headers, and other standard email features
  • Extensions to expose common ESP-added functionality, like tags, metadata, and tracking, with code that's portable between ESPs
  • Simplified inline images for HTML email
  • Normalized sent-message status and tracking notification, by connecting your ESP's webhooks to Django signals
  • "Batch transactional" sends using your ESP's merge and template features
  • Inbound message support, to receive email through your ESP's webhooks, with simplified, portable access to attachments and other inbound content

Anymail maintains compatibility with all Django versions that are in mainstream or extended support, plus (usually) a few older Django versions, and is extensively tested on all Python versions supported by Django. (Even-older Django versions may still be covered by an Anymail extended support release; consult the changelog for details.)

Anymail releases follow semantic versioning. The package is released under the BSD license.

test status in GitHub Actions

integration test status in GitHub Actions

documentation build status on ReadTheDocs

Resources

Anymail 1-2-3

Here's how to send a message. This example uses Mailgun, but you can substitute Mailjet or Postmark or SendGrid or SparkPost or any other supported ESP where you see "mailgun":

  1. Install Anymail from PyPI:

    $ pip install "django-anymail[mailgun]"

    (The [mailgun] part installs any additional packages needed for that ESP. Mailgun doesn't have any, but some other ESPs do.)

  2. Edit your project's settings.py:

    INSTALLED_APPS = [
        # ...
        "anymail",
        # ...
    ]
    
    ANYMAIL = {
        # (exact settings here depend on your ESP...)
        "MAILGUN_API_KEY": "<your Mailgun key>",
        "MAILGUN_SENDER_DOMAIN": 'mg.example.com',  # your Mailgun domain, if needed
    }
    EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend"  # or sendgrid.EmailBackend, or...
    DEFAULT_FROM_EMAIL = "[email protected]"  # if you don't already have this in settings
    SERVER_EMAIL = "[email protected]"  # ditto (default from-email for Django errors)
  3. Now the regular Django email functions will send through your chosen ESP:

    from django.core.mail import send_mail
    
    send_mail("It works!", "This will get sent through Mailgun",
              "Anymail Sender <[email protected]>", ["[email protected]"])

    You could send an HTML message, complete with an inline image, custom tags and metadata:

    from django.core.mail import EmailMultiAlternatives
    from anymail.message import attach_inline_image_file
    
    msg = EmailMultiAlternatives(
        subject="Please activate your account",
        body="Click to activate your account: https://example.com/activate",
        from_email="Example <[email protected]>",
        to=["New User <[email protected]>", "[email protected]"],
        reply_to=["Helpdesk <[email protected]>"])
    
    # Include an inline image in the html:
    logo_cid = attach_inline_image_file(msg, "/path/to/logo.jpg")
    html = """<img alt="Logo" src="cid:{logo_cid}">
              <p>Please <a href="https://example.com/activate">activate</a>
              your account</p>""".format(logo_cid=logo_cid)
    msg.attach_alternative(html, "text/html")
    
    # Optional Anymail extensions:
    msg.metadata = {"user_id": "8675309", "experiment_variation": 1}
    msg.tags = ["activation", "onboarding"]
    msg.track_clicks = True
    
    # Send it:
    msg.send()

See the full documentation for more features and options, including receiving messages and tracking sent message status.

django-anymail's People

Contributors

adamchainz avatar alee avatar andrew-chen-wang avatar anstosa avatar arnaudf avatar arondit avatar chrisjones-brack3t avatar costela avatar crccheck avatar dgilmanaidentified avatar dmwyatt avatar erichennings avatar freider avatar izimobil avatar jannethoft avatar jarcoal avatar jayfk avatar jeltef avatar jpadilla avatar kennethlove avatar kylegibson avatar luzfcb avatar medmunds avatar nikolay-saskovets avatar omerzimp avatar si14 avatar ulmus avatar winhamwr avatar wrhector avatar yourcelf avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-anymail's Issues

Simplify/streamline docs

The Anymail docs evolved from the original Djrill docs, and along the way picked up (at least) two structural flaws. At some point, I'd like to rework the docs to fix these:

Inline ESP-specific info where possible

The docs have constant references to "see info for your ESP" elsewhere in the docs. (As early as step 2 in the installation instructions.) So you're having to constantly flip back and forth. And the ESP-specific info assumes you're already familiar with the Anymail features from the main docs; there are cross-reference links, but it's not easy to follow.

A better structure (I think) would be a single flow through the docs, with any ESP-specific notes right there inline where the main feature is covered. If you're reading about Anymail's metadata option, it should tell you right there that Postmark doesn't support metadata -- if you're using Postmark.

The counter-argument is this would make readers wade through a lot of irrelevant material for other ESPs. (Most Anymail users are probably working with a single ESP at any given time.) I think this could be solved with an "ESP picker" menu that would filter the docs for the selected ESP. Could probably set up something similar to the Sphinx examplecode extension (which uses tabs to display code blocks for different languages).

Incidentally, I believe this would also make the docs easier to write and maintain. I'd originally thought that gathering each ESP's info in a single place would be simpler to write, but in practice I end up flipping back and forth just as much in the doc sources as a reader does in the results.

Split reference docs out from usage docs

Right now, the docs liberally mix usage information with detailed references. If you're trying to learn how to integrate Anymail, the references are probably getting in your way. A better approach -- used in many packages -- would be to put usage guides first and then detailed references toward the end.

Example: the installation docs include a settings reference with settings for features that haven't been introduced yet. And then each ESP-specific section has its own settings reference (e.g., Mailgun), with a mixture of important (API_KEY) and rarely-needed (API_URL) settings.

Ideally, the reference section could be generated from code docstrings.

Save all send mail with webhook status from backends

Create a settings like:

ANYMAIL_LOG_MAIL = True

And store all email data, like: subject, template_name, context, to, final_html, from, datetime, generic relation to mail object (ex: user, order, alert ...) . AND the last status sent by backend webhook, like bounce, received, open.

Can be a campaign related to this email. Will be helpful for stats like: How many emails are bounce, received, open.

Inbound mail

I'm currently evaluating django-anymail (with mailgun) which I will be using inbound mail with and see on your roadmap that you intend to add inbound mail support. Did you have a structure/plan in mind for doing this, before I do a quick specific implementation similar to the mailgun docs, which probably won't be suitable for django-anymail?

Add pre- and post-send signals

In BaseBackend, send two signals around outbound messages:

  1. pre-send: after the payload is constructed, but before the send.
    Gives receivers a chance to cancel the send (e.g., to implement
    blacklists) or otherwise muck with the payload, or to log a
    sent-message model (e.g., for an email tracking dashboard)
  2. post-send: with the normalized response (including message id).
    Good place to (e.g.,) update your sent-message model with the
    message id.

Support for OSS Postal mail service

postal recently was made available on Github as an OSS alternative to Mailgun & Sendgrid.

Supporting a new mail service backend isn't a small undertaking and could have potential challenges. However, the idea sounds really cool in concept to see a full OSS stack for mail delivery available.

"pip install django-anymail --upgrade" upgrades django

I had Django=1.8.3 already installed, which I believe is already supported by django-anymail. But when I ran:

pip install django-anymail --upgrade

just now, pip also upgraded my django to the latest version. I want to stick with Django 1.8, and don't believe this should have been upgraded automatically.

I'm not sure if this is a problem with django-anymail settings or pip.

automatically switch ESP during downtime

Downtime from ESPs is a pain (4 hours into SendGrid send delays right now), and in reading about this library it would be a very neat feature if during downtime from your preferred ESP, anymail would switch to a backup or SMTP. Would this fit into the scope of anymail?

How this might work

settings contain backups

INSTALLED_APPS = (
    ...
    "anymail"
)

ANYMAIL = {
    # now would need to have multiple
    "MAILGUN_API_KEY": "<your Mailgun key>",
    "MAILGUN_SENDER_DOMAIN": 'mg.example.com',
}
EMAIL_BACKEND = "anymail.backends.mailgun.MailgunBackend" #this is the default
BACKUP_BACKENDS = ["anymail.backends.sendgrid.SendGridBackend","'django.core.mail.backends.smtp.EmailBackend'']
DEFAULT_FROM_EMAIL = "[email protected]"

webhook flow

  • webhooks for ESP statuspages from statuspage.io can be set up within anymail SendGrid, MailGun, SparkPost
  • confirm the unauthenticated webhook with a call to that statuspage's api example
  • check backups in order to ensure it is up and running, save chosen backup as current preferred connection
  • webhook for issue resolved comes in, revert to default as preferred connection

add extra step to sending an email

current_backend = get_anymail_connection()
send_mail("Password reset", "Here you go", "[email protected]", ["[email protected]"],
          connection=current_backend)

sendgrid API user&password support

Looks like the sendgrid backend is expecting API key, but on Heroku, when Sendgrid is provisioned, the credentials are injected in the form of username and password.

Can you add in support for using sendgrid username and password?
image

In anymail settings dictionary, please use the variable names SENDGRID_PASSWORD and SENDGRID_USERNAME as the settings keys.

UnicodeEncodeError: 'ascii' codec can't encode character [with ugettext_lazy]

Seems like anymail does not handle unicode correctly.

UnicodeEncodeError: 'ascii' codec can't encode character u'\u0142' in position 0: ordinal not in range(128)
  File "django/core/handlers/base.py", line 149, in get_response
    response = self.process_exception_by_middleware(e, request)
  File "django/core/handlers/base.py", line 147, in get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "django/utils/decorators.py", line 149, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "gsmtasks/users/views.py", line 250, in password_reset
    form.save()
  File "gsmtasks/users/forms.py", line 174, in save
    msg.send()
  File "django/core/mail/message.py", line 292, in send
    return self.get_connection(fail_silently).send_messages([self])
  File "anymail/backends/base.py", line 86, in send_messages
    sent = self._send(message)
  File "anymail/backends/base_requests.py", line 56, in _send
    return super(AnymailRequestsBackend, self)._send(message)
  File "anymail/backends/base.py", line 116, in _send
    response = self.post_to_esp(payload, message)
  File "anymail/backends/base_requests.py", line 69, in post_to_esp
    response = self.session.request(**params)
  File "opbeat/instrumentation/packages/base.py", line 63, in __call__
    args, kwargs)
  File "opbeat/instrumentation/packages/base.py", line 222, in call_if_sampling
    return self.call(module, method, wrapped, instance, args, kwargs)
  File "opbeat/instrumentation/packages/requests.py", line 38, in call
    return wrapped(*args, **kwargs)
  File "requests/sessions.py", line 461, in request
    prep = self.prepare_request(req)
  File "requests/sessions.py", line 394, in prepare_request
    hooks=merge_hooks(request.hooks, self.hooks),
  File "requests/models.py", line 298, in prepare
    self.prepare_body(data, files, json)
  File "requests/models.py", line 452, in prepare_body
    body = self._encode_params(data)
  File "requests/models.py", line 97, in _encode_params
    return urlencode(result, doseq=True)
  File "python2.7/urllib.py", line 1357, in urlencode
    l.append(k + '=' + quote_plus(str(elt)))

mandrill webhook: incorrect signature with auth

dear, Im getting Mandrill webhook called with incorrect signature error with a Bad Request response towards mandrill.

Sentry logs say about the err location:

filename	
'base.py'
lineno	
216
pathname	
'/home/onlinegv/eggs/Django-1.9.9-py2.7.egg/django/core/handlers/base.py'

I triple checked my webhook and api key. my total settings look like this:

        ANYMAIL =  {
            'MANDRILL_API_KEY': get_config_or_none(
                config,'Email', 'mandrill_api_key', 'SOMEKEY-A'),
            'WEBHOOK_AUTHORIZATION':'SOMEAUTH:SOMEPW',
            'MANDRILL_WEBHOOK_KEY': get_config_or_none(
                config, 'Email', 'mandrill_webhook_key', 'SOMEKEY'),
        }

what else should I check to investigate the failure?

Rename all backends to "EmailBackend" for consistency with Django

Django's built-in email backend class names are all EmailBackend; the type of backend is indicated by the module:

  • django.core.mail.backends.smtp.EmailBackend
  • django.core.mail.backends.console.EmailBackend
  • django.core.mail.backends.filebased.EmailBackend
  • django.core.mail.backends.locmem.EmailBackend
  • django.core.mail.backends.dummy.EmailBackend

Before 1.0-alpha, Anymail should change to follow Django's naming convention:

  • anymail.backends.mailgun.MailgunBackend → anymail.backends.mailgun.EmailBackend
  • anymail.backends.mandrill. MandrillBackend → anymail.backends.mandrill.EmailBackend
  • anymail.backends.postmark.PostmarkBackend → anymail.backends.postmark.EmailBackend
  • anymail.backends.sendgrid.SendGridBackend → anymail.backends.sendgrid.EmailBackend
  • anymail.backends.sparkpost.SparkPostBackend → anymail.backends.sparkpost.EmailBackend

To avoid breaking code, the current names should still be supported with a deprecation warning.

(As an additional benefit, this would eliminate the need for admonishments throughout the docs on how to properly capitalize each ESP's name.)

Support for SparkPost

I just seemlessly migrated from djrill to django-anymail. Thanks for making it so easy!

Now I'm looking to change ESP from Mandrill, and have found SparkPost to offer a very generous number of free emails, and a very similar user interface to Mandrill. Not only that, but they have a tool to migrate templates.

It would be great if it were supported by django-anymail.

Thanks.

Allow multiple emails in the from_email argument

According to RFC-5322 Section 3.6.2, the From can be a mailbox list and the Sender can be a mailbox. I tried to set the from_email as a list of emails but caught an AnymailInvalidAddress when calling the send method.

>>> m = EmailMessage(to=['[email protected]'], from_email=['[email protected]', '[email protected]'], body='test', subject='test')
>>> m.send()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/.../lib/python2.7/site-packages/django/core/mail/message.py", line 292, in send
    return self.get_connection(fail_silently).send_messages([self])
  File "/.../lib/python2.7/site-packages/anymail/backends/base.py", line 88, in send_messages
    sent = self._send(message)
  File "/.../lib/python2.7/site-packages/anymail/backends/base_requests.py", line 56, in _send
    return super(AnymailRequestsBackend, self)._send(message)
  File "/.../lib/python2.7/site-packages/anymail/backends/base.py", line 117, in _send
    payload = self.build_message_payload(message, self.send_defaults)
  File "/Users/andrewvelis/Documents/promoterapp/apps/emailrelay/backends.py", line 8, in build_message_payload
    return SendGridSMTPAPIMethodsPayload(message, defaults, self)
  File "/.../lib/python2.7/site-packages/anymail/backends/sendgrid_v2.py", line 93, in __init__
    *args, **kwargs)
  File "/.../lib/python2.7/site-packages/anymail/backends/base_requests.py", line 116, in __init__
    super(RequestsPayload, self).__init__(message, defaults, backend)
  File "/.../lib/python2.7/site-packages/anymail/backends/base.py", line 265, in __init__
    value = converter(value)
  File "/.../lib/python2.7/site-packages/anymail/backends/base.py", line 304, in parsed_email
    return ParsedEmail(address, self.message.encoding)  # (handles lazy address)
  File "/.../lib/python2.7/site-packages/anymail/utils.py", line 144, in __init__
    % (address, str(err)))
AnymailInvalidAddress: Invalid email address format ['[email protected]', '[email protected]']: Multiple email addresses (parses as [('', ''), ('', u"'[email protected]'"), ('', u"'[email protected]'"), ('', '')])

It feels like a bug with django-anymail but I am open for discussion/education on how to achieve this.

getting HTTP 400s sending emails with sender that contains extra chars

hi there:

before finding AnyMail (which is awesome), we used to use the plain ol SMTP-based backends and when we send outbound emails with this kind of sender_email (below), things just worked:

'Foobar, inc. (via SomeOtherFoobar)" [email protected]'

after switching to AnyMail using MailGun as the backend, now i started seeing HTTP 400 errors claiming that my 'from' address was not valid:

ESP API response 400:
{
  "message": "'from' parameter is not a valid address. please check documentation"
}

The thing is, if i use curl and POST new messages to Mailgun HTTP APIs directly, that sender_email is accepted. i used a curl statement like this:

curl -s --user 'api:<secret!>' \
    https://api.mailgun.net/v3/<mydomain>/messages \
    -F from='Foobar Construction & Design, LLC. (via FoobarsRUs) <[email protected]>' \
    -F [email protected] \
    -F subject='Hello' \
    -F text='Testing some Mailgun awesomness!'

so after lots of testing, i found that i needed to double-quote the part of the sender_email that might contain commas, parentheses, etc: "Foobar Construction & Design, LLC. (via FoobarsRUs)"

doing this, then AnyMail sends the message properly. it just seems odd that other email backends worked for this sender, as does the MailGun HTTP APIs directly.

i added a test script to repro this issue, you might need to edit the To email address of course. thank you for any help!

matias elgart

test_anymail.py.txt

SendGrid: duplicate Message-IDs with merge_data/batch send

When doing a batch send with merge_data, Anymail's generated Message-ID gets used for all of the individual messages sent to each recipient. Since Message-ID is meant to be unique, this could cause (obscure) problems for some recipients.

Options:

  • Force disable SENDGRID_GENERATE_MESSAGE_ID when using merge_data. (Downside: not having id for the message complicates event tracking, which is why Anymail generates them in the first place.)
  • Generate a unique Message-ID for each recipient when using merge_data. Would only work if SG allows merge fields in arbitrary headers.
  • Rethink the whole generated Message-ID approach (get rid of it, use a field other than Message-ID, or?)

What Django versions should Anymail support?

Right now, Anymail is only building on Django 1.8 and 1.9.

Most of the code (from Djrill) is still compatible all the way back to Django 1.4. There are a few tests that are using some newer timezone pieces.

If it's important for people migrating from Djrill, it wouldn't be too hard to maintain compatibility with older Django versions.

But if everyone's already on Django 1.8 or later, that effort could probably be better spent elsewhere.

Email parser error message improvement

We came across some issues when getting Anymail set up with our project. Ended up figuring out that the issue was that we were setting message.reply_to to a string instead of a list.

Could be a minor improvement to either (1) automatically check if that variable is set to a string and automatically put it as a single-element list, or (2) throw a more helpful error message when any variable that should be a list of email addresses like to, bcc, cc, reply_to is the incorrect data type.

You can reproduce this by trying to send an email where message.reply_to = "[email protected]" instead of message.reply_to = ["[email protected]"].

Minor little thing, but could prevent some issues in the future, since the resulting error message is pretty cryptic.

I realize this issue is pretty devoid of any specifics, so feel free to let me know if any more details would help.

mandrill error with new key init

when adding a new webhook to mandrill, it verifies my webhook url:

import mandrill
mandrill_client = mandrill.Mandrill('SOMEKEY')
 events = ['send', 'open', 'click']
result = mandrill_client.webhooks.add(url='https://AUTH:[email protected]/webhooks/mail/mandrill/tracking/', description='PreProd', events=events)

but this fails with You must set ANYMAIL_MANDRILL_WEBHOOK_KEY or ANYMAIL = {'MANDRILL_WEBHOOK_KEY': ...} or MANDRILL_WEBHOOK_KEY in your Django settings as I don't have the key yet.

hence, no key, no verification, no verification, no key... hen-egg issue...

pls help!

Consider including esp_extra to EmailMultiAlternatives example in Anymail 1-2-3

The Anymail 1-2-3 document example is for Mailgun, but it does not directly address Mailgun's suggestion of using a subdomain to send mail:

"We recommend using a subdomain with Mailgun, like “mg.mydomain.com”. Using a subdomain you will still be able to send emails from your root domain e.g. “[email protected]”."

Anymail does support this setting via esp_extra, but the example using EmailMultiAlternatives does not demonstrate the use of this.

Since the recommendation from mailgun is to use a subdomain, I recommend adding a line,

msg.esp_extra = {"sender_domain": "example.com"}, # change to mg.example.com or other subdomain if necessary.

which demonstrates how to manually set this. This helps avoid a potential beginner stumbling block.

Anymail doesn't want me to use local settings

Hello,

I usually have a settings.py file and a local_settings.py file, so that my private info like api keys aren't published when I sync my repos to github.

However, when using anymail and setting my mailgun API key in local_settings.py, I get the following error message :
You must set ANYMAIL_MAILGUN_API_KEY or ANYMAIL = {'MAILGUN_API_KEY': ...} or MAILGUN_API_KEY in your Django settings

Here is the code I've been using to bring the local_settings into the settings.py file :

try: from local_settings import *
except ImportError: pass

Is my code wrong, or does anymail have a test that my local_settings won't pass ?

Isn't clear how to use the testing backend

So I've been using anymail for a while to send emails but now I've started work on tracking emails and I'm running into some issues around testing. The issue at the moment is that I've got lots of places in the code that read:

msg = self.generate_email()
msg.send()

email_sent = self.create_sent_object(email_id=msg.message_id)

Where generate_email is creating an EmailMultiAlternatives object. This is failing in tests because msg.message_id isn't working in tests. Presumably this is because Django is switching the email backend out on tests so they don't actually get sent. Based off what I've seen in this packages tests I've tried:

@override_settings(EMAIL_BACKEND='anymail.backends.test.TestBackend', ANYMAIL_TEST_SAMPLE_SETTING='sample',)

But that doesn't seem to be helping at all, also I have no idea what ANYMAIL_TEST_SAMPLE_SETTING is doing so 'sample' might be wrong. I can't seem to find anything in the docs covering what to do in this situation. Happy to write it up into the docs once I've got it working as it might be useful for other people.

Emails using SparkPost templates arrive empty

I use two approaches for sending email:

  1. Use django.core.mail.send_mail() and send the email template along with the parameters.
  2. Use django.core.mail.EmailMessage() and django.core.mail.get_connection().send_messages() to use an email template in my ESP account.

I have been using Mandrill up until now, and want to switch to SparkPost. I have configured and published templates in both my Mandrill and SparkPost accounts, and my API keys for both services. The slug for a given template on Mandrill is the same as both the name and ID of the equivalent template on SparkPost. I used SparkPost's migration tool so this should be correct.

When I switch the email backend from anymail.backends.mandrill.MandrillBackend to anymail.backends.sparkpost.SparkPostBackend, the emails sent via send_mail() come through fine, but the emails using the templates, sent via send_messages(), arrive with an empty message body, i.e. there is no email. I can send the templates as test emails with dummy data via the SparkPost dashboard just fine.

SSLError: bad handshake: SysCallError(104, 'ECONNRESET')

It looks like Mailgun has been dealing with an ongoing DDOS this morning, which caused a flurry of exceptions to be thrown by requests as used by django-anymail.

I'm honestly not sure if this is something django-anymail should catch and raise its own error, or if it should (immediately) retry the send, or leave the exception as-is and put the burden on the django application.

Slightly anonymized trace:

      File "my_code.py", line 274, in scheduled_emails
        email_msg.send()
      File "django/core/mail/message.py", line 303, in send
        return self.get_connection(fail_silently).send_messages([self])
      File "anymail/backends/base.py", line 82, in send_messages
        sent = self._send(message)
      File "anymail/backends/base_requests.py", line 56, in _send
        return super(AnymailRequestsBackend, self)._send(message)
      File "anymail/backends/base.py", line 110, in _send
        response = self.post_to_esp(payload, message)
      File "anymail/backends/base_requests.py", line 68, in post_to_esp
        response = self.session.request(**params)
      File "requests/sessions.py", line 468, in request
        resp = self.send(prep, **send_kwargs)
      File "requests/sessions.py", line 576, in send
        r = adapter.send(request, **kwargs)
      File "requests/adapters.py", line 447, in send
        raise SSLError(e, request=request)

Django 1.10 support

Tests are passing with Django 1.10 alpha.

Need to update runtests.py settings: "RemovedInDjango20Warning: Old-style middleware using settings.MIDDLEWARE_CLASSES is deprecated. Update your middleware and use settings.MIDDLEWARE instead."

ValueError in MailgunTrackingWebhookView.esp_to_anymail_event

Given the following esp_event from a Mailgun webhook:

{
'attachment-count': '1', 
'code': '5.1.1', 
'domain': 'mg.otovo.no', 
'error': 'smtp;550 5.1.1 RESOLVER.ADR.RecipNotFound; not found', 
'message-headers': '[["Received", "from drex02.felles.ds.nrk.no (2002:a043:8763::a043:8763) by MAEX02.felles.ds.nrk.no (2002:a043:8797::a043:8797) with Microsoft SMTP Server (TLS) id 15.0.1210.3; Wed, 26 Apr 2017 21:40:34 +0200"], ["Received", "from EUR02-VE1-obe.outbound.protection.outlook.com (213.199.154.48) by drex02.felle..., 
'message-id': '[email protected]', 
'tag': 'calculation', 
'token': 'exxx'
}

esp_to_anymail_event fails with a ValueError trying to turn '5.1.1' into an int.

Stacktrace:

ValueError: invalid literal for int() with base 10: '5.1.1'
  File "django/core/handlers/exception.py", line 41, in inner
    response = get_response(request)
  File "django/core/handlers/base.py", line 187, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "django/core/handlers/base.py", line 185, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "django/views/generic/base.py", line 68, in view
    return self.dispatch(request, *args, **kwargs)
  File "django/utils/decorators.py", line 67, in _wrapper
    return bound_func(*args, **kwargs)
  File "django/views/decorators/csrf.py", line 58, in wrapped_view
    return view_func(*args, **kwargs)
  File "django/utils/decorators.py", line 63, in bound_func
    return func.__get__(self, type(self))(*args2, **kwargs2)
  File "anymail/webhooks/base.py", line 109, in dispatch
    return super(AnymailBaseWebhookView, self).dispatch(request, *args, **kwargs)
  File "django/views/generic/base.py", line 88, in dispatch
    return handler(request, *args, **kwargs)
  File "anymail/webhooks/base.py", line 123, in post
    events = self.parse_events(request)
  File "anymail/webhooks/mailgun.py", line 41, in parse_events
    return [self.esp_to_anymail_event(request.POST)]
  File "anymail/webhooks/mailgun.py", line 91, in esp_to_anymail_event
    mta_status = int(esp_event['code'])

This might be a mailgun bug as well :)

SendGrid transactional emails with templates are received as plain text.

I decided to use SendGrid because of the nice WUSIWUG email editor that they have. I made all of my email templates nice and beautiful there. I copied the code from the django-anymail documentation and everythink is working just fine. The emails are interpolated correctly but there is no html at all. They are just plain text.

Here is the code that I have:

def send_mail(self, recipient, template_id, context, **kwargs):
    message = EmailMessage(
        subject=" ",
        body=" ",
        from_email=settings.DJANGO_DEFAULT_FROM_EMAIL,
        to=[recipient]
    )

    message.template_id = template_id  # SendGrid id
    message.merge_data = {
        recipient: context,
    }

    message.send()

I tried many email clients but there is no html email at all. Just a plain text 😢

Prevent Gmail from grouping same subjects Emails

Hey,
I'm using Anymail with Mailgun. My server is sending messages to my Users frquently with always same subject. This causes Gmail to group them in same thread.

Is there any solution to avoid that? It's not so clear how Gmail decides to group mails or not. For Example Amazon sends me always the same subject Email with same sender for each of my orders. But they appear in different threads for Amazon and not for me.

Any idea?

Normalizing Mandrill's soft_bounce Status

Anymail currently normalizes Mandrill's 'soft_bounce' status to 'deferred'. The Anymail documentation defines the normalized 'deferred' status as "...the ESP will keep trying to deliver the message, and should generate a separate bounced event if later it gives up." However, according to Mandrill's documentation, Mandrill does not attempt to redeliver either a 'hard_bounce' or 'soft_bounce': "After a bounce occurs for a recipient email address, Mandrill stops attempting delivery of the email. Further email to that address will be rejected temporarily."

I propose that Mandrill's 'soft_bounce' status be normalized to 'bounced' to be consistent with both Mandrill's and Anymail's documentation.

Support for backend email template variable sending.

Mandrill uses a concept called merge_vars, SendGrid template variables are called substitutions, and Postmark has a TemplateModel concept.

While all three services receive template variables differently within their API, they have a common philosophy on setting programmatic key value pairs to leverage on their respective services.

What does that look like via the EmailBackend framework, I am not sure at the moment. However, I think this feature request can fall within the scope of this library.

Postmark: Reply-To must be moved out of email headers

Postmark's API disallows Reply-To in the message headers; it's only permitted in Postmark's ReplyTo param.

# This works:
EmailMessage(..., reply_to=['[email protected]']).send()

# This doesn't:
EmailMessage(..., headers={'Reply-To': '[email protected]'}).send()

Using the headers was the only option before Django added the explicit reply_to param. There may be some old code out there still using the headers approach.

Anymail should relocate Reply-To from the generic headers, if needed.

SendGrid: update to new v3 send API

SendGrid has released a new v3 send API. Should update Anymail to switch to that.

  • Maybe consider running on official sendgrid-python package? (It's receiving maintenance again.)
  • Will cause breaking changes in esp_extra for SendGrid users. (But probably worth it, since v3 API params are generally cleaner -- no more x-smtpapi.)
  • Sandbox mode may be useful for integration tests
  • Still doesn't seem to support multiple Reply-To (in reply_to param)

Latest commit not available with pip install

Thanks for django-anymail but I notice that your latest release for 'Mailgun: Add MAILGUN_SENDER_DOMAIN setting' Is not available through 'pip install django-anymail[mailgun]'

It would be useful to have access to the 'MAILGUN_SENDER_DOMAIN' new feature.

thanks!

Show an "unreleased version" banner in RTD latest docs

ReadTheDocs' "latest" version comes from master, which is (usually) an unreleased development version. Most readers would probably prefer "stable" (which is the most-recent release tag).

Should add a banner to the top of "latest" docs pages warning the user it's for unreleased code. Similar to what Django does in their dev-version docs.

I think the best way to do this via JavaScript. (That's how RTD handles inserting their own "you are not using the most up to date version" warning in older versions.)

SSLError

Hello,
I am using anymail with mailgun for a year now and it has always been working perfectly. But for 48 hours now, I receive many error logs like this:

ERROR 2017-04-24 09:29:51,512 Error posting to https://api.mailgun.net/v3/example.com/messages:
SSLError: bad handshake: SysCallError(-1, 'Unexpected EOF')
Sending a message to XXX YYY [email protected] from AAA BBB [email protected]
Traceback (most recent call last):
File "./common/utils.py", line 26, in run
msg.send()
File "/usr/local/lib/python2.7/dist-packages/django/core/mail/message.py", line 342, in send
return self.get_connection(fail_silently).send_messages([self])
File "/usr/local/lib/python2.7/dist-packages/anymail/backends/base.py", line 86, in send_messages
sent = self._send(message)
File "/usr/local/lib/python2.7/dist-packages/anymail/backends/base_requests.py", line 56, in _send
return super(AnymailRequestsBackend, self)._send(message)
File "/usr/local/lib/python2.7/dist-packages/anymail/backends/base.py", line 116, in _send
response = self.post_to_esp(payload, message)
File "/usr/local/lib/python2.7/dist-packages/anymail/backends/base_requests.py", line 76, in post_to_esp
raised_from=err, email_message=message, payload=payload)
AnymailRequestsAPIError: Error posting to https://api.mailgun.net/v3/example.com/messages:
SSLError: bad handshake: SysCallError(-1, 'Unexpected EOF')
Sending a message to XXX YYY [email protected] from AAA BBB [email protected]
ERROR 2017-04-24 10:37:58,929 Error posting to https://api.mailgun.net/v3/example.com/messages:
SSLError: bad handshake: SysCallError(-1, 'Unexpected EOF')
Sending a message toXXX YYY [email protected] from AAA BBB [email protected]
Traceback (most recent call last):
File "./common/utils.py", line 26, in run
msg.send()
File "/usr/local/lib/python2.7/dist-packages/django/core/mail/message.py", line 342, in send
return self.get_connection(fail_silently).send_messages([self])
File "/usr/local/lib/python2.7/dist-packages/anymail/backends/base.py", line 86, in send_messages
sent = self._send(message)
File "/usr/local/lib/python2.7/dist-packages/anymail/backends/base_requests.py", line 56, in _send
return super(AnymailRequestsBackend, self)._send(message)
File "/usr/local/lib/python2.7/dist-packages/anymail/backends/base.py", line 116, in _send
response = self.post_to_esp(payload, message)
File "/usr/local/lib/python2.7/dist-packages/anymail/backends/base_requests.py", line 76, in post_to_esp
raised_from=err, email_message=message, payload=payload)
AnymailRequestsAPIError: Error posting to https://api.mailgun.net/v3/example.com/messages:
SSLError: bad handshake: SysCallError(-1, 'Unexpected EOF')
Sending a message to XXX YYY [email protected] from AAA BBB [email protected]

It looks like a SSL issue but I really do not understand why. This happens with arround 50% of the mails I send. It seems random

Clean up old Mandrill tests

  • Combine into three files (only): test_mandrill_backend.py, test_mandrill_integration.py, and test_mandrill_webhook.py.
  • Change test_mandrill_backend to use RequestsBackendMockAPITestCase, then delete Djrill-specific mock_backend.py

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.