infrasynth-backend-kit/infrasynth/notifications/channels/sms_twilio.py
2026-08-28 14:38:47 -05:00

62 lines
2 KiB
Python

import logging
from twilio.base.exceptions import TwilioRestException
from twilio.rest import Client
from infrasynth.shared.enums import ChannelType
from infrasynth.shared.results import Result
from .base import BaseChannel
logger = logging.getLogger(__name__)
class TwilioSMSChannel(BaseChannel):
"""SMS channel that sends via the Twilio Programmable SMS API.
Configuration keys (read from ``ChannelConfig.config``):
- ``account_sid`` (required)
- ``auth_token`` (required)
- ``from_number`` (required, E.164 format)
"""
channel_type = ChannelType.SMS
def __init__(self, account_sid=None, auth_token=None, from_number=None):
self.account_sid = account_sid
self.auth_token = auth_token
self.from_number = from_number
@classmethod
def from_config(cls, config: dict) -> "TwilioSMSChannel":
config = {str(key).lower(): value for key, value in (config or {}).items()}
return cls(**config)
def send(
self,
recipient: str,
subject: str,
body: str,
is_html: bool = True,
attachments=None,
) -> Result[bool, str]:
if not (self.account_sid and self.auth_token and self.from_number):
return Result.err("Twilio credentials not configured")
try:
client = Client(self.account_sid, self.auth_token)
message_body = (body or subject or "")[:1600]
client.messages.create(
to=recipient,
from_=self.from_number,
body=message_body,
)
except TwilioRestException as exc:
logger.exception("Twilio send failed for %s", recipient)
return Result.err(f"Twilio error {exc.status}: {exc.msg}")
except Exception as exc: # noqa: BLE001
logger.exception("Twilio send failed for %s", recipient)
return Result.err(str(exc))
return Result.ok(True)
def health_check(self) -> bool:
return bool(self.account_sid and self.auth_token and self.from_number)