ccai-python 1.0.1__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 (40) hide show
  1. ccai_python-1.2.0/PKG-INFO +530 -0
  2. ccai_python-1.2.0/README.md +509 -0
  3. {ccai_python-1.0.1 → ccai_python-1.2.0}/pyproject.toml +1 -1
  4. {ccai_python-1.0.1 → ccai_python-1.2.0}/setup.py +2 -2
  5. {ccai_python-1.0.1 → ccai_python-1.2.0}/src/ccai_python/__init__.py +24 -5
  6. ccai_python-1.2.0/src/ccai_python/brand_service.py +91 -0
  7. ccai_python-1.2.0/src/ccai_python/campaign_service.py +125 -0
  8. {ccai_python-1.0.1 → ccai_python-1.2.0}/src/ccai_python/ccai.py +92 -25
  9. {ccai_python-1.0.1 → ccai_python-1.2.0}/src/ccai_python/contact_test.py +2 -2
  10. ccai_python-1.2.0/src/ccai_python/contact_validator_service.py +76 -0
  11. {ccai_python-1.0.1 → ccai_python-1.2.0}/src/ccai_python/email_send.py +5 -5
  12. {ccai_python-1.0.1 → ccai_python-1.2.0}/src/ccai_python/email_service.py +127 -41
  13. {ccai_python-1.0.1 → ccai_python-1.2.0}/src/ccai_python/mms_send.py +7 -6
  14. {ccai_python-1.0.1 → ccai_python-1.2.0}/src/ccai_python/sms/mms.py +28 -12
  15. {ccai_python-1.0.1 → ccai_python-1.2.0}/src/ccai_python/sms/sms.py +81 -22
  16. {ccai_python-1.0.1 → ccai_python-1.2.0}/src/ccai_python/sms_send.py +7 -6
  17. ccai_python-1.2.0/src/ccai_python/webhook.py +171 -0
  18. ccai_python-1.2.0/src/ccai_python/webhook_handler_example.py +91 -0
  19. {ccai_python-1.0.1 → ccai_python-1.2.0}/src/ccai_python/webhook_test.py +7 -6
  20. ccai_python-1.2.0/src/ccai_python.egg-info/PKG-INFO +530 -0
  21. {ccai_python-1.0.1 → ccai_python-1.2.0}/src/ccai_python.egg-info/SOURCES.txt +6 -3
  22. {ccai_python-1.0.1 → ccai_python-1.2.0}/tests/test_ccai.py +33 -10
  23. ccai_python-1.2.0/tests/test_email.py +135 -0
  24. {ccai_python-1.0.1 → ccai_python-1.2.0}/tests/test_mms.py +25 -0
  25. {ccai_python-1.0.1 → ccai_python-1.2.0}/tests/test_sms.py +100 -3
  26. ccai_python-1.2.0/tests/test_webhook.py +178 -0
  27. ccai_python-1.0.1/PKG-INFO +0 -312
  28. ccai_python-1.0.1/README.md +0 -291
  29. ccai_python-1.0.1/src/ccai_python/examples/async_example.py +0 -248
  30. ccai_python-1.0.1/src/ccai_python/examples/basic_example.py +0 -79
  31. ccai_python-1.0.1/src/ccai_python/webhook.py +0 -110
  32. ccai_python-1.0.1/src/ccai_python/webhook_handler_example.py +0 -46
  33. ccai_python-1.0.1/src/ccai_python.egg-info/PKG-INFO +0 -312
  34. {ccai_python-1.0.1 → ccai_python-1.2.0}/setup.cfg +0 -0
  35. {ccai_python-1.0.1 → ccai_python-1.2.0}/src/ccai_python/contact_service.py +0 -0
  36. {ccai_python-1.0.1 → ccai_python-1.2.0}/src/ccai_python/sms/__init__.py +0 -0
  37. {ccai_python-1.0.1 → ccai_python-1.2.0}/src/ccai_python.egg-info/dependency_links.txt +0 -0
  38. {ccai_python-1.0.1 → ccai_python-1.2.0}/src/ccai_python.egg-info/requires.txt +0 -0
  39. {ccai_python-1.0.1 → ccai_python-1.2.0}/src/ccai_python.egg-info/top_level.txt +0 -0
  40. {ccai_python-1.0.1 → ccai_python-1.2.0}/tests/test_contact.py +0 -0
@@ -0,0 +1,530 @@
1
+ Metadata-Version: 2.4
2
+ Name: ccai-python
3
+ Version: 1.2.0
4
+ Summary: Python client for CloudContactAI API
5
+ Author: CloudContactAI LLC
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/cloudcontactai/ccai-python
8
+ Project-URL: Bug Tracker, https://github.com/cloudcontactai/ccai-python/issues
9
+ Keywords: sms,api,client,cloud,contact,ai,cloudcontactai
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: requests>=2.31.0
19
+ Requires-Dist: pydantic>=2.5.0
20
+ Dynamic: requires-python
21
+
22
+ # CCAI Python Client
23
+
24
+ Version: 1.2.0
25
+
26
+ A Python client for interacting with the CloudContactAI API.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install ccai-python
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ ### SMS
37
+
38
+ ```python
39
+ from ccai_python import CCAI
40
+
41
+ # Initialize the client
42
+ ccai = CCAI(
43
+ client_id="YOUR-CLIENT-ID",
44
+ api_key="YOUR-API-KEY"
45
+ )
46
+
47
+ # Send a single SMS
48
+ response = ccai.sms.send_single(
49
+ first_name="John",
50
+ last_name="Doe",
51
+ phone="+15551234567",
52
+ message="Hello ${first_name}, this is a test message!",
53
+ title="Test Campaign"
54
+ )
55
+
56
+ print(f"Message sent with ID: {response.id}")
57
+
58
+ # Send to multiple recipients
59
+ accounts = [
60
+ {"first_name": "John", "last_name": "Doe", "phone": "+15551234567"},
61
+ {"first_name": "Jane", "last_name": "Smith", "phone": "+15559876543"}
62
+ ]
63
+
64
+ campaign_response = ccai.sms.send(
65
+ accounts=accounts,
66
+ message="Hello ${first_name} ${last_name}, this is a test message!",
67
+ title="Bulk Test Campaign"
68
+ )
69
+
70
+ print(f"Campaign sent with ID: {campaign_response.campaign_id}")
71
+ ```
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
+
97
+ ### MMS
98
+
99
+ ```python
100
+ from ccai_python import CCAI, Account, SMSOptions
101
+
102
+ # Initialize the client
103
+ ccai = CCAI(
104
+ client_id="YOUR-CLIENT-ID",
105
+ api_key="YOUR-API-KEY"
106
+ )
107
+
108
+ # Define a progress callback
109
+ def track_progress(status):
110
+ print(f"Progress: {status}")
111
+
112
+ # Create options with progress tracking
113
+ options = SMSOptions(
114
+ timeout=60,
115
+ on_progress=track_progress
116
+ )
117
+
118
+ # Complete MMS workflow (get URL, upload image, send MMS)
119
+ image_path = "path/to/your/image.jpg"
120
+ content_type = "image/jpeg"
121
+
122
+ # Define recipient
123
+ account = Account(
124
+ first_name="John",
125
+ last_name="Doe",
126
+ phone="+15551234567" # Use E.164 format
127
+ )
128
+
129
+ # Send MMS with image in one step
130
+ response = ccai.mms.send_with_image(
131
+ image_path=image_path,
132
+ content_type=content_type,
133
+ accounts=[account],
134
+ message="Hello ${first_name}, check out this image!",
135
+ title="MMS Campaign Example",
136
+ options=options
137
+ )
138
+
139
+ print(f"MMS sent! Campaign ID: {response.campaign_id}")
140
+ ```
141
+
142
+ ### Email
143
+
144
+ ```python
145
+ from ccai_python import CCAI, EmailAccount, EmailCampaign
146
+ from datetime import datetime, timedelta
147
+
148
+ # Initialize the client
149
+ ccai = CCAI(
150
+ client_id="YOUR-CLIENT-ID",
151
+ api_key="YOUR-API-KEY"
152
+ )
153
+
154
+ # Send a single email
155
+ response = ccai.email.send_single(
156
+ first_name="John",
157
+ last_name="Doe",
158
+ email="john@example.com",
159
+ subject="Welcome to Our Service",
160
+ message="<p>Hello John,</p><p>Thank you for signing up!</p>",
161
+ sender_email="noreply@yourcompany.com",
162
+ reply_email="support@yourcompany.com",
163
+ sender_name="Your Company",
164
+ title="Welcome Email"
165
+ )
166
+
167
+ print(f"Email sent with ID: {response.id}")
168
+
169
+ # Send email campaign to multiple recipients
170
+ accounts = [
171
+ EmailAccount(
172
+ first_name="John",
173
+ last_name="Doe",
174
+ email="john@example.com",
175
+ phone=""
176
+ ),
177
+ EmailAccount(
178
+ first_name="Jane",
179
+ last_name="Smith",
180
+ email="jane@example.com",
181
+ phone=""
182
+ )
183
+ ]
184
+
185
+ campaign = EmailCampaign(
186
+ subject="Monthly Newsletter",
187
+ title="July 2025 Newsletter",
188
+ message="<h1>Hello ${firstName}!</h1><p>Here's our monthly update...</p>",
189
+ sender_email="newsletter@yourcompany.com",
190
+ reply_email="support@yourcompany.com",
191
+ sender_name="Your Company Newsletter",
192
+ accounts=accounts
193
+ )
194
+
195
+ response = ccai.email.send_campaign(campaign)
196
+ print(f"Email campaign sent: {response}")
197
+
198
+ # Schedule an email for future delivery
199
+ tomorrow = datetime.now() + timedelta(days=1)
200
+ tomorrow = tomorrow.replace(hour=10, minute=0, second=0, microsecond=0)
201
+
202
+ scheduled_campaign = EmailCampaign(
203
+ subject="Scheduled Email",
204
+ title="Future Email",
205
+ message="<p>This email was scheduled in advance!</p>",
206
+ sender_email="scheduled@yourcompany.com",
207
+ reply_email="support@yourcompany.com",
208
+ sender_name="Your Company",
209
+ accounts=[accounts[0]],
210
+ scheduled_timestamp=tomorrow.isoformat(),
211
+ scheduled_timezone="America/New_York"
212
+ )
213
+
214
+ response = ccai.email.send_campaign(scheduled_campaign)
215
+ print(f"Email scheduled: {response}")
216
+ ```
217
+
218
+ ### Contacts
219
+
220
+ ```python
221
+ from ccai_python import CCAI
222
+
223
+ # Initialize the client
224
+ ccai = CCAI(
225
+ client_id="YOUR-CLIENT-ID",
226
+ api_key="YOUR-API-KEY"
227
+ )
228
+
229
+ # Set do not text status using contact ID
230
+ response = ccai.contact.set_do_not_text(
231
+ do_not_text=True,
232
+ contact_id="your-contact-id"
233
+ )
234
+ print(f"Contact {response.contact_id} do not text set to {response.do_not_text}")
235
+
236
+ # Set do not text status using phone number
237
+ response = ccai.contact.set_do_not_text(
238
+ do_not_text=True,
239
+ phone="+15551234567"
240
+ )
241
+ print(f"Contact {response.contact_id} ({response.phone}) do not text set to {response.do_not_text}")
242
+
243
+ # Remove do not text status from a contact
244
+ response = ccai.contact.set_do_not_text(
245
+ do_not_text=False,
246
+ contact_id="your-contact-id"
247
+ )
248
+ print(f"Contact {response.contact_id} do not text removed: {response.do_not_text}")
249
+ ```
250
+
251
+ ### Contact Validator
252
+
253
+ Validate email addresses and phone numbers.
254
+
255
+ > Bulk endpoints accept up to 50 contacts per request and are processed server-side in chunks.
256
+
257
+ ```python
258
+ from ccai_python import CCAI
259
+
260
+ ccai = CCAI(
261
+ client_id="YOUR-CLIENT-ID",
262
+ api_key="YOUR-API-KEY"
263
+ )
264
+
265
+ # Validate a single email
266
+ email_result = ccai.contact_validator.validate_email("user@example.com")
267
+ print(email_result.status) # "valid" | "invalid" | "risky"
268
+ print(email_result.metadata.get("safe_to_send")) # True | False
269
+
270
+ # Validate multiple emails (up to 50, processed server-side in chunks)
271
+ bulk_emails = ccai.contact_validator.validate_emails(["user@example.com", "bad@invalid.xyz"])
272
+ print(bulk_emails.summary.model_dump()) # {"total": 2, "valid": 1, "invalid": 1, "risky": 0, "landline": 0}
273
+
274
+ # Validate a single phone number
275
+ phone_result = ccai.contact_validator.validate_phone("+15551234567", country_code="US")
276
+ print(phone_result.status) # "valid" | "invalid" | "landline"
277
+ print(phone_result.metadata.get("carrier_type")) # "mobile" | "landline" | "voip"
278
+
279
+ # Validate multiple phone numbers (up to 50, processed server-side in chunks)
280
+ bulk_phones = ccai.contact_validator.validate_phones([
281
+ {"phone": "+15551234567"},
282
+ {"phone": "+15559876543", "countryCode": "US"}
283
+ ])
284
+ print(bulk_phones.summary.model_dump()) # {"total": 2, "valid": 1, "invalid": 0, "risky": 0, "landline": 1}
285
+ ```
286
+
287
+ ### Webhooks
288
+
289
+ ```python
290
+ from ccai_python import CCAI, WebhookConfig, WebhookEventType
291
+
292
+ # Initialize the client
293
+ ccai = CCAI(
294
+ client_id="YOUR-CLIENT-ID",
295
+ api_key="YOUR-API-KEY"
296
+ )
297
+
298
+ # Example 1: Register a webhook with auto-generated secret
299
+ # If secret is not provided, the server will auto-generate one
300
+ config = WebhookConfig(
301
+ url="https://your-domain.com/api/ccai-webhook",
302
+ events=[WebhookEventType.MESSAGE_SENT, WebhookEventType.MESSAGE_RECEIVED]
303
+ # secret not provided - server will auto-generate and return it
304
+ )
305
+
306
+ webhook = ccai.webhook.register(config)
307
+ print(f"Webhook registered with ID: {webhook.id}")
308
+ print(f"Auto-generated Secret: {webhook.secretKey}")
309
+
310
+ # Example 2: Register a webhook with a custom secret
311
+ config_custom = WebhookConfig(
312
+ url="https://your-domain.com/api/ccai-webhook-v2",
313
+ events=[WebhookEventType.MESSAGE_SENT, WebhookEventType.MESSAGE_RECEIVED],
314
+ secret="your-custom-secret-key"
315
+ )
316
+
317
+ webhook_custom = ccai.webhook.register(config_custom)
318
+ print(f"Webhook with custom secret registered: {webhook_custom.id}")
319
+
320
+ # List all webhooks
321
+ webhooks = ccai.webhook.list()
322
+ print(f"Found {len(webhooks)} webhooks")
323
+
324
+ # Update a webhook
325
+ updated_webhook = ccai.webhook.update(webhook.id, {
326
+ "url": "https://your-domain.com/api/ccai-webhook-v3"
327
+ })
328
+ print(f"Webhook updated: {updated_webhook.url}")
329
+
330
+ # Delete a webhook
331
+ result = ccai.webhook.delete(webhook.id)
332
+ print(f"Webhook deleted: {result}")
333
+
334
+ # Verify webhook signature in your handler
335
+ def verify_and_handle_webhook(signature, client_id, event_hash, secret):
336
+ if ccai.webhook.verify_signature(signature, client_id, event_hash, secret):
337
+ print(f"Valid webhook signature verified")
338
+ else:
339
+ print("Invalid signature")
340
+
341
+ # Create a webhook handler for web frameworks
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')}")
347
+
348
+ webhook_handler = ccai.webhook.create_handler({'on_event': handle_event})
349
+
350
+ # Use with Flask
351
+ import os
352
+ import json
353
+ from flask import Flask, request, jsonify
354
+
355
+ app = Flask(__name__)
356
+
357
+ @app.route('/api/ccai-webhook', methods=['POST'])
358
+ def handle_webhook():
359
+ signature = request.headers.get('X-CCAI-Signature', '')
360
+ body = request.get_data(as_text=True)
361
+ secret = 'your-webhook-secret-key' # Use the secret from webhook registration
362
+
363
+ # Parse payload to get client_id and event_hash
364
+ payload = json.loads(body)
365
+ client_id = os.getenv('CCAI_CLIENT_ID')
366
+ event_hash = payload.get('eventHash', '')
367
+
368
+ # Verify signature
369
+ if not ccai.webhook.verify_signature(signature, client_id, event_hash, secret):
370
+ return jsonify({"error": "Invalid signature"}), 401
371
+
372
+ # Process webhook
373
+ result = webhook_handler(payload)
374
+ return jsonify(result)
375
+ ```
376
+
377
+ ### Brands
378
+
379
+ Register and manage brands for TCR (The Campaign Registry) business verification.
380
+
381
+ ```python
382
+ from ccai_python import CCAI
383
+
384
+ ccai = CCAI(
385
+ client_id="YOUR-CLIENT-ID",
386
+ api_key="YOUR-API-KEY"
387
+ )
388
+
389
+ # Create a brand
390
+ brand = ccai.brands.create({
391
+ "legalCompanyName": "Collect.org Inc.",
392
+ "dba": "Collect",
393
+ "entityType": "NON_PROFIT",
394
+ "taxId": "123456789",
395
+ "taxIdCountry": "US",
396
+ "country": "US",
397
+ "verticalType": "NON_PROFIT",
398
+ "websiteUrl": "https://www.collect.org",
399
+ "street": "123 Main Street",
400
+ "city": "San Francisco",
401
+ "state": "CA",
402
+ "postalCode": "94105",
403
+ "contactFirstName": "Jane",
404
+ "contactLastName": "Doe",
405
+ "contactEmail": "jane@collect.org",
406
+ "contactPhone": "+14155551234",
407
+ })
408
+ print(f"Brand created with ID: {brand['id']}")
409
+
410
+ # Get a brand by ID
411
+ fetched = ccai.brands.get(brand["id"])
412
+ print(f"Website match score: {fetched.get('websiteMatchScore')}")
413
+
414
+ # List all brands for the account
415
+ brands = ccai.brands.list()
416
+ print(f"Found {len(brands)} brand(s)")
417
+
418
+ # Update a brand (partial update)
419
+ updated = ccai.brands.update(brand["id"], {
420
+ "street": "456 Oak Avenue",
421
+ "city": "Los Angeles",
422
+ })
423
+
424
+ # Delete a brand
425
+ ccai.brands.delete(brand["id"])
426
+ ```
427
+
428
+ #### Entity Types
429
+
430
+ `PRIVATE_PROFIT`, `PUBLIC_PROFIT`, `NON_PROFIT`, `GOVERNMENT`, `SOLE_PROPRIETOR`
431
+
432
+ > Note: `PUBLIC_PROFIT` entities require `stockSymbol` and `stockExchange` fields.
433
+
434
+ #### Vertical Types
435
+
436
+ `AUTOMOTIVE`, `AGRICULTURE`, `BANKING`, `COMMUNICATION`, `CONSTRUCTION`, `EDUCATION`, `ENERGY`, `ENTERTAINMENT`, `GOVERNMENT`, `HEALTHCARE`, `HOSPITALITY`, `INSURANCE`, `LEGAL`, `MANUFACTURING`, `NON_PROFIT`, `PROFESSIONAL`, `REAL_ESTATE`, `RETAIL`, `TECHNOLOGY`, `TRANSPORTATION`
437
+
438
+ ### Campaigns
439
+
440
+ Register and manage campaigns for TCR (The Campaign Registry) carrier vetting. Each campaign must be linked to a verified brand.
441
+
442
+ ```python
443
+ from ccai_python import CCAI
444
+
445
+ ccai = CCAI(
446
+ client_id="YOUR-CLIENT-ID",
447
+ api_key="YOUR-API-KEY"
448
+ )
449
+
450
+ # Create a campaign
451
+ campaign = ccai.campaigns.create({
452
+ "brandId": 1,
453
+ "useCase": "MIXED",
454
+ "subUseCases": ["CUSTOMER_CARE", "TWO_FACTOR_AUTHENTICATION", "ACCOUNT_NOTIFICATION"],
455
+ "description": "Security codes and support messaging.",
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",
459
+ "hasEmbeddedLinks": True,
460
+ "hasEmbeddedPhone": False,
461
+ "isAgeGated": False,
462
+ "isDirectLending": False,
463
+ "optInKeywords": ["START"],
464
+ "optInMessage": "Welcome! Reply STOP to cancel.",
465
+ "optInProofUrl": "https://example.com/opt-in-proof.png",
466
+ "helpKeywords": ["HELP"],
467
+ "helpMessage": "For HELP email support@example.com.",
468
+ "optOutKeywords": ["STOP"],
469
+ "optOutMessage": "STOP received. You are unsubscribed.",
470
+ "sampleMessages": [
471
+ "Your code is 554321. Reply STOP to cancel.",
472
+ "Your ticket has been updated. Reply HELP for info."
473
+ ]
474
+ })
475
+ print(f"Campaign created with ID: {campaign['id']}")
476
+
477
+ # Get a campaign by ID
478
+ fetched = ccai.campaigns.get(campaign["id"])
479
+
480
+ # List all campaigns for the account
481
+ campaigns = ccai.campaigns.list()
482
+ print(f"Found {len(campaigns)} campaign(s)")
483
+
484
+ # Update a campaign (partial update)
485
+ updated = ccai.campaigns.update(campaign["id"], {
486
+ "description": "Updated description."
487
+ })
488
+
489
+ # Delete a campaign
490
+ ccai.campaigns.delete(campaign["id"])
491
+ ```
492
+
493
+ #### Use Cases
494
+
495
+ `TWO_FACTOR_AUTHENTICATION`, `ACCOUNT_NOTIFICATION`, `CUSTOMER_CARE`, `DELIVERY_NOTIFICATION`, `FRAUD_ALERT`, `HIGHER_EDUCATION`, `LOW_VOLUME_MIXED`, `MARKETING`, `MIXED`, `POLLING_VOTING`, `PUBLIC_SERVICE_ANNOUNCEMENT`, `SECURITY_ALERT`
496
+
497
+ > Note: `MIXED` and `LOW_VOLUME_MIXED` campaigns require 2–3 `subUseCases`.
498
+
499
+ > `termsLink` and `privacyLink` are optional fields on the campaign dict.
500
+
501
+ #### Sub-Use Cases
502
+
503
+ `TWO_FACTOR_AUTHENTICATION`, `ACCOUNT_NOTIFICATION`, `CUSTOMER_CARE`, `DELIVERY_NOTIFICATION`, `FRAUD_ALERT`, `MARKETING`, `POLLING_VOTING`
504
+
505
+ ## Features
506
+
507
+ - Send SMS messages to single or multiple recipients
508
+ - Send MMS messages with images
509
+ - Send Email campaigns with HTML content
510
+ - Schedule emails for future delivery
511
+ - Brand registration and management for TCR verification
512
+ - Campaign registration and management for TCR carrier vetting
513
+ - Webhook management (register, update, list, delete)
514
+ - Webhook event handling for web frameworks
515
+ - Validate email addresses (valid/invalid/risky) and phone numbers (valid/invalid/landline)
516
+ - Upload images to S3 with signed URLs
517
+ - Variable substitution in messages
518
+ - Progress tracking callbacks
519
+ - Type hints for better IDE integration
520
+ - Comprehensive error handling
521
+
522
+ ## Requirements
523
+
524
+ - Python 3.10 or higher
525
+ - `requests` library
526
+ - `pydantic` library
527
+
528
+ ## License
529
+
530
+ MIT