ccai-python 1.1.0__tar.gz → 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {ccai_python-1.1.0 → ccai_python-1.2.0}/PKG-INFO +38 -15
  2. {ccai_python-1.1.0 → ccai_python-1.2.0}/README.md +37 -14
  3. {ccai_python-1.1.0 → ccai_python-1.2.0}/pyproject.toml +1 -1
  4. {ccai_python-1.1.0 → ccai_python-1.2.0}/setup.py +1 -1
  5. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python/sms/sms.py +32 -3
  6. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python.egg-info/PKG-INFO +38 -15
  7. {ccai_python-1.1.0 → ccai_python-1.2.0}/tests/test_sms.py +42 -0
  8. {ccai_python-1.1.0 → ccai_python-1.2.0}/setup.cfg +0 -0
  9. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python/__init__.py +0 -0
  10. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python/brand_service.py +0 -0
  11. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python/campaign_service.py +0 -0
  12. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python/ccai.py +0 -0
  13. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python/contact_service.py +0 -0
  14. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python/contact_test.py +0 -0
  15. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python/contact_validator_service.py +0 -0
  16. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python/email_send.py +0 -0
  17. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python/email_service.py +0 -0
  18. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python/mms_send.py +0 -0
  19. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python/sms/__init__.py +0 -0
  20. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python/sms/mms.py +0 -0
  21. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python/sms_send.py +0 -0
  22. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python/webhook.py +0 -0
  23. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python/webhook_handler_example.py +0 -0
  24. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python/webhook_test.py +0 -0
  25. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python.egg-info/SOURCES.txt +0 -0
  26. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python.egg-info/dependency_links.txt +0 -0
  27. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python.egg-info/requires.txt +0 -0
  28. {ccai_python-1.1.0 → ccai_python-1.2.0}/src/ccai_python.egg-info/top_level.txt +0 -0
  29. {ccai_python-1.1.0 → ccai_python-1.2.0}/tests/test_ccai.py +0 -0
  30. {ccai_python-1.1.0 → ccai_python-1.2.0}/tests/test_contact.py +0 -0
  31. {ccai_python-1.1.0 → ccai_python-1.2.0}/tests/test_email.py +0 -0
  32. {ccai_python-1.1.0 → ccai_python-1.2.0}/tests/test_mms.py +0 -0
  33. {ccai_python-1.1.0 → ccai_python-1.2.0}/tests/test_webhook.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ccai-python
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: Python client for CloudContactAI API
5
5
  Author: CloudContactAI LLC
6
6
  License: MIT
@@ -21,7 +21,7 @@ Dynamic: requires-python
21
21
 
22
22
  # CCAI Python Client
23
23
 
24
- Version: 1.1.0
24
+ Version: 1.2.0
25
25
 
26
26
  A Python client for interacting with the CloudContactAI API.
27
27
 
@@ -70,6 +70,30 @@ campaign_response = ccai.sms.send(
70
70
  print(f"Campaign sent with ID: {campaign_response.campaign_id}")
71
71
  ```
72
72
 
73
+ ### SMS — Template-Controlled Accounts
74
+
75
+ If an account has been configured to enforce template-only messaging, all campaigns must reference a pre-approved template ID. Sending a free-text message to such an account will result in a `422` error.
76
+
77
+ ```python
78
+ # Send to multiple recipients using a template
79
+ response = ccai.sms.send_with_template(
80
+ accounts=accounts,
81
+ template_id=12345, # the ID of the approved template
82
+ title="My Campaign"
83
+ )
84
+
85
+ # Send to a single recipient using a template
86
+ response = ccai.sms.send_single_with_template(
87
+ first_name="John",
88
+ last_name="Doe",
89
+ phone="+15551234567",
90
+ template_id=12345,
91
+ title="My Campaign"
92
+ )
93
+ ```
94
+
95
+ The message body is resolved server-side from the template. Variable substitution (e.g. `${firstName}`) is applied automatically using the recipient's account data.
96
+
73
97
  ### MMS
74
98
 
75
99
  ```python
@@ -88,7 +112,6 @@ def track_progress(status):
88
112
  # Create options with progress tracking
89
113
  options = SMSOptions(
90
114
  timeout=60,
91
- retries=3,
92
115
  on_progress=track_progress
93
116
  )
94
117
 
@@ -316,22 +339,18 @@ def verify_and_handle_webhook(signature, client_id, event_hash, secret):
316
339
  print("Invalid signature")
317
340
 
318
341
  # Create a webhook handler for web frameworks
319
- def handle_message_sent(event):
320
- print(f"Message sent: {event.message} to {event.to}")
321
-
322
- def handle_message_received(event):
323
- print(f"Message received: {event.message} from {event.from_}")
342
+ def handle_event(event):
343
+ if event.event_type == WebhookEventType.MESSAGE_SENT:
344
+ print(f"Message sent: {event.data.get('Message')} to {event.data.get('To')}")
345
+ elif event.event_type == WebhookEventType.MESSAGE_RECEIVED:
346
+ print(f"Message received: {event.data.get('Message')} from {event.data.get('From')}")
324
347
 
325
- handlers = {
326
- 'on_message_sent': handle_message_sent,
327
- 'on_message_received': handle_message_received
328
- }
329
-
330
- webhook_handler = ccai.webhook.create_handler(handlers)
348
+ webhook_handler = ccai.webhook.create_handler({'on_event': handle_event})
331
349
 
332
350
  # Use with Flask
333
- from flask import Flask, request, jsonify
351
+ import os
334
352
  import json
353
+ from flask import Flask, request, jsonify
335
354
 
336
355
  app = Flask(__name__)
337
356
 
@@ -435,6 +454,8 @@ campaign = ccai.campaigns.create({
435
454
  "subUseCases": ["CUSTOMER_CARE", "TWO_FACTOR_AUTHENTICATION", "ACCOUNT_NOTIFICATION"],
436
455
  "description": "Security codes and support messaging.",
437
456
  "messageFlow": "Users opt-in via signup form at https://example.com/signup",
457
+ "termsLink": "https://example.com/terms",
458
+ "privacyLink": "https://example.com/privacy",
438
459
  "hasEmbeddedLinks": True,
439
460
  "hasEmbeddedPhone": False,
440
461
  "isAgeGated": False,
@@ -475,6 +496,8 @@ ccai.campaigns.delete(campaign["id"])
475
496
 
476
497
  > Note: `MIXED` and `LOW_VOLUME_MIXED` campaigns require 2–3 `subUseCases`.
477
498
 
499
+ > `termsLink` and `privacyLink` are optional fields on the campaign dict.
500
+
478
501
  #### Sub-Use Cases
479
502
 
480
503
  `TWO_FACTOR_AUTHENTICATION`, `ACCOUNT_NOTIFICATION`, `CUSTOMER_CARE`, `DELIVERY_NOTIFICATION`, `FRAUD_ALERT`, `MARKETING`, `POLLING_VOTING`
@@ -1,6 +1,6 @@
1
1
  # CCAI Python Client
2
2
 
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
 
5
5
  A Python client for interacting with the CloudContactAI API.
6
6
 
@@ -49,6 +49,30 @@ campaign_response = ccai.sms.send(
49
49
  print(f"Campaign sent with ID: {campaign_response.campaign_id}")
50
50
  ```
51
51
 
52
+ ### SMS — Template-Controlled Accounts
53
+
54
+ If an account has been configured to enforce template-only messaging, all campaigns must reference a pre-approved template ID. Sending a free-text message to such an account will result in a `422` error.
55
+
56
+ ```python
57
+ # Send to multiple recipients using a template
58
+ response = ccai.sms.send_with_template(
59
+ accounts=accounts,
60
+ template_id=12345, # the ID of the approved template
61
+ title="My Campaign"
62
+ )
63
+
64
+ # Send to a single recipient using a template
65
+ response = ccai.sms.send_single_with_template(
66
+ first_name="John",
67
+ last_name="Doe",
68
+ phone="+15551234567",
69
+ template_id=12345,
70
+ title="My Campaign"
71
+ )
72
+ ```
73
+
74
+ The message body is resolved server-side from the template. Variable substitution (e.g. `${firstName}`) is applied automatically using the recipient's account data.
75
+
52
76
  ### MMS
53
77
 
54
78
  ```python
@@ -67,7 +91,6 @@ def track_progress(status):
67
91
  # Create options with progress tracking
68
92
  options = SMSOptions(
69
93
  timeout=60,
70
- retries=3,
71
94
  on_progress=track_progress
72
95
  )
73
96
 
@@ -295,22 +318,18 @@ def verify_and_handle_webhook(signature, client_id, event_hash, secret):
295
318
  print("Invalid signature")
296
319
 
297
320
  # Create a webhook handler for web frameworks
298
- def handle_message_sent(event):
299
- print(f"Message sent: {event.message} to {event.to}")
300
-
301
- def handle_message_received(event):
302
- print(f"Message received: {event.message} from {event.from_}")
321
+ def handle_event(event):
322
+ if event.event_type == WebhookEventType.MESSAGE_SENT:
323
+ print(f"Message sent: {event.data.get('Message')} to {event.data.get('To')}")
324
+ elif event.event_type == WebhookEventType.MESSAGE_RECEIVED:
325
+ print(f"Message received: {event.data.get('Message')} from {event.data.get('From')}")
303
326
 
304
- handlers = {
305
- 'on_message_sent': handle_message_sent,
306
- 'on_message_received': handle_message_received
307
- }
308
-
309
- webhook_handler = ccai.webhook.create_handler(handlers)
327
+ webhook_handler = ccai.webhook.create_handler({'on_event': handle_event})
310
328
 
311
329
  # Use with Flask
312
- from flask import Flask, request, jsonify
330
+ import os
313
331
  import json
332
+ from flask import Flask, request, jsonify
314
333
 
315
334
  app = Flask(__name__)
316
335
 
@@ -414,6 +433,8 @@ campaign = ccai.campaigns.create({
414
433
  "subUseCases": ["CUSTOMER_CARE", "TWO_FACTOR_AUTHENTICATION", "ACCOUNT_NOTIFICATION"],
415
434
  "description": "Security codes and support messaging.",
416
435
  "messageFlow": "Users opt-in via signup form at https://example.com/signup",
436
+ "termsLink": "https://example.com/terms",
437
+ "privacyLink": "https://example.com/privacy",
417
438
  "hasEmbeddedLinks": True,
418
439
  "hasEmbeddedPhone": False,
419
440
  "isAgeGated": False,
@@ -454,6 +475,8 @@ ccai.campaigns.delete(campaign["id"])
454
475
 
455
476
  > Note: `MIXED` and `LOW_VOLUME_MIXED` campaigns require 2–3 `subUseCases`.
456
477
 
478
+ > `termsLink` and `privacyLink` are optional fields on the campaign dict.
479
+
457
480
  #### Sub-Use Cases
458
481
 
459
482
  `TWO_FACTOR_AUTHENTICATION`, `ACCOUNT_NOTIFICATION`, `CUSTOMER_CARE`, `DELIVERY_NOTIFICATION`, `FRAUD_ALERT`, `MARKETING`, `POLLING_VOTING`
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ccai-python"
7
- version = "1.1.0"
7
+ version = "1.2.0"
8
8
  description = "Python client for CloudContactAI API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='ccai',
5
- version='1.1.0',
5
+ version='1.2.0',
6
6
  description='A CLI and Python client for sending SMS via CloudContactAI',
7
7
  author='CloudContactAI LLC',
8
8
  license='MIT',
@@ -30,8 +30,9 @@ class Account(BaseModel):
30
30
  class SMSCampaign(BaseModel):
31
31
  """SMS campaign data model"""
32
32
  accounts: List[Account] = Field(..., description="List of recipient accounts")
33
- message: str = Field(..., description="Message content with optional variables")
33
+ message: str = Field(default="", description="Message content with optional variables")
34
34
  title: str = Field(..., description="Campaign title")
35
+ template_id: Optional[int] = Field(default=None, alias="templateId", description="Optional template ID for template-controlled accounts")
35
36
 
36
37
 
37
38
  class SMSResponse(BaseModel):
@@ -99,11 +100,12 @@ class SMS:
99
100
  message: str,
100
101
  title: str,
101
102
  sender_phone: Optional[str] = None,
102
- options: Optional[SMSOptions] = None
103
+ options: Optional[SMSOptions] = None,
104
+ template_id: Optional[int] = None
103
105
  ) -> SMSResponse:
104
106
  if not accounts:
105
107
  raise ValueError("At least one account is required")
106
- if not message:
108
+ if not message and template_id is None:
107
109
  raise ValueError("Message is required")
108
110
  if not title:
109
111
  raise ValueError("Campaign title is required")
@@ -151,6 +153,8 @@ class SMS:
151
153
  }
152
154
  if sender_phone:
153
155
  payload["senderPhone"] = sender_phone
156
+ if template_id is not None:
157
+ payload["templateId"] = template_id
154
158
 
155
159
  try:
156
160
  if options and options.on_progress:
@@ -193,3 +197,28 @@ class SMS:
193
197
  )
194
198
  return self.send([account], message, title, sender_phone, options)
195
199
 
200
+ def send_with_template(
201
+ self,
202
+ accounts: List[Union[Account, Dict[str, str]]],
203
+ template_id: int,
204
+ title: str,
205
+ sender_phone: Optional[str] = None,
206
+ options: Optional[SMSOptions] = None
207
+ ) -> SMSResponse:
208
+ """Send SMS using a pre-approved template (for template-controlled accounts)."""
209
+ return self.send(accounts, "", title, sender_phone, options, template_id)
210
+
211
+ def send_single_with_template(
212
+ self,
213
+ first_name: str,
214
+ last_name: str,
215
+ phone: str,
216
+ template_id: int,
217
+ title: str,
218
+ sender_phone: Optional[str] = None,
219
+ options: Optional[SMSOptions] = None
220
+ ) -> SMSResponse:
221
+ """Send SMS to a single recipient using a pre-approved template."""
222
+ account = Account(first_name=first_name, last_name=last_name, phone=phone)
223
+ return self.send_with_template([account], template_id, title, sender_phone, options)
224
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ccai-python
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: Python client for CloudContactAI API
5
5
  Author: CloudContactAI LLC
6
6
  License: MIT
@@ -21,7 +21,7 @@ Dynamic: requires-python
21
21
 
22
22
  # CCAI Python Client
23
23
 
24
- Version: 1.1.0
24
+ Version: 1.2.0
25
25
 
26
26
  A Python client for interacting with the CloudContactAI API.
27
27
 
@@ -70,6 +70,30 @@ campaign_response = ccai.sms.send(
70
70
  print(f"Campaign sent with ID: {campaign_response.campaign_id}")
71
71
  ```
72
72
 
73
+ ### SMS — Template-Controlled Accounts
74
+
75
+ If an account has been configured to enforce template-only messaging, all campaigns must reference a pre-approved template ID. Sending a free-text message to such an account will result in a `422` error.
76
+
77
+ ```python
78
+ # Send to multiple recipients using a template
79
+ response = ccai.sms.send_with_template(
80
+ accounts=accounts,
81
+ template_id=12345, # the ID of the approved template
82
+ title="My Campaign"
83
+ )
84
+
85
+ # Send to a single recipient using a template
86
+ response = ccai.sms.send_single_with_template(
87
+ first_name="John",
88
+ last_name="Doe",
89
+ phone="+15551234567",
90
+ template_id=12345,
91
+ title="My Campaign"
92
+ )
93
+ ```
94
+
95
+ The message body is resolved server-side from the template. Variable substitution (e.g. `${firstName}`) is applied automatically using the recipient's account data.
96
+
73
97
  ### MMS
74
98
 
75
99
  ```python
@@ -88,7 +112,6 @@ def track_progress(status):
88
112
  # Create options with progress tracking
89
113
  options = SMSOptions(
90
114
  timeout=60,
91
- retries=3,
92
115
  on_progress=track_progress
93
116
  )
94
117
 
@@ -316,22 +339,18 @@ def verify_and_handle_webhook(signature, client_id, event_hash, secret):
316
339
  print("Invalid signature")
317
340
 
318
341
  # Create a webhook handler for web frameworks
319
- def handle_message_sent(event):
320
- print(f"Message sent: {event.message} to {event.to}")
321
-
322
- def handle_message_received(event):
323
- print(f"Message received: {event.message} from {event.from_}")
342
+ def handle_event(event):
343
+ if event.event_type == WebhookEventType.MESSAGE_SENT:
344
+ print(f"Message sent: {event.data.get('Message')} to {event.data.get('To')}")
345
+ elif event.event_type == WebhookEventType.MESSAGE_RECEIVED:
346
+ print(f"Message received: {event.data.get('Message')} from {event.data.get('From')}")
324
347
 
325
- handlers = {
326
- 'on_message_sent': handle_message_sent,
327
- 'on_message_received': handle_message_received
328
- }
329
-
330
- webhook_handler = ccai.webhook.create_handler(handlers)
348
+ webhook_handler = ccai.webhook.create_handler({'on_event': handle_event})
331
349
 
332
350
  # Use with Flask
333
- from flask import Flask, request, jsonify
351
+ import os
334
352
  import json
353
+ from flask import Flask, request, jsonify
335
354
 
336
355
  app = Flask(__name__)
337
356
 
@@ -435,6 +454,8 @@ campaign = ccai.campaigns.create({
435
454
  "subUseCases": ["CUSTOMER_CARE", "TWO_FACTOR_AUTHENTICATION", "ACCOUNT_NOTIFICATION"],
436
455
  "description": "Security codes and support messaging.",
437
456
  "messageFlow": "Users opt-in via signup form at https://example.com/signup",
457
+ "termsLink": "https://example.com/terms",
458
+ "privacyLink": "https://example.com/privacy",
438
459
  "hasEmbeddedLinks": True,
439
460
  "hasEmbeddedPhone": False,
440
461
  "isAgeGated": False,
@@ -475,6 +496,8 @@ ccai.campaigns.delete(campaign["id"])
475
496
 
476
497
  > Note: `MIXED` and `LOW_VOLUME_MIXED` campaigns require 2–3 `subUseCases`.
477
498
 
499
+ > `termsLink` and `privacyLink` are optional fields on the campaign dict.
500
+
478
501
  #### Sub-Use Cases
479
502
 
480
503
  `TWO_FACTOR_AUTHENTICATION`, `ACCOUNT_NOTIFICATION`, `CUSTOMER_CARE`, `DELIVERY_NOTIFICATION`, `FRAUD_ALERT`, `MARKETING`, `POLLING_VOTING`
@@ -289,5 +289,47 @@ class TestSMS(unittest.TestCase):
289
289
  )
290
290
 
291
291
 
292
+ @patch.object(CCAI, 'request')
293
+ def test_send_with_template_id(self, mock_request):
294
+ """Test sending SMS with a template ID"""
295
+ mock_request.return_value = {
296
+ "id": "msg-tpl-1",
297
+ "status": "sent",
298
+ "campaign_id": "camp-tpl-1"
299
+ }
300
+
301
+ response = self.ccai.sms.send_with_template(
302
+ accounts=[self.account],
303
+ template_id=12345,
304
+ title="Template Campaign"
305
+ )
306
+
307
+ self.assertEqual(response.id, "msg-tpl-1")
308
+
309
+ call_args = mock_request.call_args
310
+ payload = call_args.kwargs.get("data") or call_args[1].get("data")
311
+ self.assertEqual(payload["templateId"], 12345)
312
+ self.assertEqual(payload["message"], "")
313
+
314
+ @patch.object(CCAI, 'request')
315
+ def test_send_single_with_template_id(self, mock_request):
316
+ """Test sending SMS to a single recipient with a template ID"""
317
+ mock_request.return_value = {"id": "msg-tpl-2", "status": "sent"}
318
+
319
+ response = self.ccai.sms.send_single_with_template(
320
+ first_name="Jane",
321
+ last_name="Smith",
322
+ phone="+14156961732",
323
+ template_id=99,
324
+ title="Single Template Campaign"
325
+ )
326
+
327
+ self.assertEqual(response.id, "msg-tpl-2")
328
+
329
+ call_args = mock_request.call_args
330
+ payload = call_args.kwargs.get("data") or call_args[1].get("data")
331
+ self.assertEqual(payload["templateId"], 99)
332
+
333
+
292
334
  if __name__ == '__main__':
293
335
  unittest.main()
File without changes