Vibeship-spawner-skills twilio-communications

Twilio Communications Skill

install
source · Clone the upstream repo
git clone https://github.com/vibeforge1111/vibeship-spawner-skills
manifest: integrations/twilio-communications/skill.yaml
source content

Twilio Communications Skill

SMS, Voice, WhatsApp, and Verify API integration patterns

id: twilio-communications name: Twilio Communications category: integrations description: | Build communication features with Twilio: SMS messaging, voice calls, WhatsApp Business API, and user verification (2FA). Covers the full spectrum from simple notifications to complex IVR systems and multi-channel authentication. Critical focus on compliance, rate limits, and error handling.

version: 1.0.0

triggers:

  • "twilio"
  • "send SMS"
  • "text message"
  • "voice call"
  • "phone verification"
  • "2FA SMS"
  • "WhatsApp API"
  • "programmable messaging"
  • "IVR system"
  • "TwiML"
  • "phone number verification"

============================================================================

CORE PATTERNS

============================================================================

patterns:

  • id: sms-sending-pattern name: SMS Sending Pattern description: | Basic pattern for sending SMS messages with Twilio. Handles the fundamentals: phone number formatting, message delivery, and delivery status callbacks.

    Key considerations:

    • Phone numbers must be in E.164 format (+1234567890)
    • Default rate limit: 80 messages per second (MPS)
    • Messages over 160 characters are split (and cost more)
    • Carrier filtering can block messages (especially to US numbers) when_to_use:
    • "Sending notifications to users"
    • "Transactional messages (order confirmations, shipping)"
    • "Alerts and reminders" implementation: | from twilio.rest import Client from twilio.base.exceptions import TwilioRestException import os import re

    class TwilioSMS: """ SMS sending with proper error handling and validation. """

      def __init__(self):
          self.client = Client(
              os.environ["TWILIO_ACCOUNT_SID"],
              os.environ["TWILIO_AUTH_TOKEN"]
          )
          self.from_number = os.environ["TWILIO_PHONE_NUMBER"]
    
      def validate_e164(self, phone: str) -> bool:
          """Validate phone number is in E.164 format."""
          pattern = r'^\+[1-9]\d{1,14}$'
          return bool(re.match(pattern, phone))
    
      def send_sms(
          self,
          to: str,
          body: str,
          status_callback: str = None
      ) -> dict:
          """
          Send an SMS message.
    
          Args:
              to: Recipient phone number in E.164 format
              body: Message text (160 chars = 1 segment)
              status_callback: URL for delivery status webhooks
    
          Returns:
              Message SID and status
          """
          # Validate phone number format
          if not self.validate_e164(to):
              return {
                  "success": False,
                  "error": "Phone number must be in E.164 format (+1234567890)"
              }
    
          # Check message length (warn about segmentation)
          segment_count = (len(body) + 159) // 160
          if segment_count > 1:
              print(f"Warning: Message will be sent as {segment_count} segments")
    
          try:
              message = self.client.messages.create(
                  to=to,
                  from_=self.from_number,
                  body=body,
                  status_callback=status_callback
              )
    
              return {
                  "success": True,
                  "message_sid": message.sid,
                  "status": message.status,
                  "segments": segment_count
              }
    
          except TwilioRestException as e:
              return self._handle_error(e)
    
      def _handle_error(self, error: TwilioRestException) -> dict:
          """Handle Twilio-specific errors."""
          error_handlers = {
              21610: "Recipient has opted out. They must reply START.",
              21614: "Invalid 'To' phone number format.",
              21211: "'From' phone number is not valid.",
              30003: "Phone is unreachable (off, airplane mode, no signal).",
              30005: "Unknown destination (invalid number or landline).",
              30006: "Landline or unreachable carrier.",
              30429: "Rate limit exceeded. Implement exponential backoff.",
          }
    
          return {
              "success": False,
              "error_code": error.code,
              "error": error_handlers.get(error.code, error.msg),
              "details": str(error)
          }
    

    Usage

    sms = TwilioSMS() result = sms.send_sms( to="+14155551234", body="Your order #1234 has shipped!", status_callback="https://your-app.com/webhooks/twilio/status" ) anti_patterns:

    • "Not validating E.164 format before sending"
    • "Hardcoding Twilio credentials in code"
    • "Ignoring delivery status callbacks"
    • "Not handling the opted-out (21610) error"
  • id: twilio-verify-pattern name: Twilio Verify Pattern (2FA/OTP) description: | Use Twilio Verify for phone number verification and 2FA. Handles code generation, delivery, rate limiting, and fraud prevention.

    Key benefits over DIY OTP:

    • Twilio manages code generation and expiration
    • Built-in fraud prevention (saved customers $82M+ blocking 747M attempts)
    • Handles rate limiting automatically
    • Multi-channel: SMS, Voice, Email, Push, WhatsApp

    Google found SMS 2FA blocks "100% of automated bots, 96% of bulk phishing attacks, and 76% of targeted attacks." when_to_use:

    • "User phone number verification at signup"
    • "Two-factor authentication (2FA)"
    • "Password reset verification"
    • "High-value transaction confirmation" implementation: | from twilio.rest import Client from twilio.base.exceptions import TwilioRestException import os from enum import Enum from typing import Optional

    class VerifyChannel(Enum): SMS = "sms" CALL = "call" EMAIL = "email" WHATSAPP = "whatsapp"

    class TwilioVerify: """ Phone verification with Twilio Verify. Never store OTP codes - Twilio handles it. """

      def __init__(self, verify_service_sid: str = None):
          self.client = Client(
              os.environ["TWILIO_ACCOUNT_SID"],
              os.environ["TWILIO_AUTH_TOKEN"]
          )
          # Create a Verify Service in Twilio Console first
          self.service_sid = verify_service_sid or os.environ["TWILIO_VERIFY_SID"]
    
      def send_verification(
          self,
          to: str,
          channel: VerifyChannel = VerifyChannel.SMS,
          locale: str = "en"
      ) -> dict:
          """
          Send verification code to phone/email.
    
          Args:
              to: Phone number (E.164) or email
              channel: SMS, call, email, or whatsapp
              locale: Language code for message
    
          Returns:
              Verification status
          """
          try:
              verification = self.client.verify \
                  .v2 \
                  .services(self.service_sid) \
                  .verifications \
                  .create(
                      to=to,
                      channel=channel.value,
                      locale=locale
                  )
    
              return {
                  "success": True,
                  "status": verification.status,  # "pending"
                  "channel": channel.value,
                  "valid": verification.valid
              }
    
          except TwilioRestException as e:
              return self._handle_verify_error(e)
    
      def check_verification(self, to: str, code: str) -> dict:
          """
          Check if verification code is correct.
    
          Args:
              to: Phone number or email that received code
              code: The code entered by user
    
          Returns:
              Verification result
          """
          try:
              check = self.client.verify \
                  .v2 \
                  .services(self.service_sid) \
                  .verification_checks \
                  .create(
                      to=to,
                      code=code
                  )
    
              return {
                  "success": True,
                  "valid": check.status == "approved",
                  "status": check.status  # "approved" or "pending"
              }
    
          except TwilioRestException as e:
              # Code was wrong or expired
              return {
                  "success": False,
                  "valid": False,
                  "error": str(e)
              }
    
      def _handle_verify_error(self, error: TwilioRestException) -> dict:
          """Handle Verify-specific errors."""
          error_handlers = {
              60200: "Invalid phone number format",
              60203: "Max send attempts reached for this number",
              60205: "Service not found - check VERIFY_SID",
              60223: "Failed to create verification - carrier rejected",
          }
    
          return {
              "success": False,
              "error_code": error.code,
              "error": error_handlers.get(error.code, error.msg)
          }
    

    Usage Example - Signup Flow

    verify = TwilioVerify()

    Step 1: User enters phone number

    result = verify.send_verification("+14155551234", VerifyChannel.SMS) if result["success"]: print("Code sent! Check your phone.")

    Step 2: User enters the code they received

    code = "123456" # From user input check = verify.check_verification("+14155551234", code)

    if check["valid"]: print("Phone verified! Create account.") else: print("Invalid code. Try again.")

    Best Practice: Offer voice fallback

    async def verify_with_fallback(phone: str, max_attempts: int = 3): """Verify with voice fallback if SMS fails.""" for attempt in range(max_attempts): channel = VerifyChannel.SMS if attempt == 0 else VerifyChannel.CALL result = verify.send_verification(phone, channel)

          if result["success"]:
              return result
    
          # If SMS failed, wait and try voice
          if channel == VerifyChannel.SMS:
              await asyncio.sleep(30)
              continue
    
      return {"success": False, "error": "All verification attempts failed"}
    

    anti_patterns:

    • "Storing OTP codes in your database (Twilio handles this)"
    • "Not implementing rate limiting on your verify endpoint"
    • "Using same-code retries (let Verify generate new codes)"
    • "No fallback channel when SMS fails"
  • id: twiml-ivr-pattern name: TwiML IVR Pattern description: | Build Interactive Voice Response (IVR) systems using TwiML. TwiML (Twilio Markup Language) is XML that tells Twilio what to do when receiving calls.

    Core TwiML verbs:

    • <Say>: Text-to-speech
    • <Play>: Play audio file
    • <Gather>: Collect keypad/speech input
    • <Dial>: Connect to another number
    • <Record>: Record caller's voice
    • <Redirect>: Move to another TwiML endpoint

    Key insight: Twilio makes HTTP request to your webhook, you return TwiML, Twilio executes it. Stateless, so use URL params or sessions. when_to_use:

    • "Phone menu systems (press 1 for sales...)"
    • "Automated customer support"
    • "Appointment reminders with confirmation"
    • "Voicemail systems" implementation: | from flask import Flask, request, Response from twilio.twiml.voice_response import VoiceResponse, Gather from twilio.request_validator import RequestValidator import os

    app = Flask(name)

    def validate_twilio_request(f): """Decorator to validate requests are from Twilio.""" def wrapper(*args, **kwargs): validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"])

          # Get request details
          url = request.url
          params = request.form.to_dict()
          signature = request.headers.get("X-Twilio-Signature", "")
    
          if not validator.validate(url, params, signature):
              return "Invalid request", 403
    
          return f(*args, **kwargs)
      wrapper.__name__ = f.__name__
      return wrapper
    

    @app.route("/voice/incoming", methods=["POST"]) @validate_twilio_request def incoming_call(): """Handle incoming call with IVR menu.""" response = VoiceResponse()

      # Gather digits with timeout
      gather = Gather(
          num_digits=1,
          action="/voice/menu-selection",
          method="POST",
          timeout=5
      )
      gather.say(
          "Welcome to Acme Corp. "
          "Press 1 for sales. "
          "Press 2 for support. "
          "Press 3 to leave a message."
      )
      response.append(gather)
    
      # If no input, repeat
      response.redirect("/voice/incoming")
    
      return Response(str(response), mimetype="text/xml")
    

    @app.route("/voice/menu-selection", methods=["POST"]) @validate_twilio_request def menu_selection(): """Route based on menu selection.""" response = VoiceResponse() digit = request.form.get("Digits", "")

      if digit == "1":
          # Transfer to sales
          response.say("Connecting you to sales.")
          response.dial(os.environ["SALES_PHONE"])
    
      elif digit == "2":
          # Transfer to support
          response.say("Connecting you to support.")
          response.dial(os.environ["SUPPORT_PHONE"])
    
      elif digit == "3":
          # Voicemail
          response.say("Please leave a message after the beep.")
          response.record(
              action="/voice/voicemail-saved",
              max_length=120,
              transcribe=True,
              transcribe_callback="/voice/transcription"
          )
    
      else:
          response.say("Invalid selection.")
          response.redirect("/voice/incoming")
    
      return Response(str(response), mimetype="text/xml")
    

    @app.route("/voice/voicemail-saved", methods=["POST"]) @validate_twilio_request def voicemail_saved(): """Handle saved voicemail.""" response = VoiceResponse()

      recording_url = request.form.get("RecordingUrl")
      recording_sid = request.form.get("RecordingSid")
    
      # Save to database, notify team, etc.
      print(f"Voicemail saved: {recording_url}")
    
      response.say("Thank you. Goodbye.")
      response.hangup()
    
      return Response(str(response), mimetype="text/xml")
    

    @app.route("/voice/transcription", methods=["POST"]) @validate_twilio_request def transcription_callback(): """Handle voicemail transcription.""" transcription = request.form.get("TranscriptionText") recording_sid = request.form.get("RecordingSid")

      # Save transcription, send to Slack, etc.
      print(f"Transcription: {transcription}")
    
      return "", 200
    

    Outbound call example

    from twilio.rest import Client

    def make_outbound_call(to: str, message: str): """Make outbound call with custom TwiML.""" client = Client( os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"] )

      # TwiML Bin URL or your endpoint
      call = client.calls.create(
          to=to,
          from_=os.environ["TWILIO_PHONE_NUMBER"],
          url="https://your-app.com/voice/outbound-message",
          status_callback="https://your-app.com/voice/status"
      )
    
      return call.sid
    

    if name == "main": app.run(debug=True) anti_patterns:

    • "Not validating X-Twilio-Signature (security risk)"
    • "Returning non-XML responses to Twilio"
    • "Not handling timeout/no-input cases"
    • "Hardcoding phone numbers in TwiML"
  • id: whatsapp-business-pattern name: WhatsApp Business API Pattern description: | Send and receive WhatsApp messages via Twilio API. Uses the same Twilio Messages API as SMS with minor changes.

    Key WhatsApp rules:

    • 24-hour session window: Can only reply within 24 hours of user message
    • Template messages: Pre-approved templates for outside session window
    • Opt-in required: Users must explicitly consent to receive messages
    • Rate limit: 80 MPS default (up to 400 with approval)
    • Character limits: Non-template 1024 chars, templates ~550 chars when_to_use:
    • "Customer support with rich media"
    • "Order notifications with buttons"
    • "Marketing messages (with templates)"
    • "Interactive flows (booking, surveys)" implementation: | from twilio.rest import Client from twilio.base.exceptions import TwilioRestException import os from datetime import datetime, timedelta from typing import Optional

    class TwilioWhatsApp: """ WhatsApp Business API via Twilio. Handles session windows and template messages. """

      def __init__(self):
          self.client = Client(
              os.environ["TWILIO_ACCOUNT_SID"],
              os.environ["TWILIO_AUTH_TOKEN"]
          )
          # WhatsApp number format: whatsapp:+14155551234
          self.from_number = os.environ["TWILIO_WHATSAPP_NUMBER"]
    
      def send_message(
          self,
          to: str,
          body: str,
          media_url: Optional[str] = None
      ) -> dict:
          """
          Send WhatsApp message within 24-hour session.
    
          Args:
              to: Recipient number (E.164, without whatsapp: prefix)
              body: Message text (max 1024 chars for non-template)
              media_url: Optional image/document URL
    
          Returns:
              Message result
          """
          # Format for WhatsApp
          to_whatsapp = f"whatsapp:{to}"
          from_whatsapp = f"whatsapp:{self.from_number}"
    
          try:
              message_params = {
                  "to": to_whatsapp,
                  "from_": from_whatsapp,
                  "body": body
              }
    
              if media_url:
                  message_params["media_url"] = [media_url]
    
              message = self.client.messages.create(**message_params)
    
              return {
                  "success": True,
                  "message_sid": message.sid,
                  "status": message.status
              }
    
          except TwilioRestException as e:
              return self._handle_whatsapp_error(e)
    
      def send_template_message(
          self,
          to: str,
          content_sid: str,
          content_variables: dict
      ) -> dict:
          """
          Send pre-approved template message.
          Use this for messages outside 24-hour window.
    
          Content templates must be approved by WhatsApp first.
          Create them in Twilio Console > Content Template Builder.
          """
          to_whatsapp = f"whatsapp:{to}"
          from_whatsapp = f"whatsapp:{self.from_number}"
    
          try:
              message = self.client.messages.create(
                  to=to_whatsapp,
                  from_=from_whatsapp,
                  content_sid=content_sid,
                  content_variables=content_variables
              )
    
              return {
                  "success": True,
                  "message_sid": message.sid,
                  "template": True
              }
    
          except TwilioRestException as e:
              return self._handle_whatsapp_error(e)
    
      def _handle_whatsapp_error(self, error: TwilioRestException) -> dict:
          """Handle WhatsApp-specific errors."""
          error_handlers = {
              63016: "Outside 24-hour window. Use template message.",
              63018: "Template not approved or doesn't exist.",
              63025: "Too many template messages sent to this user.",
              63038: "Rate limit exceeded for WhatsApp.",
          }
    
          return {
              "success": False,
              "error_code": error.code,
              "error": error_handlers.get(error.code, error.msg)
          }
    

    Flask webhook for incoming WhatsApp messages

    from flask import Flask, request

    app = Flask(name)

    @app.route("/webhooks/whatsapp", methods=["POST"]) def whatsapp_webhook(): """Handle incoming WhatsApp messages.""" from_number = request.form.get("From", "").replace("whatsapp:", "") body = request.form.get("Body", "") media_url = request.form.get("MediaUrl0") # First attachment

      # Track session start (24-hour window begins now)
      session_start = datetime.now()
      session_expires = session_start + timedelta(hours=24)
    
      # Store in database for session tracking
      # user_sessions[from_number] = session_expires
    
      # Process message and respond
      response = process_whatsapp_message(from_number, body, media_url)
    
      # Reply within session
      whatsapp = TwilioWhatsApp()
      whatsapp.send_message(from_number, response)
    
      return "", 200
    

    def process_whatsapp_message(phone: str, text: str, media: str) -> str: """Process incoming message and generate response.""" text_lower = text.lower()

      if "order status" in text_lower:
          return "Your order #1234 is out for delivery!"
      elif "support" in text_lower:
          return "A support agent will contact you shortly."
      else:
          return "Thanks for your message! Reply with 'order status' or 'support'."
    

    Send typing indicator (2025 feature)

    def send_typing_indicator(to: str): """Let user know you're typing.""" # Requires Senders API setup pass anti_patterns:

    • "Sending non-template messages outside 24-hour window"
    • "Not tracking session windows per user"
    • "Exceeding 1024 char limit for session messages"
    • "Not handling template rejection errors"
  • id: webhook-handler-pattern name: Webhook Handler Pattern description: | Handle Twilio webhooks for delivery status, incoming messages, and call events. Critical: always validate X-Twilio-Signature.

    Twilio sends webhooks for:

    • Message status updates (queued → sent → delivered/failed)
    • Incoming SMS/WhatsApp messages
    • Call events (initiated, ringing, answered, completed)
    • Recording/transcription ready when_to_use:
    • "Tracking message delivery status"
    • "Receiving incoming messages"
    • "Call analytics and logging"
    • "Voicemail transcription processing" implementation: | from flask import Flask, request, abort from twilio.request_validator import RequestValidator from functools import wraps import os import logging

    app = Flask(name) logger = logging.getLogger(name)

    def validate_twilio_signature(f): """ Validate that request came from Twilio. CRITICAL: Always use this for webhook endpoints. """ @wraps(f) def wrapper(*args, **kwargs): validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"])

          # Build full URL (including query params)
          url = request.url
    
          # Get POST body as dict
          params = request.form.to_dict()
    
          # Get signature from header
          signature = request.headers.get("X-Twilio-Signature", "")
    
          if not validator.validate(url, params, signature):
              logger.warning(f"Invalid Twilio signature from {request.remote_addr}")
              abort(403)
    
          return f(*args, **kwargs)
      return wrapper
    

    @app.route("/webhooks/twilio/sms/status", methods=["POST"]) @validate_twilio_signature def sms_status_callback(): """ Handle SMS delivery status updates.

      Status progression: queued → sending → sent → delivered
      Or: queued → sending → undelivered/failed
      """
      message_sid = request.form.get("MessageSid")
      status = request.form.get("MessageStatus")
      error_code = request.form.get("ErrorCode")
      error_message = request.form.get("ErrorMessage")
    
      logger.info(f"SMS {message_sid}: {status}")
    
      if status == "delivered":
          # Message successfully delivered
          update_message_status(message_sid, "delivered")
    
      elif status == "undelivered":
          # Carrier rejected or other failure
          logger.error(f"SMS failed: {error_code} - {error_message}")
          handle_failed_message(message_sid, error_code, error_message)
    
      elif status == "failed":
          # Twilio couldn't send
          logger.error(f"SMS send failed: {error_code}")
          handle_failed_message(message_sid, error_code, error_message)
    
      return "", 200
    

    @app.route("/webhooks/twilio/sms/incoming", methods=["POST"]) @validate_twilio_signature def incoming_sms(): """ Handle incoming SMS messages. """ from_number = request.form.get("From") to_number = request.form.get("To") body = request.form.get("Body") num_media = int(request.form.get("NumMedia", 0))

      # Handle media attachments
      media_urls = []
      for i in range(num_media):
          media_urls.append(request.form.get(f"MediaUrl{i}"))
    
      # Check for opt-out keywords
      if body.strip().upper() in ["STOP", "UNSUBSCRIBE", "CANCEL"]:
          handle_opt_out(from_number)
          return "", 200
    
      # Check for opt-in keywords
      if body.strip().upper() in ["START", "SUBSCRIBE"]:
          handle_opt_in(from_number)
          return "", 200
    
      # Process message
      process_incoming_sms(from_number, body, media_urls)
    
      return "", 200
    

    @app.route("/webhooks/twilio/voice/status", methods=["POST"]) @validate_twilio_signature def voice_status_callback(): """Handle call status updates.""" call_sid = request.form.get("CallSid") status = request.form.get("CallStatus") duration = request.form.get("CallDuration") direction = request.form.get("Direction")

      # Call statuses: initiated, ringing, in-progress, completed, busy, no-answer, canceled, failed
    
      logger.info(f"Call {call_sid}: {status} ({duration}s)")
    
      if status == "completed":
          # Call ended normally
          log_call_completion(call_sid, duration)
    
      elif status in ["busy", "no-answer", "canceled", "failed"]:
          # Call didn't connect
          handle_failed_call(call_sid, status)
    
      return "", 200
    

    Helper functions

    def update_message_status(message_sid: str, status: str): """Update message status in database.""" pass

    def handle_failed_message(message_sid: str, error_code: str, error_msg: str): """Handle failed message delivery.""" # Notify team, retry logic, etc. pass

    def handle_opt_out(phone: str): """Handle user opting out of messages.""" # Mark user as opted out in database # IMPORTANT: Must respect this! pass

    def handle_opt_in(phone: str): """Handle user opting back in.""" pass

    def process_incoming_sms(from_phone: str, body: str, media: list): """Process incoming SMS message.""" pass

    def log_call_completion(call_sid: str, duration: str): """Log completed call.""" pass

    def handle_failed_call(call_sid: str, status: str): """Handle call that didn't connect.""" pass anti_patterns:

    • "Not validating X-Twilio-Signature"
    • "Exposing webhook URLs without authentication"
    • "Not handling opt-out keywords (STOP)"
    • "Blocking webhook response (should be fast)"
  • id: rate-limit-retry-pattern name: Rate Limit and Retry Pattern description: | Handle Twilio rate limits and implement proper retry logic.

    Default limits:

    • SMS: 80 messages per second (MPS)
    • Voice: Varies by number type and region
    • API calls: 100 requests per second

    Error codes:

    • 20429: Voice API rate limit
    • 30429: Messaging API rate limit when_to_use:
    • "High-volume messaging applications"
    • "Bulk SMS campaigns"
    • "Automated calling systems" implementation: | import time import random from functools import wraps from twilio.base.exceptions import TwilioRestException import logging

    logger = logging.getLogger(name)

    def exponential_backoff_retry( max_retries: int = 5, base_delay: float = 1.0, max_delay: float = 60.0, rate_limit_codes: list = [20429, 30429] ): """ Decorator for exponential backoff retry on rate limits.

      Uses jitter to prevent thundering herd.
      """
      def decorator(func):
          @wraps(func)
          def wrapper(*args, **kwargs):
              last_exception = None
    
              for attempt in range(max_retries + 1):
                  try:
                      return func(*args, **kwargs)
    
                  except TwilioRestException as e:
                      last_exception = e
    
                      # Only retry on rate limit errors
                      if e.code not in rate_limit_codes:
                          raise
    
                      if attempt == max_retries:
                          logger.error(f"Max retries exceeded: {e}")
                          raise
    
                      # Calculate delay with jitter
                      delay = min(
                          base_delay * (2 ** attempt) + random.uniform(0, 1),
                          max_delay
                      )
    
                      logger.warning(
                          f"Rate limited (attempt {attempt + 1}/{max_retries}). "
                          f"Retrying in {delay:.1f}s"
                      )
                      time.sleep(delay)
    
              raise last_exception
    
          return wrapper
      return decorator
    

    Usage

    from twilio.rest import Client

    client = Client(account_sid, auth_token)

    @exponential_backoff_retry(max_retries=5) def send_sms(to: str, body: str): return client.messages.create( to=to, from_=from_number, body=body )

    Bulk sending with rate limiting

    import asyncio from asyncio import Semaphore

    class RateLimitedSender: """ Send messages with built-in rate limiting. Stays under Twilio's 80 MPS limit. """

      def __init__(self, client, from_number: str, mps: int = 50):
          self.client = client
          self.from_number = from_number
          self.mps = mps
          self.semaphore = Semaphore(mps)
    
      async def send_bulk(self, messages: list[dict]) -> list[dict]:
          """
          Send messages with rate limiting.
    
          Args:
              messages: List of {"to": "+1...", "body": "..."}
    
          Returns:
              Results for each message
          """
          tasks = [
              self._send_with_limit(msg["to"], msg["body"])
              for msg in messages
          ]
    
          return await asyncio.gather(*tasks, return_exceptions=True)
    
      async def _send_with_limit(self, to: str, body: str):
          """Send single message with semaphore-based rate limit."""
          async with self.semaphore:
              try:
                  # Use sync client in thread pool
                  loop = asyncio.get_event_loop()
                  result = await loop.run_in_executor(
                      None,
                      lambda: self.client.messages.create(
                          to=to,
                          from_=self.from_number,
                          body=body
                      )
                  )
                  return {"success": True, "sid": result.sid, "to": to}
    
              except TwilioRestException as e:
                  return {"success": False, "error": str(e), "to": to}
    
              finally:
                  # Delay to maintain rate limit
                  await asyncio.sleep(1 / self.mps)
    

    Usage

    async def send_campaign(): sender = RateLimitedSender(client, from_number, mps=50)

      messages = [
          {"to": "+14155551234", "body": "Hello!"},
          {"to": "+14155555678", "body": "Hello!"},
          # ... thousands of messages
      ]
    
      results = await sender.send_bulk(messages)
    
      successful = sum(1 for r in results if r.get("success"))
      print(f"Sent {successful}/{len(messages)} messages")
    

    anti_patterns:

    • "Retrying immediately without backoff"
    • "No jitter causing thundering herd"
    • "Retrying non-rate-limit errors"
    • "Exceeding Twilio's MPS limit"

============================================================================

DECISION FRAMEWORK

============================================================================

decision_framework: messaging_channel: question: "Which messaging channel should I use?" options: sms: when: "Universal reach, transactional messages" pros: "Works on all phones, no app needed" cons: "Per-message cost, carrier filtering, 160 char limit" cost: "$0.0075+ per message (US)"

  whatsapp:
    when: "Rich media, international, interactive"
    pros: "Free inbound, rich media, 2B+ users"
    cons: "24-hour session window, template approval"
    cost: "Template: $0.005-0.08, Session: Free response"

  voice:
    when: "Urgent, accessibility, verification fallback"
    pros: "Higher attention, works without data"
    cons: "Higher cost, more intrusive"
    cost: "$0.013+ per minute"

verification_method: question: "How should I verify users?" options: sms_otp: when: "Most users, broad compatibility" security: "Blocks 96% of bulk phishing attacks" cost: "$0.05 per verification"

  voice_otp:
    when: "SMS delivery issues, accessibility"
    security: "Same as SMS"
    cost: "$0.05 per verification"

  silent_network_auth:
    when: "Best UX, mobile apps"
    security: "Highest - carrier-verified"
    cost: "$0.05 per verification"

  whatsapp_otp:
    when: "WhatsApp-native apps, international"
    security: "Same as SMS"
    cost: "$0.05 per verification"

============================================================================

HANDOFFS

============================================================================

handoffs:

  • to: slack-bot-builder when: "Need Slack integration with Twilio" context: "Send SMS from Slack, notify channels on calls"

  • to: voice-agents when: "Building AI voice assistant" context: "Twilio provides telephony, voice agent handles conversation"

  • to: auth-specialist when: "Implementing full auth system" context: "Twilio Verify for 2FA, auth-specialist for broader auth"

  • to: devops when: "High-volume production deployment" context: "Rate limiting, monitoring, scaling webhooks"

============================================================================

QUICK REFERENCE

============================================================================

quick_reference: phone_format: correct: "+14155551234" incorrect: "415-555-1234, (415) 555-1234, 4155551234" note: "Always use E.164 format"

rate_limits: sms: "80 MPS default, 400 with approval" voice: "Varies by number type" api: "100 requests per second"

common_errors: 21610: "Recipient opted out (replied STOP)" 30003: "Phone unreachable (off, airplane mode)" 30005: "Unknown/invalid phone number" 30429: "Rate limit exceeded" 63016: "WhatsApp: Outside 24-hour window"

costs_usd: sms_outbound_us: "$0.0079" sms_inbound_us: "$0.0079" whatsapp_template: "$0.005-0.08" verify_sms: "$0.05" voice_outbound: "$0.013/min"

environment_variables: required: - "TWILIO_ACCOUNT_SID" - "TWILIO_AUTH_TOKEN" - "TWILIO_PHONE_NUMBER" optional: - "TWILIO_VERIFY_SID" - "TWILIO_WHATSAPP_NUMBER"