diff --git a/README.md b/README.md index 5123cb4..9c2bc80 100644 --- a/README.md +++ b/README.md @@ -25,10 +25,11 @@ pip install customerio ```python from customerio import CustomerIO, Regions + cio = CustomerIO(site_id, api_key, region=Regions.US) -cio.identify(id="5", email='customer@example.com', name='Bob', plan='premium') -cio.track(customer_id="5", name='purchased') -cio.track(customer_id="5", name='purchased', data={"price": 23.45}) +cio.identify(id="5", email="customer@example.com", name="Bob", plan="premium") +cio.track(customer_id="5", name="purchased") +cio.track(customer_id="5", name="purchased", data={"price": 23.45}) ``` ### Instantiating customer.io object @@ -37,6 +38,7 @@ Create an instance of the client with your [Customer.io credentials](https://fly ```python from customerio import CustomerIO, Regions + cio = CustomerIO(site_id, api_key, region=Regions.US) ``` `region` is optional and takes one of two values—`Regions.US` or `Regions.EU`. If you do not specify your region, we assume that your account is based in the US (`Regions.US`). If your account is based in the EU and you do not provide the correct region (`Regions.EU`), we'll route requests to our EU data centers accordingly, however this may cause data to be logged in the US. @@ -44,7 +46,7 @@ cio = CustomerIO(site_id, api_key, region=Regions.US) ### Create or update a Customer.io customer profile ```python -cio.identify(id="5", email='customer@example.com', name='Bob', plan='premium') +cio.identify(id="5", email="customer@example.com", name="Bob", plan="premium") ``` Only the id field is used to identify the customer here. Using an existing id with @@ -58,13 +60,13 @@ See original REST documentation [here](http://customer.io/docs/api/track/#operat ### Track a custom event ```python -cio.track(customer_id="5", name='purchased') +cio.track(customer_id="5", name="purchased") ``` ### Track a custom event with custom data values ```python -cio.track(customer_id="5", name='purchased', data={"price": 23.45, "product": "widget"}) +cio.track(customer_id="5", name="purchased", data={"price": 23.45, "product": "widget"}) ``` Pass custom event attributes to `track` in the `data` dict. @@ -75,11 +77,11 @@ See original REST documentation [here](http://customer.io/docs/api/track/#operat ```python cio.track( - customer_id="5", - name='purchased', - data={"price": 23.45, "product": "widget"}, - id="01HB4HBDKTFWYZCK01DMRSWRFD", - timestamp=1561231234 + customer_id="5", + name="purchased", + data={"price": 23.45, "product": "widget"}, + id="01HB4HBDKTFWYZCK01DMRSWRFD", + timestamp=1561231234, ) ``` @@ -114,9 +116,7 @@ See original REST documentation [here](http://customer.io/docs/api/track/#operat ```python cio.track_anonymous( - anonymous_id="anon-event", - name="purchased", - data={"price": 23.45, "product": "widget"} + anonymous_id="anon-event", name="purchased", data={"price": 23.45, "product": "widget"} ) ``` @@ -130,9 +130,9 @@ If you previously sent [invite events](https://customer.io/docs/journeys/anonymo ```python cio.track_anonymous( - anonymous_id=None, - name="invite", - data={"first_name": "alex", "recipient": "alex.person@example.com"} + anonymous_id=None, + name="invite", + data={"first_name": "alex", "recipient": "alex.person@example.com"}, ) ``` @@ -158,16 +158,17 @@ For each person, you'll set the type of identifier you want to use to identify a ```python ## Please import identifier types -cio.merge_customers(primary_id_type=ID, - primary_id="cool.person@company.com", - secondary_id_type=EMAIL, - secondary_id="cperson@gmail.com" +cio.merge_customers( + primary_id_type=ID, + primary_id="cool.person@company.com", + secondary_id_type=EMAIL, + secondary_id="cperson@gmail.com", ) ``` ### Add a device ```python -cio.add_device(customer_id="1", device_id='device_hash', platform='ios') +cio.add_device(customer_id="1", device_id="device_hash", platform="ios") ``` Adds the device `device_hash` with the platform `ios` for a specified customer. @@ -184,7 +185,7 @@ This method returns nothing. ### Delete a device ```python -cio.delete_device(customer_id="1", device_id='device_hash') +cio.delete_device(customer_id="1", device_id="device_hash") ``` Deletes the specified device for a specified customer. @@ -226,28 +227,29 @@ Use `send_email` referencing your request to send a transactional message. [Lear ```python from customerio import APIClient, Regions, SendEmailRequest + client = APIClient("your API key", region=Regions.US) request = SendEmailRequest( - to="person@example.com", - _from="override.sender@example.com", - transactional_message_id="3", - message_data={ - "name": "person", - "items": [ - { - "name": "shoes", - "price": "59.99", - }, - ] - }, - identifiers={ - "email": "person@example.com", - } + to="person@example.com", + _from="override.sender@example.com", + transactional_message_id="3", + message_data={ + "name": "person", + "items": [ + { + "name": "shoes", + "price": "59.99", + }, + ], + }, + identifiers={ + "email": "person@example.com", + }, ) with open("receipt.pdf", "rb") as f: - request.attach('receipt.pdf', f.read()) + request.attach("receipt.pdf", f.read()) response = client.send_email(request) print(response) @@ -263,28 +265,59 @@ Use `send_push` referencing your request to send a transactional message. [Learn ```python from customerio import APIClient, Regions, SendPushRequest + client = APIClient("your API key", region=Regions.US) request = SendPushRequest( - transactional_message_id="3", - message_data={ - "name": "person", - "items": [ - { - "name": "shoes", - "price": "59.99", - }, - ] - }, - identifiers={ - "id": "2", - } + transactional_message_id="3", + message_data={ + "name": "person", + "items": [ + { + "name": "shoes", + "price": "59.99", + }, + ], + }, + identifiers={ + "id": "2", + }, ) 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` is the WhatsApp number in E.164 format. + +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", + 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. @@ -293,6 +326,7 @@ print(response) ### Usage Example Disabling Connection Pooling ```python from customerio import CustomerIO, Regions + cio = CustomerIO(site_id, api_key, region=Regions.US, use_connection_pooling=False) ``` 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..eab6d24 100644 --- a/customerio/api.py +++ b/customerio/api.py @@ -63,6 +63,12 @@ def _payload_from_fields(source, field_map): "send_to_unsubscribed": "send_to_unsubscribed", } +WHATSAPP_FIELD_MAP = COMMON_MESSAGE_FIELD_MAP | { + "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 +114,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 +288,38 @@ 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, + 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.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..6f9119f 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,34 @@ 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", + "tracked": True, + }, + }, + ) + ) + + whatsapp = SendWhatsAppRequest( + identifiers={"id": "customer_1"}, + transactional_message_id=100, + to="+15551234567", + tracked=True, + ) + + self.client.send_whatsapp(whatsapp) + def test_send_inbox_message(self): self.client.http.hooks = dict( response=partial(