diff --git a/README.md b/README.md index 5123cb4..7982e87 100644 --- a/README.md +++ b/README.md @@ -285,6 +285,36 @@ response = client.send_push(request) print(response) ``` +## WhatsApp + +SendWhatsAppRequest requires: +* `transactional_message_id`: the ID of the transactional WhatsApp message you want to send. +* an `identifiers` object containing the `id` or `email` of your recipient. If the profile does not exist, Customer.io will create it. + +`to` and `_from` are WhatsApp numbers in E.164 format. `_from` (the object field for the `from` payload key, which is a reserved keyword in Python) is optional when the referenced `transactional_message_id` already defines it. + +Use `send_whatsapp` referencing your request to send a transactional message. [Learn more about transactional messages and `SendWhatsAppRequest` properties](https://customer.io/docs/journeys/transactional-api). + +```python +from customerio import APIClient, Regions, SendWhatsAppRequest +client = APIClient("your API key", region=Regions.US) + +request = SendWhatsAppRequest( + transactional_message_id="3", + to="+15551234567", + _from="+15559876543", + message_data={ + "name": "person", + }, + identifiers={ + "id": "2", + } +) + +response = client.send_whatsapp(request) +print(response) +``` + ## Notes - The Customer.io Python SDK depends on the [`Requests`](https://pypi.org/project/requests/) library which includes [`urllib3`](https://pypi.org/project/urllib3/) as a transitive dependency. The [`Requests`](https://pypi.org/project/requests/) library leverages connection pooling defined in [`urllib3`](https://pypi.org/project/urllib3/). [`urllib3`](https://pypi.org/project/urllib3/) only attempts to retry invocations of `HTTP` methods which are understood to be idempotent (See: [`Retry.DEFAULT_ALLOWED_METHODS`](https://github.com/urllib3/urllib3/blob/main/src/urllib3/util/retry.py#L184)). Since the `POST` method is not considered to be idempotent, any invocations which require `POST` are not retried. diff --git a/customerio/__init__.py b/customerio/__init__.py index 92eb5c3..2955934 100644 --- a/customerio/__init__.py +++ b/customerio/__init__.py @@ -5,6 +5,7 @@ SendInboxMessageRequest, SendPushRequest, SendSMSRequest, + SendWhatsAppRequest, ) from customerio.client_base import CustomerIOException from customerio.regions import Regions @@ -20,4 +21,5 @@ "SendInboxMessageRequest", "SendPushRequest", "SendSMSRequest", + "SendWhatsAppRequest", ] diff --git a/customerio/api.py b/customerio/api.py index 5eb637c..ff6112c 100644 --- a/customerio/api.py +++ b/customerio/api.py @@ -63,6 +63,14 @@ def _payload_from_fields(source, field_map): "send_to_unsubscribed": "send_to_unsubscribed", } +WHATSAPP_FIELD_MAP = COMMON_MESSAGE_FIELD_MAP | { + # from is a reserved keyword, so the object field is `_from`. + "_from": "from", + "to": "to", + "send_to_unsubscribed": "send_to_unsubscribed", + "tracked": "tracked", +} + INBOX_FIELD_MAP = COMMON_MESSAGE_FIELD_MAP IN_APP_FIELD_MAP = COMMON_MESSAGE_FIELD_MAP @@ -108,6 +116,12 @@ def send_sms(self, request): resp = self.send_request("POST", self.url + "/v1/send/sms", request) return resp.json() + def send_whatsapp(self, request): + if isinstance(request, SendWhatsAppRequest): + request = request._to_dict() + resp = self.send_request("POST", self.url + "/v1/send/whatsapp", request) + return resp.json() + def send_inbox_message(self, request): if isinstance(request, SendInboxMessageRequest): request = request._to_dict() @@ -276,6 +290,40 @@ def _to_dict(self): return _payload_from_fields(self, SMS_FIELD_MAP) +class SendWhatsAppRequest: + """An object with all the options available for triggering a transactional WhatsApp message.""" + + def __init__( + self, + transactional_message_id=None, + to=None, + identifiers=None, + _from=None, + tracked=None, + disable_message_retention=None, + send_to_unsubscribed=None, + queue_draft=None, + message_data=None, + send_at=None, + language=None, + ): + self.transactional_message_id = transactional_message_id + self.to = to + self.identifiers = identifiers + self._from = _from + self.tracked = tracked + self.disable_message_retention = disable_message_retention + self.send_to_unsubscribed = send_to_unsubscribed + self.queue_draft = queue_draft + self.message_data = message_data + self.send_at = send_at + self.language = language + + def _to_dict(self): + """Build a request payload from the object.""" + return _payload_from_fields(self, WHATSAPP_FIELD_MAP) + + class SendInboxMessageRequest: """An object with all the options available for triggering a transactional inbox message.""" diff --git a/tests/test_api.py b/tests/test_api.py index d994f25..3ad34ef 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -15,6 +15,7 @@ SendInboxMessageRequest, SendPushRequest, SendSMSRequest, + SendWhatsAppRequest, ) from tests.server import HTTPSTestCase @@ -147,6 +148,36 @@ def test_send_sms(self): self.client.send_sms(sms) + def test_send_whatsapp(self): + self.client.http.hooks = dict( + response=partial( + self._check_request, + rq={ + "method": "POST", + "authorization": "Bearer app_api_key", + "content_type": "application/json", + "url_suffix": "/v1/send/whatsapp", + "body": { + "identifiers": {"id": "customer_1"}, + "transactional_message_id": 100, + "to": "+15551234567", + "from": "+15559876543", + "tracked": True, + }, + }, + ) + ) + + whatsapp = SendWhatsAppRequest( + identifiers={"id": "customer_1"}, + transactional_message_id=100, + to="+15551234567", + _from="+15559876543", + tracked=True, + ) + + self.client.send_whatsapp(whatsapp) + def test_send_inbox_message(self): self.client.http.hooks = dict( response=partial(