From 584515b8ea9ad224711bbdab58a9514ce1446778 Mon Sep 17 00:00:00 2001 From: Deyner lopez Date: Thu, 14 May 2026 17:45:32 -0500 Subject: [PATCH] https://mobileaws.atlassian.net/browse/CLOUD-2744 --- README.md | 37 ++++++++++ pyproject.toml | 2 +- src/ccai_python/__init__.py | 16 +++++ src/ccai_python/ccai.py | 2 + src/ccai_python/contact_validator_service.py | 76 ++++++++++++++++++++ 5 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 src/ccai_python/contact_validator_service.py diff --git a/README.md b/README.md index 2affb40..514922f 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,42 @@ response = ccai.contact.set_do_not_text( print(f"Contact {response.contact_id} do not text removed: {response.do_not_text}") ``` +### Contact Validator + +Validate email addresses and phone numbers. + +> Bulk endpoints accept up to 50 contacts per request and are processed server-side in chunks. + +```python +from ccai_python import CCAI + +ccai = CCAI( + client_id="YOUR-CLIENT-ID", + api_key="YOUR-API-KEY" +) + +# Validate a single email +email_result = ccai.contact_validator.validate_email("user@example.com") +print(email_result.status) # "valid" | "invalid" | "risky" +print(email_result.metadata.get("safe_to_send")) # True | False + +# Validate multiple emails (up to 50, processed server-side in chunks) +bulk_emails = ccai.contact_validator.validate_emails(["user@example.com", "bad@invalid.xyz"]) +print(bulk_emails.summary.model_dump()) # {"total": 2, "valid": 1, "invalid": 1, "risky": 0, "landline": 0} + +# Validate a single phone number +phone_result = ccai.contact_validator.validate_phone("+15551234567", country_code="US") +print(phone_result.status) # "valid" | "invalid" | "landline" +print(phone_result.metadata.get("carrier_type")) # "mobile" | "landline" | "voip" + +# Validate multiple phone numbers (up to 50, processed server-side in chunks) +bulk_phones = ccai.contact_validator.validate_phones([ + {"phone": "+15551234567"}, + {"phone": "+15559876543", "countryCode": "US"} +]) +print(bulk_phones.summary.model_dump()) # {"total": 2, "valid": 1, "invalid": 0, "risky": 0, "landline": 1} +``` + ### Webhooks ```python @@ -432,6 +468,7 @@ ccai.campaigns.delete(campaign["id"]) - Campaign registration and management for TCR carrier vetting - Webhook management (register, update, list, delete) - Webhook event handling for web frameworks +- Validate email addresses (valid/invalid/risky) and phone numbers (valid/invalid/landline) - Upload images to S3 with signed URLs - Variable substitution in messages - Progress tracking callbacks diff --git a/pyproject.toml b/pyproject.toml index 354ead9..1eb466d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "ccai-python" -version = "1.0.1" +version = "1.1.0" description = "Python client for CloudContactAI API" readme = "README.md" requires-python = ">=3.10" diff --git a/src/ccai_python/__init__.py b/src/ccai_python/__init__.py index 6d95f54..d1ba9c9 100644 --- a/src/ccai_python/__init__.py +++ b/src/ccai_python/__init__.py @@ -13,6 +13,15 @@ from .contact_service import Contact, ContactDoNotTextRequest, ContactDoNotTextResponse from .brand_service import Brand from .campaign_service import Campaign as CampaignService +from .contact_validator_service import ( + ContactValidator, + EmailValidationResult, + PhoneValidationResult, + ValidationSummary, + BulkEmailValidationResult, + BulkPhoneValidationResult, + PhoneInput, +) __all__ = [ 'CCAI', @@ -37,6 +46,13 @@ 'ContactDoNotTextResponse', 'Brand', 'CampaignService', + 'ContactValidator', + 'EmailValidationResult', + 'PhoneValidationResult', + 'ValidationSummary', + 'BulkEmailValidationResult', + 'BulkPhoneValidationResult', + 'PhoneInput', ] __version__ = '1.0.1' diff --git a/src/ccai_python/ccai.py b/src/ccai_python/ccai.py index f8a5134..08a7dcb 100644 --- a/src/ccai_python/ccai.py +++ b/src/ccai_python/ccai.py @@ -18,6 +18,7 @@ from .contact_service import Contact from .brand_service import Brand from .campaign_service import Campaign as CampaignService +from .contact_validator_service import ContactValidator class CCAIConfig(BaseModel): @@ -110,6 +111,7 @@ def __init__( self.contact = Contact(self) self.brands = Brand(self) self.campaigns = CampaignService(self) + self.contact_validator = ContactValidator(self) def _resolve_url( self, diff --git a/src/ccai_python/contact_validator_service.py b/src/ccai_python/contact_validator_service.py new file mode 100644 index 0000000..9b46949 --- /dev/null +++ b/src/ccai_python/contact_validator_service.py @@ -0,0 +1,76 @@ +""" +contact_validator_service.py - Email and phone contact validation via CloudContactAI + +:license: MIT +:copyright: 2025 CloudContactAI LLC +""" + +from typing import Any, Dict, List, Optional +from pydantic import BaseModel + + +class EmailValidationResult(BaseModel): + contactField: str + type: str + status: str + metadata: Dict[str, Any] = {} + + +class PhoneValidationResult(BaseModel): + contactField: str + type: str + status: str + metadata: Dict[str, Any] = {} + + +class ValidationSummary(BaseModel): + total: int + valid: int + invalid: int + risky: int + landline: int = 0 + + +class BulkEmailValidationResult(BaseModel): + results: List[EmailValidationResult] + summary: ValidationSummary + + +class BulkPhoneValidationResult(BaseModel): + results: List[PhoneValidationResult] + summary: ValidationSummary + + +class PhoneInput(BaseModel): + phone: str + countryCode: Optional[str] = None + + +class ContactValidator: + """Service for validating email addresses and phone numbers""" + + def __init__(self, ccai) -> None: + self.ccai = ccai + + def validate_email(self, email: str) -> EmailValidationResult: + """Validate a single email address""" + response = self.ccai.request('POST', '/v1/contact-validator/email', {'email': email}) + return EmailValidationResult(**response) + + def validate_emails(self, emails: List[str]) -> BulkEmailValidationResult: + """Validate multiple email addresses (up to 50)""" + response = self.ccai.request('POST', '/v1/contact-validator/emails', {'emails': emails}) + return BulkEmailValidationResult(**response) + + def validate_phone(self, phone: str, country_code: Optional[str] = None) -> PhoneValidationResult: + """Validate a single phone number in E.164 format""" + response = self.ccai.request( + 'POST', '/v1/contact-validator/phone', + {'phone': phone, 'countryCode': country_code} + ) + return PhoneValidationResult(**response) + + def validate_phones(self, phones: List[Dict[str, Any]]) -> BulkPhoneValidationResult: + """Validate multiple phone numbers (up to 50)""" + response = self.ccai.request('POST', '/v1/contact-validator/phones', {'phones': phones}) + return BulkPhoneValidationResult(**response)