diff --git a/.travis.yml b/.travis.yml index 69cfe51..81d676c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,7 +56,7 @@ install: - if [[ -n $DJANGO ]]; then pip install $DJANGO; fi # For now, install Anymail including all optional ESPs, and test at once # (in future, might want to matrix ESPs to test cross-dependencies) - - if [[ -n $DJANGO ]]; then pip install .[mailgun,mandrill,postmark,sendgrid,sparkpost]; fi + - if [[ -n $DJANGO ]]; then pip install .[mailgun,mailjet,mandrill,postmark,sendinblue,sendgrid,sparkpost]; fi - if [[ -n $FLAKE8 ]]; then pip install flake8; fi - pip list diff --git a/README.rst b/README.rst index 6f44491..d580810 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,5 @@ -Anymail: Django email backends for Mailgun, Mailjet, Postmark, SendGrid, SparkPost and more -=========================================================================================== +Anymail: Django email integration for transactional ESPs +======================================================== .. This README is reused in multiple places: * Github: project page, exactly as it appears here @@ -23,8 +23,8 @@ Anymail integrates several transactional email service providers (ESPs) into Dja with a consistent API that lets you use ESP-added features without locking your code to a particular ESP. -It currently fully supports Mailgun, Mailjet, Postmark, SendGrid, and SparkPost, -and has limited support for Mandrill. +It currently fully supports **Mailgun, Mailjet, Postmark, SendinBlue, SendGrid,** +and **SparkPost,** and has limited support for **Mandrill.** Anymail normalizes ESP functionality so it "just works" with Django's built-in `django.core.mail` package. It includes: diff --git a/docs/esps/index.rst b/docs/esps/index.rst index bfa5057..c0efb51 100644 --- a/docs/esps/index.rst +++ b/docs/esps/index.rst @@ -49,7 +49,7 @@ Email Service Provider |Mailgun| |Mailjet| |Mandrill .. rubric:: :ref:`Status ` and :ref:`event tracking ` ------------------------------------------------------------------------------------------------------------------------------------- :attr:`~AnymailMessage.anymail_status` Yes Yes Yes Yes Yes Yes Yes -|AnymailTrackingEvent| from webhooks Yes Yes Yes Yes Yes No Yes +|AnymailTrackingEvent| from webhooks Yes Yes Yes Yes Yes *Coming* Yes .. rubric:: :ref:`Inbound handling ` ------------------------------------------------------------------------------------------------------------------------------------- diff --git a/docs/esps/sendinblue.rst b/docs/esps/sendinblue.rst index 93d47cf..cc6cd57 100644 --- a/docs/esps/sendinblue.rst +++ b/docs/esps/sendinblue.rst @@ -3,26 +3,27 @@ SendinBlue ========== -Anymail integrates with the `SendinBlue`_ email service, using their `Web API v3`_. +Anymail integrates with the `SendinBlue`_ email service, using their `API v3`_. +SendinBlue's transactional API does not support some basic email features, such as +inline images. Be sure to review the :ref:`limitations ` below. .. important:: **Troubleshooting:** If your SendinBlue messages aren't being delivered as expected, be sure to look for - events in your SendinBlue `statistic panel`_. + events in your SendinBlue `logs`_. - SendGrid detects certain types of errors only *after* the send API call appears - to succeed, and reports these errors in the statistic panel. + SendinBlue detects certain types of errors only *after* the send API call reports + the message as "queued." These errors appear in the logging dashboard. .. _SendinBlue: https://www.sendinblue.com/ -.. _Web API v3: https://developers.sendinblue.com/docs -.. _statistic panel: https://app-smtp.sendinblue.com/statistics +.. _API v3: https://developers.sendinblue.com/docs +.. _logs: https://app-smtp.sendinblue.com/log Settings -------- - .. rubric:: EMAIL_BACKEND To use Anymail's SendinBlue backend, set: @@ -38,57 +39,187 @@ in your settings.py. .. rubric:: SENDINBLUE_API_KEY -The API key can be retrieved from the -`account settings`_. Make sure to get the -key for the version of the API you're -using..) +The API key can be retrieved from your SendinBlue `SMTP & API settings`_. +Make sure the version column indicates "v3." (v2 keys don't work with +Anymail. If you don't see a v3 key listed, use "Create a New API Key".) Required. .. code-block:: python ANYMAIL = { ... - "SENDINBLUE_API_KEY": "", + "SENDINBLUE_API_KEY": "", } Anymail will also look for ``SENDINBLUE_API_KEY`` at the root of the settings file if neither ``ANYMAIL["SENDINBLUE_API_KEY"]`` nor ``ANYMAIL_SENDINBLUE_API_KEY`` is set. -.. _account settings: https://account.sendinblue.com/advanced/api +.. _SMTP & API settings: https://account.sendinblue.com/advanced/api +.. setting:: ANYMAIL_SENDINBLUE_API_URL + +.. rubric:: SENDINBLUE_API_URL + +The base url for calling the SendinBlue API. + +The default is ``SENDINBLUE_API_URL = "https://api.sendinblue.com/v3/"`` +(It's unlikely you would need to change this.) + + +.. _sendinblue-esp-extra: + +esp_extra support +----------------- + +To use SendinBlue features not directly supported by Anymail, you can +set a message's :attr:`~anymail.message.AnymailMessage.esp_extra` to +a `dict` that will be merged into the json sent to SendinBlue's +`smtp/email API`_. + +Example: + + .. code-block:: python + + message.esp_extra = { + 'hypotheticalFutureSendinBlueParam': '2022', # merged into send params + } + + +(You can also set `"esp_extra"` in Anymail's :ref:`global send defaults ` +to apply it to all messages.) + +.. _smtp/email API: https://developers.sendinblue.com/v3.0/reference#sendtransacemail + + +.. _sendinblue-limitations: + Limitations and quirks ---------------------- +SendinBlue's v3 API has several limitations. In most cases below, +Anymail will raise an :exc:`~anymail.exceptions.AnymailUnsupportedFeature` +error if you try to send a message using missing features. You can +override this by enabling the :setting:`ANYMAIL_IGNORE_UNSUPPORTED_FEATURES` +setting, and Anymail will try to limit the API request to features +SendinBlue can handle. + +**Inline images** + SendinBlue's v3 API doesn't support inline images, at all. + (Confirmed with SendinBlue support Feb 2018.) + + If you are ignoring unsupported features, Anymail will try to send + inline images as ordinary image attachments. + +**Attachment names must be filenames with recognized extensions** + SendinBlue determines attachment content type by assuming the attachment's + name is a filename, and examining that filename's extension (e.g., ".jpg"). + + Trying to send an attachment without a name, or where the name does not end + in a supported filename extension, will result in a SendinBlue API error. + Anymail has no way to communicate an attachment's desired content-type + to the SendinBlue API if the name is not set correctly. + +**Additional template limitations** + If you are sending using a SendinBlue template, their API doesn't allow display + names in recipient or reply-to emails, and doesn't support overriding the template's + from_email, subject, or body. See the :ref:`templates ` + section below. + **Single Reply-To** SendinBlue's v3 API only supports a single Reply-To address. - If your message has multiple reply addresses, you'll get an - :exc:`~anymail.exceptions.AnymailUnsupportedFeature` error---or - if you've enabled :setting:`ANYMAIL_IGNORE_UNSUPPORTED_FEATURES`, + If you are ignoring unsupported features and have multiple reply addresses, Anymail will use only the first one. -**Attachment content-type** - Attachment content-type is determined from the filename - extension and you can't specify a different one. Trying - to send an attachment without a name or a name without - an extension generates an error with SendinBlue's API. +**Single tag** + SendinBlue supports a single message tag, which can be used for filtering in their + dashboard statistics and logs panels, and is available in tracking webhooks. + Anymail will pass the first of a message's :attr:`~anymail.message.AnymailMessage.tags` + to SendinBlue, using their :mailheader:`X-Mailin-tag` email header. -**Inline images** - SendinBlue doesn't support inline images at all, it - only support basic attachment. + Trying to send a message with more than one tag will result in an error unless you + are ignoring unsupported features. -**Email's display-names** - Email's display-names are only supported - **without** :attr:`template_id`. If you specify - a :attr:`template_id` all display-names will be hidden. +**Metadata** + Anymail passes :attr:`~anymail.message.AnymailMessage.metadata` to SendinBlue + as a JSON-encoded string using their :mailheader:`X-Mailin-custom` email header. + The metadata is available in tracking webhooks. -**Template's limitation** - If you use a template you will suffer some limitations: - you can't change the subject or/and the body, and all email's - display-names will be hidden. +**No delayed sending** + SendinBlue does not support :attr:`~anymail.message.AnymailMessage.send_at`. + +**No click-tracking or open-tracking options** + SendinBlue does not provide a way to control open or click tracking for individual + messages. Anymail's :attr:`~anymail.message.AnymailMessage.track_clicks` and + :attr:`~anymail.message.AnymailMessage.track_opens` settings are unsupported. **No envelope sender overrides** SendinBlue does not support overriding :attr:`~anymail.message.AnymailMessage.envelope_sender` on individual messages. + + +.. _sendinblue-templates: + +Batch sending/merge and ESP templates +------------------------------------- + +SendinBlue supports :ref:`ESP stored templates ` +populated with global merge data for all recipients, but does not +offer :ref:`batch sending ` with per-recipient merge data. +Anymail's :attr:`~anymail.message.AnymailMessage.merge_data` +message attribute is not supported with the SendinBlue backend. + +To use a SendinBlue template, set the message's +:attr:`~anymail.message.AnymailMessage.template_id` to the numeric +SendinBlue template ID, and supply substitution attributes using +the messages's :attr:`~anymail.message.AnymailMessage.merge_global_data`: + + .. code-block:: python + + message = EmailMessage( + subject=None, # required for SendinBlue templates + body=None, # required for SendinBlue templates + to=["alice@example.com"] # single recipient... + # ...multiple to emails would all get the same message + # (and would all see each other's emails in the "to" header) + ) + message.from_email = None # required for SendinBlue templates + message.template_id = 3 # use this SendinBlue template + message.merge_global_data = { + 'name': "Alice", + 'order_no': "12345", + 'ship_date': "May 15", + } + +Within your SendinBlue template body and subject, you can refer to merge +variables using %-delimited names, e.g., `%order_no%` or `%ship_date%` +from the example above. + +Note that SendinBlue's API does not permit overriding a template's +subject, body, or from_email. You *must* set them to `None` as shown above, +or Anymail will raise an :exc:`~anymail.exceptions.AnymailUnsupportedFeature` +error (if you are not ignoring unsupported features). + +Also, SendinBlue's API does not permit display names in recipient or reply-to +emails when sending with a template. Code like `to=["Alice "]` +will result in an unsupported feature error. (SendinBlue supports display names +only in *non*-template sends.) + + +.. _sendinblue-webhooks: + +Status tracking webhooks +------------------------ + +SendinBlue supports status tracking webhooks. Integration with Anymail's normalized +:ref:`status tracking ` is planned for a future release. + + +.. _sendinblue-inbound: + +Inbound webhook +--------------- + +SendinBlue does not support inbound email handling. diff --git a/docs/index.rst b/docs/index.rst index ecd7ec9..14bfa19 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,5 +1,5 @@ -Anymail: Django email backends for Mailgun, Mailjet, Postmark, SendGrid and more -================================================================================ +Anymail: Django email integration for transactional ESPs +======================================================== Version |release| diff --git a/setup.py b/setup.py index 2ec0100..2d09088 100644 --- a/setup.py +++ b/setup.py @@ -33,9 +33,10 @@ with open(path.join(here, 'README.rst'), encoding='utf-8') as f: setup( name="django-anymail", version=version, - description='Django email backends for Mailgun, Mailjet, Postmark, SendGrid, SparkPost ' + description='Django email integration for Mailgun, Mailjet, Postmark, SendGrid, SendinBlue, SparkPost ' 'and other transactional ESPs', - keywords="django, email, email backend, ESP, transactional mail, mailgun, mailjet, mandrill, postmark, sendgrid", + keywords="Django, email, email backend, ESP, transactional mail, " + "Mailgun, Mailjet, Mandrill, Postmark, SendinBlue, SendGrid, SparkPost", author="Mike Edmunds and Anymail contributors", author_email="medmunds@gmail.com", url="https://github.com/anymail/django-anymail", @@ -52,6 +53,7 @@ setup( "mandrill": [], "postmark": [], "sendgrid": [], + "sendinblue": [], "sparkpost": ["sparkpost"], }, include_package_data=True,