Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added support for AWS SNS #35

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
All notable changes in **django-sms** are documented below.

## [Unreleased]
### Added
- The **sms.backends.aws.SmsBackend** to send messages using [AWS SNS](https://aws.amazon.com/sns/).

## [0.6.0]
### Changed
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- [Dummy backend](#dummy-backend)
- [MessageBird backend](#messagebird-backend)
- [Twilio backend](#twilio-backend)
- [AWS SNS backend](#aws-sns-backend)
- [Defining a custom SMS backend](#defining-a-custom-sms-backend)
- [Signals](#signals)
- [sms.signals.post_send](#sms.signals.post_send)
Expand Down Expand Up @@ -213,6 +214,23 @@ Make sure the Twilio Python SDK is installed by running the following command:
```console
pip install "django-sms[twilio]"
```
#### AWS SNS backend
The [AWS SNS](https://docs.aws.amazon.com/sns/latest/dg/sms_publish-to-phone.html#sms_publish_sdk) backend sends messages using `boto3`. To use this backend add the following settings:

```python
SMS_BACKEND = 'sms.backends.aws.SmsBackend'
AWS_SNS_REGION = 'your aws region code'
AWS_SNS_ACCESS_KEY_ID = 'your access key ID'
AWS_SNS_SECRET_ACCESS_KEY = 'your access key'
AWS_SNS_SENDER_ID = '1-11 character identifier for your service'
AWS_SNS_SMS_TYPE = 'Transactional' | 'Promotional'
```

Make sure `boto3` is installed by running the following command:

```console
pip install "django-sms[aws]"
```

### Defining a custom SMS backend
If you need to change how text messages are sent you can write your own SMS backend. The **SMS_BACKEND** setting in your settings file is then the Python import path for you backend class.
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,6 @@ def long_description() -> str:
extras_require={
'messagebird': ['messagebird'],
'twilio': ['twilio'],
'aws': ['boto3'],
}
)
96 changes: 96 additions & 0 deletions sms/backends/aws.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from typing import List, Optional

from django.conf import settings # type: ignore
from django.core.exceptions import ImproperlyConfigured # type: ignore

from sms.backends.base import BaseSmsBackend

try:
import boto3 # type: ignore
from botocore.exceptions import ClientError # type: ignore

HAS_BOTO3 = True
except ImportError:
HAS_BOTO3 = False


class SmsBackend(BaseSmsBackend):
def __init__(self, fail_silently: bool = False, **kwargs) -> None:
super().__init__(fail_silently=fail_silently, **kwargs)

if not HAS_BOTO3 and not self.fail_silently:
raise ImproperlyConfigured(
"You're using the SMS backend "
"'sms.backends.aws.SmsBackend' without having "
"'boto3' installed. Install 'boto3' or use "
"another SMS backend."
)

region_name: Optional[str] = getattr(settings, 'AWS_SNS_REGION')
if region_name is None and not self.fail_silently:
raise ImproperlyConfigured(
"You're using the SMS backend "
"'sms.backends.aws.SmsBackend' without having the "
"setting 'AWS_SNS_REGION' set."
)

access_key_id: Optional[str] = getattr(settings, 'AWS_SNS_ACCESS_KEY_ID')
if access_key_id is None and not self.fail_silently:
raise ImproperlyConfigured(
"You're using the SMS backend "
"'sms.backends.aws.SmsBackend' without having the "
"setting 'AWS_SNS_ACCESS_KEY_ID' set."
)

access_key: Optional[str] = getattr(settings, 'AWS_SNS_SECRET_ACCESS_KEY')
if access_key is None and not self.fail_silently:
raise ImproperlyConfigured(
"You're using the SMS backend "
"'sms.backends.aws.SmsBackend' without having the "
"setting 'AWS_SNS_SECRET_ACCESS_KEY' set."
)

self.sns_resource = None
if HAS_BOTO3:
self.sns_resource = boto3.resource(
'sns',
region_name=region_name,
aws_access_key_id=access_key_id,
aws_secret_access_key=access_key,
)

def send_messages(self, messages: List[Message]) -> int:
if self.sns_resource is None:
return 0

msg_count: int = 0
for message in messages:
for recipient in message.recipients:
try:
self.sns_resource.meta.client.publish(
PhoneNumber=recipient,
Message=message.body,
MessageAttributes={
'AWS.SNS.SMS.SenderID': {
'DataType': 'String',
'StringValue': getattr(
settings, 'AWS_SNS_SENDER_ID', 'django-sms'
),
},
'AWS.SNS.SMS.SMSType': {
'DataType': 'String',
'StringValue': getattr(
settings, 'AWS_SNS_SMS_TYPE', 'Promotional'
),
},
'AWS.MM.SMS.OriginationNumber': {
'DataType': 'String',
'StringValue': message.originator,
},
},
)
except (ClientError, AssertionError):
if not self.fail_silently:
raise
msg_count += 1
return msg_count
47 changes: 47 additions & 0 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,53 @@ def test_send_messages(self) -> None:
)


class AwsBackendTests(BaseSmsBackendTests, SimpleTestCase):
sms_backend = 'sms.backends.aws.SmsBackend'

def setUp(self) -> None:
super().setUp()
self._settings_override = override_settings(
AWS_SNS_REGION='us-moon-3',
AWS_SNS_ACCESS_KEY_ID='AKIAFAKEACCESSKEYID',
AWS_SNS_SECRET_ACCESS_KEY='fake_secret_access_key',
)
self._settings_override.enable()

def tearDown(self) -> None:
self._settings_override.disable()
super().tearDown()

def test_send_messages(self) -> None:
"""Test send_messages with the Twilio backend."""
message = Message(
'Here is the message',
'+12065550100',
['+441134960000']
)

connection = sms.get_connection()
connection.sns_resource.meta.client.publish = MagicMock() # type: ignore
connection.send_messages([message]) # type: ignore
connection.sns_resource.meta.client.publish.assert_called_with( # type: ignore
PhoneNumber='+441134960000',
Message='Here is the message',
MessageAttributes={
"AWS.SNS.SMS.SenderID": {
"DataType": "String",
"StringValue": "django-sms",
},
"AWS.SNS.SMS.SMSType": {
"DataType": "String",
"StringValue": "Promotional",
},
"AWS.MM.SMS.OriginationNumber": {
"DataType": "String",
"StringValue": '+12065550100',
},
},
)


class SignalTests(SimpleTestCase):

def flush_mailbox(self) -> None:
Expand Down
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ setenv=
deps=
messagebird
twilio
boto3
coverage
mypy
django22: Django==2.2.*
Expand Down