{"id":13448799,"url":"https://github.com/django-ses/django-ses","last_synced_at":"2026-02-07T01:27:05.438Z","repository":{"id":1346253,"uuid":"1292669","full_name":"django-ses/django-ses","owner":"django-ses","description":"A Django email backend for Amazon's Simple Email Service","archived":false,"fork":false,"pushed_at":"2026-02-03T19:43:24.000Z","size":691,"stargazers_count":1073,"open_issues_count":53,"forks_count":241,"subscribers_count":15,"default_branch":"main","last_synced_at":"2026-02-04T08:41:12.723Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://hmarr.com/2011/jan/26/using-amazons-simple-email-service-ses-with-django/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/django-ses.png","metadata":{"files":{"readme":"README.rst","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2011-01-25T19:41:40.000Z","updated_at":"2026-02-03T19:43:33.000Z","dependencies_parsed_at":"2024-01-11T17:47:22.010Z","dependency_job_id":"c344cd5c-99fd-453f-a5e9-1e5258944c58","html_url":"https://github.com/django-ses/django-ses","commit_stats":{"total_commits":277,"total_committers":79,"mean_commits":"3.5063291139240507","dds":0.7906137184115524,"last_synced_commit":"c01fd529156e08b91b57507332084632067cf85f"},"previous_names":[],"tags_count":63,"template":false,"template_full_name":null,"purl":"pkg:github/django-ses/django-ses","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/django-ses%2Fdjango-ses","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/django-ses%2Fdjango-ses/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/django-ses%2Fdjango-ses/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/django-ses%2Fdjango-ses/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/django-ses","download_url":"https://codeload.github.com/django-ses/django-ses/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/django-ses%2Fdjango-ses/sbom","scorecard":{"id":345840,"data":{"date":"2025-08-11","repo":{"name":"github.com/django-ses/django-ses","commit":"305933dd9cc225c726dba102d2fd4308a2eb0168"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.4,"checks":[{"name":"Code-Review","score":5,"reason":"Found 10/19 approved changesets -- score normalized to 5","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/ci.yml:1","Warn: no topLevel permission defined: .github/workflows/pypi.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":1,"reason":"2 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/django-ses/django-ses/ci.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/django-ses/django-ses/ci.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:40: update your workflow using https://app.stepsecurity.io/secureworkflow/django-ses/django-ses/ci.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:42: update your workflow using https://app.stepsecurity.io/secureworkflow/django-ses/django-ses/ci.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pypi.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/django-ses/django-ses/pypi.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/pypi.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/django-ses/django-ses/pypi.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pypi.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/django-ses/django-ses/pypi.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/pypi.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/django-ses/django-ses/pypi.yml/main?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/ci.yml:47","Warn: pipCommand not pinned by hash: .github/workflows/ci.yml:22","Info:   0 out of   6 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned","Info:   0 out of   2 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/pypi.yml:7"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 21 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":6,"reason":"4 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-79v4-65xg-pq4g","Warn: Project is vulnerable to: PYSEC-2022-42969","Warn: Project is vulnerable to: GHSA-pq67-6m6q-mj2v","Warn: Project is vulnerable to: GHSA-48p4-8xcf-vxj5"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-18T07:00:00.776Z","repository_id":1346253,"created_at":"2025-08-18T07:00:00.776Z","updated_at":"2025-08-18T07:00:00.776Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29183842,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-07T00:44:15.062Z","status":"ssl_error","status_checked_at":"2026-02-07T00:35:01.758Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T06:00:21.398Z","updated_at":"2026-02-07T01:27:05.431Z","avatar_url":"https://github.com/django-ses.png","language":"Python","funding_links":[],"categories":["Email","Python"],"sub_categories":[],"readme":"==========\nDjango-SES\n==========\n:Info: A Django email backend for Amazon's Simple Email Service\n:Author: Harry Marr (http://github.com/hmarr, http://twitter.com/harrymarr)\n:Collaborators: Paul Craciunoiu (http://github.com/pcraciunoiu, http://twitter.com/embrangler)\n\n|pypi| |pypi-downloads| |build| |python| |django|\n\nA bird's eye view\n=================\nDjango-SES is a drop-in mail backend for Django_. Instead of sending emails\nthrough a traditional SMTP mail server, Django-SES routes email through\nAmazon Web Services' excellent Simple Email Service (SES_).\n\nDjango-SES can also receive emails using `SES Email receiving`_\n\n.. _SES: http://aws.amazon.com/ses/\n.. _Django: http://djangoproject.com\n.. _SES Email receiving: https://docs.aws.amazon.com/ses/latest/dg/receiving-email.html\n\nPlease Contribute!\n==================\nThis project is maintained, but not actively used by the maintainer. Interested\nin helping maintain this project? Reach out via GitHub Issues if you're actively\nusing `django-ses` and would be interested in contributing to it.\n\n\nChangelog\n=========\n\nFor details about each release, see the GitHub releases page: https://github.com/django-ses/django-ses/releases or CHANGES.md.\n\n\nUsing Django directly\n=====================\n\nAmazon SES allows you to also setup usernames and passwords. If you do configure\nthings that way, you do not need this package. The Django default email backend\nis capable of authenticating with Amazon SES and correctly sending email.\n\nUsing django-ses gives you additional features like deliverability reports that\ncan be hard and/or cumbersome to obtain when using the SMTP interface.\n\n\nWhy SES instead of SMTP?\n========================\nConfiguring, maintaining, and dealing with some complicated edge cases can be\ntime-consuming. Sending emails with Django-SES might be attractive to you if:\n\n* You don't want to maintain mail servers.\n* You are already deployed on EC2 (In-bound traffic to SES is free from EC2\n  instances).\n* You need to send a high volume of email.\n* You don't want to have to worry about PTR records, Reverse DNS, email\n  whitelist/blacklist services.\n* You want to improve delivery rate and inbox cosmetics by DKIM signing\n  your messages using SES's Easy DKIM feature.\n* Django-SES is a truly drop-in replacement for the default mail backend.\n  Your code should require no changes.\n\nWhy SES instead of IMAP/POP?\n============================\n\nConfiguring, maintaining, and dealing with some complicated edge cases can be\ntime-consuming. Receiving emails with Django-SES might be attractive to you if:\n\n* You don't want to maintain mail servers.\n* You want programmatic access to received emails.\n* You want to react to received emails as soon as they are received.\n\nGetting going\n=============\nAssuming you've got Django_ installed, you'll just need to install django-ses::\n\n    pip install django-ses\n\n\nTo receive bounces or webhook events install the events \"extra\"::\n\n    pip install django-ses[events]\n\nAdd the following to your settings.py::\n\n    EMAIL_BACKEND = 'django_ses.SESBackend'\n\n    # These are optional if you are using AWS IAM Roles https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html\n    AWS_ACCESS_KEY_ID = 'YOUR-ACCESS-KEY-ID'\n    AWS_SECRET_ACCESS_KEY = 'YOUR-SECRET-ACCESS-KEY'\n    # https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-files.html\n    AWS_SESSION_PROFILE = 'YOUR-PROFILE-NAME'\n    # Additionally, if you are not using the default AWS region of us-east-1,\n    # you need to specify a region, like so:\n    AWS_SES_REGION_NAME = 'us-west-2'\n    AWS_SES_REGION_ENDPOINT = 'email.us-west-2.amazonaws.com'\n\n    # If you want to use the SESv2 client\n    USE_SES_V2 = True\n\n    # If you want to use SES Global Endpoint (Multi-Region Endpoint) for high availability\n    AWS_SES_GLOBAL_ENDPOINT_ID = 'abcdef12.g3h'  # Your global endpoint ID\n    USE_SES_V2 = True  # Required for global endpoints\n\nAlternatively, instead of `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`, you\ncan include the following two settings values. This is useful in situations\nwhere you would like to use a separate access key to send emails via SES than\nyou would to upload files via S3::\n\n    AWS_SES_ACCESS_KEY_ID = 'YOUR-ACCESS-KEY-ID'\n    AWS_SES_SECRET_ACCESS_KEY = 'YOUR-SECRET-ACCESS-KEY'\n\nNow, when you use ``django.core.mail.send_mail``, Simple Email Service will\nsend the messages by default.\n\nSince SES imposes a rate limit and will reject emails after the limit has been\nreached, django-ses will attempt to conform to the rate limit by querying the\nAPI for your current limit and then sending no more than that number of\nmessages in a two-second period (which is half of the rate limit, just to\nbe sure to stay clear of the limit). This is controlled by the following setting:\n\n    AWS_SES_AUTO_THROTTLE = 0.5 # (default; safety factor applied to rate limit)\n\nTo turn off automatic throttling, set this to None.\n\nCheck out the ``example`` directory for more information.\n\nMonitoring email status using Amazon Simple Notification Service (Amazon SNS)\n=============================================================================\nTo set this up, install `django-ses` with the `events` extra::\n\n    pip install django-ses[events]\n\nThen add a event URL handler in your `urls.py`::\n\n    from django.urls import re_path\n    from django_ses.views import SESEventWebhookView\n    from django.views.decorators.csrf import csrf_exempt\n    urlpatterns = [ ...\n            re_path(r'^ses/event-webhook/$', SESEventWebhookView.as_view(), name='handle-event-webhook'),\n            ...\n    ]\n\nSESEventWebhookView handles bounce, complaint, send, delivery, open and click events.\nIt is also capable of auto confirming subscriptions, it handles `SubscriptionConfirmation` notification.\n\nOn AWS\n-------\n1. Add an SNS topic.\n\n2. In SES setup an SNS destination in \"Configuration Sets\". Use this\nconfiguration set by setting ``AWS_SES_CONFIGURATION_SET``. Set the topic\nto what you created in 1.\n\n3. Add an https subscriber to the topic. (e.g., https://www.yourdomain.com/ses/event-webhook/)\nDo not check \"Enable raw message delivery\".\n\n\nBounces\n-------\nUsing signal 'bounce_received' for manager bounce email. For example::\n\n    from django_ses.signals import bounce_received\n    from django.dispatch import receiver\n\n\n    @receiver(bounce_received)\n    def bounce_handler(sender, mail_obj, bounce_obj, raw_message, *args, **kwargs):\n        # you can then use the message ID and/or recipient_list(email address) to identify any problematic email messages that you have sent\n        message_id = mail_obj['messageId']\n        recipient_list = mail_obj['destination']\n        ...\n        print(\"This is bounce email object\")\n        print(mail_obj)\n\nThe most common use case for irrecoverable bounces (status ``5xx``) is to add the\nemail(s) that caused the bounce to a blacklist in order to avoid sending more\nemails and triggering more bounces. ``django-ses`` provides a built-in blacklist\nthat does this. Check ``AWS_SES_ADD_BOUNCE_TO_BLACKLIST`` and ``AWS_SES_USE_BLACKLIST``.\n\nComplaint\n---------\nUsing signal 'complaint_received' for manager complaint email. For example::\n\n    from django_ses.signals import complaint_received\n    from django.dispatch import receiver\n\n\n    @receiver(complaint_received)\n    def complaint_handler(sender, mail_obj, complaint_obj, raw_message,  *args, **kwargs):\n        ...\n\nThe most common use case for complaints is to add the email(s) that caused the\ncomplaint to a blacklist in order to avoid sending more emails and triggering\nmore complaints. ``django-ses`` provides a built-in blacklist that does this.\nCheck ``AWS_SES_ADD_COMPLAINT_TO_BLACKLIST`` and ``AWS_SES_USE_BLACKLIST``.\n\nMessage sent\n------------\n\nUse this event to know when an email was sent. Keep in mind that the\n``extra_headers`` field of the message will contain the ``message_id`` that AWS\nSES assigned to the email, which means you could use this event to store emails\nand cross-reference them later if/when you receive a bounce/complaint. For\nexample::\n\n    from django_ses.signals import message_sent\n    from django.dispatch import receiver\n\n\n    @receiver(message_sent)\n    def sent_handler(sender, message,  *args, **kwargs):\n        ...\n\nSend\n----\nUsing signal 'send_received' for manager send email. For example::\n\n    from django_ses.signals import send_received\n    from django.dispatch import receiver\n\n\n    @receiver(send_received)\n    def send_handler(sender, mail_obj, raw_message,  *args, **kwargs):\n        ...\n\nDelivery\n--------\nUsing signal 'delivery_received' for manager delivery email. For example::\n\n    from django_ses.signals import delivery_received\n    from django.dispatch import receiver\n\n\n    @receiver(delivery_received)\n    def delivery_handler(sender, mail_obj, delivery_obj, raw_message,  *args, **kwargs):\n        ...\n\nOpen\n----\nUsing signal 'open_received' for manager open email. For example::\n\n    from django_ses.signals import open_received\n    from django.dispatch import receiver\n\n\n    @receiver(open_received)\n    def open_handler(sender, mail_obj, open_obj, raw_message, *args, **kwargs):\n        ...\n\nClick\n-----\nUsing signal 'click_received' for manager send email. For example::\n\n    from django_ses.signals import click_received\n    from django.dispatch import receiver\n\n\n    @receiver(click_received)\n    def click_handler(sender, mail_obj, click_obj, raw_message, *args, **kwargs):\n        ...\n\nTesting Signals\n===============\n\nIf you would like to test your signals, you can optionally disable `AWS_SES_VERIFY_EVENT_SIGNATURES` in settings. Examples for the JSON object AWS SNS sends can be found here: https://docs.aws.amazon.com/sns/latest/dg/sns-message-and-json-formats.html#http-subscription-confirmation-json\n\nSES Event Monitoring with Configuration Sets\n============================================\n\nYou can track your SES email sending at a granular level using `SES Event Publishing`_.\nTo do this, you set up an SES Configuration Set and add event\nhandlers to it to send your events on to a destination within AWS (SNS,\nCloudwatch or Kinesis Firehose) for further processing and analysis.\n\nTo ensure that emails you send via `django-ses` will be tagged with your\nSES Configuration Set, set the `AWS_SES_CONFIGURATION_SET` setting in your\nsettings.py to the name of the configuration set::\n\n    AWS_SES_CONFIGURATION_SET = 'my-configuration-set-name'\n\nThis will add the `X-SES-CONFIGURATION-SET` header to all your outgoing\ne-mails.\n\nIf you want to set the SES Configuration Set on a per message basis, set\n`AWS_SES_CONFIGURATION_SET` to a callable.  The callable should conform to the\nfollowing prototype::\n\n    def ses_configuration_set(message, dkim_domain=None, dkim_key=None,\n                                dkim_selector=None, dkim_headers=()):\n        configuration_set = 'my-default-set'\n        # use message and dkim_* to modify configuration_set\n        return configuration_set\n\n    AWS_SES_CONFIGURATION_SET = ses_configuration_set\n\nwhere\n\n* `message` is a `django.core.mail.EmailMessage` object (or subclass)\n* `dkim_domain` is a string containing the DKIM domain for this message\n* `dkim_key` is a string containing the DKIM private key for this message\n* `dkim_selector` is a string containing the DKIM selector (see DKIM, below for\n  explanation)\n* `dkim_headers` is a list of strings containing the names of the headers to\n  be DKIM signed (see DKIM, below for explanation)\n\n.. _SES Event Publishing: https://docs.aws.amazon.com/ses/latest/DeveloperGuide/monitor-using-event-publishing.html\n\n\nSES Global Endpoint (Multi-Region Endpoint)\n============================================\n\nAWS SES Global Endpoints (also known as Multi-Region Endpoints or MREPs) offer\nmulti-region high availability and automatic failover. When you use a global\nendpoint, AWS automatically distributes your email sending traffic between two\nAWS regions and handles failover if one region experiences issues.\n\nBenefits\n--------\n\n* **High Availability**: Automatic failover between two AWS regions\n* **Improved Resilience**: Reduced impact from regional outages\n* **Traffic Distribution**: Automatic load balancing between primary and secondary regions\n\nPrerequisites\n-------------\n\nTo use global endpoints, you must:\n\n1. **Use SES API v2**: Set ``USE_SES_V2 = True`` (global endpoints require v2)\n2. **Create a Multi-Region Endpoint**: Create via AWS Console or CLI\n3. **Configure both regions**: Verify identities and configure settings in both primary and secondary regions\n4. **Obtain the Endpoint ID**: Get the endpoint ID (e.g., ``abcdef12.g3h``) from the AWS Console or CLI\n\nCreating a Global Endpoint\n---------------------------\n\n**Via AWS Console:**\n\n1. Navigate to SES Console → **Global Endpoints**\n2. Click **Create global endpoint**\n3. Enter a name and select secondary region\n4. Click **Create global endpoint**\n5. Copy the **Endpoint ID** from the details page\n\n**Via AWS CLI:**\n\n.. code-block:: bash\n\n    aws sesv2 create-multi-region-endpoint \\\n      --primary-region us-west-2 \\\n      --secondary-region us-east-1 \\\n      --endpoint-name MyGlobalEndpoint\n\n    # Get the endpoint ID\n    aws sesv2 get-multi-region-endpoint \\\n      --endpoint-name MyGlobalEndpoint \\\n      --region us-west-2\n\nUsage\n-----\n\nTo enable the SES global endpoint in django-ses, add these settings::\n\n    USE_SES_V2 = True  # Required\n    AWS_SES_GLOBAL_ENDPOINT_ID = 'abcdef12.g3h'  # Your endpoint ID\n\nWhen ``AWS_SES_GLOBAL_ENDPOINT_ID`` is set, django-ses will include the\n``EndpointId`` parameter in all ``send_email`` API calls.\n\nRecommendations\n---------------\n\nAWS recommends using global endpoints with Configuration Sets for better\ntracking and management::\n\n    USE_SES_V2 = True\n    AWS_SES_GLOBAL_ENDPOINT_ID = 'abcdef12.g3h'\n    AWS_SES_CONFIGURATION_SET = 'my-configuration-set'\n\n**Important:** Ensure that configuration sets, verified identities, and\nsending limits are configured in both primary and secondary regions.\n\nMonitoring\n----------\n\nMonitor your global endpoint metrics in the AWS Console under\n**Global Endpoints → [Your Endpoint] → Cross-region metrics**.\n\nFor more information, see the `AWS SES documentation on global endpoints`_.\n\n.. _AWS SES documentation on global endpoints: https://docs.aws.amazon.com/ses/latest/dg/global-endpoints.html\n\n\nDKIM\n====\n\nUsing DomainKeys_ is entirely optional, however it is recommended by Amazon for\nauthenticating your email address and improving delivery success rate.  See\nhttp://docs.amazonwebservices.com/ses/latest/DeveloperGuide/DKIM.html.\nBesides authentication, you might also want to consider using DKIM in order to\nremove the `via email-bounces.amazonses.com` message shown to gmail users -\nsee http://support.google.com/mail/bin/answer.py?hl=en\u0026answer=1311182.\n\nCurrently there are two methods to use DKIM with Django-SES: traditional Manual\nSigning and the more recently introduced Amazon Easy DKIM feature.\n\nEasy DKIM\n---------\nEasy DKIM is a feature of Amazon SES that automatically signs every message\nthat you send from a verified email address or domain with a DKIM signature.\n\nYou can enable Easy DKIM in the AWS Management Console for SES. There you can\nalso add the required domain verification and DKIM records to Route 53 (or\ncopy them to your alternate DNS).\n\nOnce enabled and verified Easy DKIM needs no additional dependencies or\nDKIM specific settings to work with Django-SES.\n\nFor more information and a setup guide see:\nhttp://docs.aws.amazon.com/ses/latest/DeveloperGuide/easy-dkim.html\n\nManual DKIM Signing\n-------------------\nTo enable Manual DKIM Signing you should install the pydkim_ package and specify values\nfor the ``DKIM_PRIVATE_KEY`` and ``DKIM_DOMAIN`` settings.  You can generate a\nprivate key with a command such as ``openssl genrsa 512`` and get the public key\nportion with ``openssl rsa -pubout \u003cprivate.key``.  The public key should be\npublished to ``ses._domainkey.example.com`` if your domain is example.com.  You\ncan use a different name instead of ``ses`` by changing the ``DKIM_SELECTOR``\nsetting.\n\nThe SES relay will modify email headers such as `Date` and `Message-Id` so by\ndefault only the `From`, `To`, `Cc`, `Subject` headers are signed, not the full\nset of headers.  This is sufficient for most DKIM validators but can be overridden\nwith the ``DKIM_HEADERS`` setting.\n\n\nExample settings.py::\n\n   DKIM_DOMAIN = 'example.com'\n   DKIM_PRIVATE_KEY = '''\n   -----BEGIN RSA PRIVATE KEY-----\n   xxxxxxxxxxx\n   -----END RSA PRIVATE KEY-----\n   '''\n\nExample DNS record published to Route53 with boto:\n\n   route53 add_record ZONEID ses._domainkey.example.com. TXT '\"v=DKIM1; p=xxx\"' 86400\n\n\n.. _DomainKeys: http://dkim.org/\n\n\nIdentity Owners\n===============\n\nWith Identity owners, you can use validated SES-domains across multiple accounts:\nhttps://docs.aws.amazon.com/ses/latest/DeveloperGuide/sending-authorization-delegate-sender-tasks-email.html\n\nThis is useful if you got multiple environments in different accounts and still want to send mails via the same domain.\n\nYou can configure the following environment variables to use them as described in boto3-docs_::\n\n    AWS_SES_SOURCE_ARN=arn:aws:ses:eu-central-1:012345678910:identity/example.com\n    AWS_SES_FROM_ARN=arn:aws:ses:eu-central-1:012345678910:identity/example.com\n    AWS_SES_RETURN_PATH_ARN=arn:aws:ses:eu-central-1:012345678910:identity/example.com\n\n.. _boto3-docs: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ses.html#SES.Client.send_raw_email\n\n\nSES Sending Stats\n=================\n\nDjango SES comes with two ways of viewing sending statistics.\n\nThe first one is a simple read-only report on your 24 hour sending quota,\nverified email addresses and bi-weekly sending statistics.\n\nTo enable the dashboard to retrieve data from AWS, you need to update the IAM policy by adding the following actions::\n\n    {\n        \"Effect\": \"Allow\",\n        \"Action\": [\n            \"ses:ListVerifiedEmailAddresses\",\n            \"ses:GetSendStatistics\"\n        ],\n        \"Resource\": \"*\"\n    }\n\nTo generate and view SES sending statistics reports, include, update\n``INSTALLED_APPS``::\n\n    INSTALLED_APPS = (\n        # ...\n        'django.contrib.admin',\n        'django_ses',\n        # ...\n    )\n\n... and ``urls.py``::\n\n    urlpatterns += (url(r'^admin/django-ses/', include('django_ses.urls')),)\n\n*Optional enhancements to stats:*\n\nOverride the dashboard view\n---------------------------\nYou can override the Dashboard view, for example, to add more context data::\n\n    class CustomSESDashboardView(DashboardView):\n        def get_context_data(self, **kwargs):\n            context = super().get_context_data(**kwargs)\n            context.update(**admin.site.each_context(self.request))\n            return context\n\nThen update your urls::\n\n    urlpatterns += path('admin/django-ses/', CustomSESDashboardView.as_view(), name='django_ses_stats'),\n\n\nLink the dashboard from the admin\n---------------------------------\nYou can use adminplus for this (https://github.com/jsocol/django-adminplus)::\n\n    from django_ses.views import DashboardView\n    admin.site.register_view('django-ses', DashboardView.as_view(), 'Django SES Stats')\n\n\n\nStore daily stats\n-----------------\nIf you need to keep send statistics around for longer than two weeks,\ndjango-ses also comes with a model that lets you store these. To use this\nfeature you'll need to run::\n\n    python manage.py migrate\n\nTo collect the statistics, run the ``get_ses_statistics`` management command\n(refer to next section for details). After running this command the statistics\nwill be viewable via ``/admin/django_ses/``.\n\nDjango SES Management Commands\n==============================\n\nTo use these you must include ``django_ses`` in your INSTALLED_APPS.\n\nManaging Verified Email Addresses\n---------------------------------\n\nManage verified email addresses through the management command.\n\n    python manage.py ses_email_address --list\n\nAdd emails to the verified email list through:\n\n    python manage.py ses_email_address --add john.doe@example.com\n\nRemove emails from the verified email list through:\n\n    python manage.py ses_email_address --delete john.doe@example.com\n\nYou can toggle the console output through setting the verbosity level.\n\n    python manage.py ses_email_address --list --verbosity 0\n\n\nCollecting Sending Statistics\n-----------------------------\n\nTo collect and store SES sending statistics in the database, run:\n\n    python manage.py get_ses_statistics\n\nSending statistics are aggregated daily (UTC time). Stats for the latest day\n(when you run the command) may be inaccurate if run before end of day (UTC).\nIf you want to keep your statistics up to date, setup ``cron`` to run this\ncommand a short time after midnight (UTC) daily.\n\n\nManaging the blacklist\n-----------------------------\n\nTo manage the blacklist (add, remote, list), run:\n\n    python manage.py blacklist\n\nDjango Built-in Error Emails\n==============================\n\nIf you'd like Django's `Built-in Email Error Reporting`_ to function properly\n(actually send working emails), you'll have to explicitly set the\n``SERVER_EMAIL`` setting to one of your SES-verified addresses. Otherwise, your\nerror emails will all fail and you'll be blissfully unaware of a problem.\n\n*Note:* You will need to sign up for SES_ and verify any emails you're going\nto use in the `from_email` argument to `django.core.mail.send_email()`. Boto_\nhas a `verify_email_address()` method: https://github.com/boto/boto/blob/master/boto/ses/connection.py\n\n.. _Boto: http://boto.cloudhackers.com/\n.. _Built-in Email Error Reporting: https://docs.djangoproject.com/en/dev/howto/error-reporting/\n\n\nReceiving emails\n================\n\nIn order to setup your AWS SES account to receive emails you should follow the\nofficial `SES Email receiving setup`_ instructions. Here is a quick sum up:\n\n1. Add an ``MX`` entry to your domain, pointing to ``inbound-smtp.us-east-1.amazonaws.com`` (or the region of your choice).\n2. Create a new rule set in the ``Email receiving`` section.\n3. Create a new rule in the newly created rule set.\n4. Create a new recipient condition for that rule. In the ``actions`` step pick\n   either ``Publish to Amazon SNS topic`` or ``Deliver to S3 bucket``. Also create\n   a new SNS topic. That should point to ``https://your-django-ses-app/ses/event-webhook/``.\n   Don't enable raw message delivery.\n\nThe difference between ``SNS`` and ``S3`` in the 4th step is that ``SNS`` will\ndeliver the entire email message (headers, subject, content and attachments)\ndirectly to your endpoint; ``S3``, on the other hand, will store the email message\nin a ``S3`` bucket and will deliver to your endpoint the bucket name and the\nfile path, then your webhook handler should fetch that file from the ``S3`` bucket\nin order to get the actual email object.\n\nThe ``SNS`` way is easier to setup, but it only supports messages up to 150kb,\nincluding headers.\n\nDepending which method you selected in step 4 you should inherit either from the\n``SnsHandler`` or the ``S3Handler`` class and create your own handler.\nYou should then set the path of your handler in the ``AWS_SES_INBOUND_HANDLER``\nsetting (e.g., ``AWS_SES_INBOUND_HANDLER='my_app.service.MyReceiver'``).\n\nExample\n\n.. code-block:: python\n\n   from django_ses.inbound import SnsHandler\n\n   class MyReceiver(SnsHandler):\n       def process(self):\n           print(self.email.get(\"subject\"))\n           print(self.email.get(\"plain_text\"))\n\n\nThe email parsing logic in Django-SES has been kept simple in order to avoid\nextra dependencies. If you wish to parse emails yourself or with a third party\npackage, you can reimplement the ``parse_email`` method:\n\n.. code-block:: python\n\n   import mailparser\n   from django_ses.inbound import\n\n   class MyReceiver(SnsHandler):\n       def parse_email(self, content):\n           return mailparser.parse_from_bytes(content)\n\n       def process(self):\n           print(self.email.subject)\n           print(self.email.body)\n\n\n.. _SES Email receiving setup: https://docs.aws.amazon.com/ses/latest/dg/receiving-email-setting-up.html\n\nRequirements\n============\ndjango-ses requires supported version of Django or Python.\n\n\nFull List of Settings\n=====================\n\n``AWS_ACCESS_KEY_ID``, ``AWS_SECRET_ACCESS_KEY``\n  *Required.* Your API keys for Amazon SES.\n\n``AWS_SES_ACCESS_KEY_ID``, ``AWS_SES_SECRET_ACCESS_KEY``\n  *Required.* Alternative API keys for Amazon SES. This is useful in situations\n  where you would like to use separate access keys for different AWS services.\n\n``AWS_SES_SESSION_TOKEN``, ``AWS_SES_SECRET_ACCESS_KEY``\n  Optional. Use `AWS_SES_SESSION_TOKEN` to provide session token\n  when temporary credentials are used. Details:\n  https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html\n  https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html\n\n``AWS_SES_REGION_NAME``, ``AWS_SES_REGION_ENDPOINT``\n  Optionally specify what region your SES service is using. Note that this is\n  required if your SES service is not using us-east-1, as omitting these settings\n  implies this region. Details:\n  http://readthedocs.org/docs/boto/en/latest/ref/ses.html#boto.ses.regions\n  http://docs.aws.amazon.com/general/latest/gr/rande.html\n\n``USE_SES_V2``\n  Optional. If you want to use client v2, you'll need to add `USE_SES_V2=True`.\n  Some settings will need this flag enabled.\n  See https://boto3.amazonaws.com/v1/documentation/api/1.26.31/reference/services/sesv2.html#id87\n\n``AWS_SES_GLOBAL_ENDPOINT_ID``\n  Optional. The ID of your AWS SES Global Endpoint (Multi-Region Endpoint).\n  Example: ``'abcdef12.g3h'``. When set, django-ses will include the\n  ``EndpointId`` parameter in SES API calls for multi-region high availability\n  and automatic failover. **Requires USE_SES_V2=True** (SES API v2 only).\n  Default is ``None``. See the Global Endpoints section for setup instructions.\n\n``AWS_SES_FROM_EMAIL``\n  Optional. The email address to be used as the \"From\" address for the email. The address that you specify has to be verified.\n  For more information please refer to https://boto3.amazonaws.com/v1/documentation/api/1.26.31/reference/services/sesv2.html#SESV2.Client.send_email\n\n``AWS_SES_RETURN_PATH``\n  Optional. Use `AWS_SES_RETURN_PATH` to receive complaint notifications\n  You must use the v2 client by setting `USE_SES_V2=True` for this setting to work, otherwise it is ignored.\n  https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendEmail.html#API_SendEmail_RequestSyntax\n\n``AWS_SES_CONFIGURATION_SET``\n  Optional. Use this to mark your e-mails as from being from a particular SES\n  Configuration Set. Set this to a string if you want all messages to have the\n  same configuration set.  Set this to a callable if you want to set\n  configuration set on a per message basis.\n\n``TIME_ZONE``\n  Default Django setting, optionally set this. Details:\n  https://docs.djangoproject.com/en/dev/ref/settings/#time-zone\n\n``DKIM_DOMAIN``, ``DKIM_PRIVATE_KEY``\n  Optional. If these settings are defined and the pydkim_ module is installed\n  then email messages will be signed with the specified key.   You will also\n  need to publish your public key on DNS; the selector is set to ``ses`` by\n  default.  See http://dkim.org/ for further detail.\n\n``AWS_SES_SOURCE_ARN``\n  Instruct Amazon SES to use a domain from another account.\n  For more information please refer to https://docs.aws.amazon.com/ses/latest/DeveloperGuide/sending-authorization-delegate-sender-tasks-email.html\n\n``AWS_SES_FROM_ARN``\n  Instruct Amazon SES to use a domain from another account.\n  For more information please refer to https://docs.aws.amazon.com/ses/latest/DeveloperGuide/sending-authorization-delegate-sender-tasks-email.html\n\n``AWS_SES_RETURN_PATH_ARN``\n  Instruct Amazon SES to use a domain from another account.\n  For more information please refer to https://docs.aws.amazon.com/ses/latest/DeveloperGuide/sending-authorization-delegate-sender-tasks-email.html\n\n``AWS_SES_VERIFY_EVENT_SIGNATURES``, ``AWS_SES_VERIFY_BOUNCE_SIGNATURES``\n  Optional. Default is True. Verify the contents of the message by matching the signature\n  you recreated from the message contents with the signature that Amazon SNS sent with the message.\n  See https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html for further detail.\n\n``EVENT_CERT_DOMAINS``, ``BOUNCE_CERT_DOMAINS``\n  Optional. Default is 'amazonaws.com' and 'amazon.com'.\n\n``AWS_SES_ADD_BOUNCE_TO_BLACKLIST``\n  If set to ``True`` (default ``False``) email addresses that triggered an\n  irrecoverable bounce (status in the ``5xx`` range) will be added to the\n  blacklist. Note that emails will be stored in lowercase.\n\n``AWS_SES_ADD_COMPLAINT_TO_BLACKLIST``\n  If set to ``True`` (default ``False``) email addresses that triggered a complaint\n  will be added to the blacklist. Note that emails will be stored in lowercase.\n\n``AWS_SES_USE_BLACKLIST``\n  If set to ``True`` (default ``False``), calls to the ``send_mail()`` method will\n  cause the recipients to be filtered using the blacklist. Any recipient that\n  exists in the blacklist will be removed from the email.\n  You must add ``django-ses`` to ``INSTALLED_APPS`` and run migrations to get\n  the database models required for this feature.\n\n``AWS_SES_INBOUND_ACCESS_KEY_ID``\n  If you're inheriting from the ``S3Handler``, you should set this so that\n  Django-SES can fetch the actual email message. Make sure to attach the right\n  permission policies to the IAM.\n\n``AWS_SES_INBOUND_SECRET_ACCESS_KEY``\n  Check ``AWS_SES_INBOUND_ACCESS_KEY_ID``.\n\n``AWS_SES_INBOUND_HANDLER``\n  If you want to receive emails with Django-SES, set this to the path where your\n  handler is (e.g., ``my_app.service.MyReceiver``).\n\n.. _pydkim: http://hewgill.com/pydkim/\n\nProxy\n=====\n\nIf you are using a proxy, please enable it via the env variables.\n\nIf your proxy server does not have a password try the following:\n\n.. code-block:: python\n\n   import os\n   os.environ[\"HTTP_PROXY\"] = \"http://proxy.com:port\"\n   os.environ[\"HTTPS_PROXY\"] = \"https://proxy.com:port\"\n\nif your proxy server has a password try the following:\n\n.. code-block:: python\n\n   import os\n   os.environ[\"HTTP_PROXY\"] = \"http://user:password@proxy.com:port\"\n   os.environ[\"HTTPS_PROXY\"] = \"https://user:password@proxy.com:port\"\n\nSource: https://stackoverflow.com/a/33501223/1331671\n\nContributing\n============\nIf you'd like to fix a bug, add a feature, etc\n\n#. Start by opening an issue.\n    Be explicit so that project collaborators can understand and reproduce the\n    issue, or decide whether the feature falls within the project's goals.\n    Code examples can be useful, too.\n\n#. File a pull request.\n    You may write a prototype or suggested fix.\n\n#. Check your code for errors, complaints.\n    Use `check.py \u003chttps://github.com/jbalogh/check\u003e`_\n\n#. Write and run tests.\n    Write your own test showing the issue has been resolved, or the feature\n    works as intended.\n\nGit hooks (via pre-commit)\n==========================\n\nWe use pre-push hooks to ensure that only linted code reaches our remote repository and pipelines aren't triggered in\nvain.\n\nTo enable the configured pre-push hooks, you need to `install \u003chttps://pre-commit.com/\u003e`_ pre-commit and run once::\n\n    pre-commit install -t pre-push -t pre-commit --install-hooks\n\nThis will permanently install the git hooks for both, frontend and backend, in your local\n``.git/hooks`` folder.\nThe hooks are configured in the ``.pre-commit-config.yaml``.\n\nYou can check whether hooks work as intended using the `run \u003chttps://pre-commit.com/#pre-commit-run\u003e`_ command::\n\n    pre-commit run [hook-id] [options]\n\nExample: run single hook::\n\n    pre-commit run ruff --all-files --hook-stage push\n\nExample: run all hooks of pre-push stage::\n\n    pre-commit run --all-files --hook-stage push\n\nRunning Tests\n=============\nTo run the tests::\n\n    python runtests.py\n\nIf you want to debug the tests, just add this file as a python script to your IDE run configuration.\n\nCreating a Release\n==================\n\nTo create a release:\n\n* Run ``poetry version {patch|minor|major}`` as explained in `the docs \u003chttps://python-poetry.org/docs/cli/#version\u003e`_. This will update the version in pyproject.toml.\n* Commit that change and use git to tag that commit with a version that matches the pattern ``v*.*.*``.\n* Push the tag and the commit (note some IDEs don't push tags by default).\n\n\n.. |pypi| image:: https://badge.fury.io/py/django-ses.svg\n    :target: http://badge.fury.io/py/django-ses\n.. |pypi-downloads| image:: https://img.shields.io/pypi/dm/django-ses?style=flat\n    :target: https://pypi.org/project/django-ses/\n.. |build| image:: https://github.com/django-ses/django-ses/actions/workflows/ci.yml/badge.svg\n    :target: https://github.com/django-ses/django-ses/actions/workflows/ci.yml\n.. |python| image:: https://img.shields.io/badge/python-3.8|3.9|3.10|3.11|3.12|3.13|3.14-blue.svg\n    :target: https://pypi.org/project/django-ses/\n.. |django| image:: https://img.shields.io/badge/django-3.2%7C%205.0+-blue.svg\n    :target: https://www.djangoproject.com/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdjango-ses%2Fdjango-ses","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdjango-ses%2Fdjango-ses","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdjango-ses%2Fdjango-ses/lists"}