[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M7CEKY2DPJDZ26G513ATVEWV":3,"skill-same:01M7CEKY2DPJDZ26G513ATVEWV":50},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":27,"commitSha":28,"skillDirectory":29,"forksCount":30,"repositoryHtmlUrl":31,"repositoryHomepageUrl":32,"ownerHtmlUrl":33,"ownerBlogUrl":6,"ownerDescription":34,"updatedAt":35,"install":36,"resources":49},"01M7CEKY2DPJDZ26G513ATVEWV","twilio-communications",null,"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.","YYC-Cube\u002FYYC3-Knowledge-Base","YYC-Cube","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F235006543?v=4","01M79J2PH4SWD2X0825X6PN748","yyc3","01M79J2PHVK3F6ZBYT8K2SCZCW",1,"MIT","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-Skills-技能包\u002FYYC3-Skills-技能包-antigravity-awesome-skills\u002Fplugins\u002Fantigravity-awesome-skills\u002Fskills\u002Ftwilio-communications\u002FSKILL.md",[],[],"2026-11-15 14:19:17",false,"---\nname: twilio-communications\ndescription: \"Build communication features with Twilio: SMS messaging, voice\n  calls, WhatsApp Business API, and user verification (2FA). Covers the full\n  spectrum from simple notifications to complex IVR systems and multi-channel\n  authentication.\"\nrisk: unknown\nsource: vibeship-spawner-skills (Apache 2.0)\ndate_added: 2026-02-27\n---\n\n# Twilio Communications\n\nBuild communication features with Twilio: SMS messaging, voice calls,\nWhatsApp Business API, and user verification (2FA). Covers the full\nspectrum from simple notifications to complex IVR systems and multi-channel\nauthentication. Critical focus on compliance, rate limits, and error handling.\n\n## Patterns\n\n### SMS Sending Pattern\n\nBasic pattern for sending SMS messages with Twilio.\nHandles the fundamentals: phone number formatting, message delivery,\nand delivery status callbacks.\n\nKey considerations:\n- Phone numbers must be in E.164 format (+1234567890)\n- Default rate limit: 80 messages per second (MPS)\n- Messages over 160 characters are split (and cost more)\n- Carrier filtering can block messages (especially to US numbers)\n\n**When to use**: Sending notifications to users,Transactional messages (order confirmations, shipping),Alerts and reminders\n\nfrom twilio.rest import Client\nfrom twilio.base.exceptions import TwilioRestException\nimport os\nimport re\n\nclass TwilioSMS:\n    \"\"\"\n    SMS sending with proper error handling and validation.\n    \"\"\"\n\n    def __init__(self):\n        self.client = Client(\n            os.environ[\"TWILIO_ACCOUNT_SID\"],\n            os.environ[\"TWILIO_AUTH_TOKEN\"]\n        )\n        self.from_number = os.environ[\"TWILIO_PHONE_NUMBER\"]\n\n    def validate_e164(self, phone: str) -> bool:\n        \"\"\"Validate phone number is in E.164 format.\"\"\"\n        pattern = r'^\\+[1-9]\\d{1,14}$'\n        return bool(re.match(pattern, phone))\n\n    def send_sms(\n        self,\n        to: str,\n        body: str,\n        status_callback: str = None\n    ) -> dict:\n        \"\"\"\n        Send an SMS message.\n\n        Args:\n            to: Recipient phone number in E.164 format\n            body: Message text (160 chars = 1 segment)\n            status_callback: URL for delivery status webhooks\n\n        Returns:\n            Message SID and status\n        \"\"\"\n        # Validate phone number format\n        if not self.validate_e164(to):\n            return {\n                \"success\": False,\n                \"error\": \"Phone number must be in E.164 format (+1234567890)\"\n            }\n\n        # Check message length (warn about segmentation)\n        segment_count = (len(body) + 159) \u002F\u002F 160\n        if segment_count > 1:\n            print(f\"Warning: Message will be sent as {segment_count} segments\")\n\n        try:\n            message = self.client.messages.create(\n                to=to,\n                from_=self.from_number,\n                body=body,\n                status_callback=status_callback\n            )\n\n            return {\n                \"success\": True,\n                \"message_sid\": message.sid,\n                \"status\": message.status,\n                \"segments\": segment_count\n            }\n\n        except TwilioRestException as e:\n            return self._handle_error(e)\n\n    def _handle_error(self, error: TwilioRestException) -> dict:\n        \"\"\"Handle Twilio-specific errors.\"\"\"\n        error_handlers = {\n            21610: \"Recipient has opted out. They must reply START.\",\n            21614: \"Invalid 'To' phone number format.\",\n            21211: \"'From' phone number is not valid.\",\n            30003: \"Phone is unreachable (off, airplane mode, no signal).\",\n            30005: \"Unknown destination (invalid number or landline).\",\n            30006: \"Landline or unreachable carrier.\",\n            30429: \"Rate limit exceeded. Implement exponential backoff.\",\n        }\n\n        return {\n            \"success\": False,\n            \"error_code\": error.code,\n            \"error\": error_handlers.get(error.code, error.msg),\n            \"details\": str(error)\n        }\n\n# Usage\nsms = TwilioSMS()\nresult = sms.send_sms(\n    to=\"+14155551234\",\n    body=\"Your order #1234 has shipped!\",\n    status_callback=\"https:\u002F\u002Fyour-app.com\u002Fwebhooks\u002Ftwilio\u002Fstatus\"\n)\n\n### Anti_patterns\n\n- Not validating E.164 format before sending\n- Hardcoding Twilio credentials in code\n- Ignoring delivery status callbacks\n- Not handling the opted-out (21610) error\n\n### Twilio Verify Pattern (2FA\u002FOTP)\n\nUse Twilio Verify for phone number verification and 2FA.\nHandles code generation, delivery, rate limiting, and fraud prevention.\n\nKey benefits over DIY OTP:\n- Twilio manages code generation and expiration\n- Built-in fraud prevention (saved customers $82M+ blocking 747M attempts)\n- Handles rate limiting automatically\n- Multi-channel: SMS, Voice, Email, Push, WhatsApp\n\nGoogle found SMS 2FA blocks \"100% of automated bots, 96% of bulk\nphishing attacks, and 76% of targeted attacks.\"\n\n**When to use**: User phone number verification at signup,Two-factor authentication (2FA),Password reset verification,High-value transaction confirmation\n\nfrom twilio.rest import Client\nfrom twilio.base.exceptions import TwilioRestException\nimport os\nfrom enum import Enum\nfrom typing import Optional\n\nclass VerifyChannel(Enum):\n    SMS = \"sms\"\n    CALL = \"call\"\n    EMAIL = \"email\"\n    WHATSAPP = \"whatsapp\"\n\nclass TwilioVerify:\n    \"\"\"\n    Phone verification with Twilio Verify.\n    Never store OTP codes - Twilio handles it.\n    \"\"\"\n\n    def __init__(self, verify_service_sid: str = None):\n        self.client = Client(\n            os.environ[\"TWILIO_ACCOUNT_SID\"],\n            os.environ[\"TWILIO_AUTH_TOKEN\"]\n        )\n        # Create a Verify Service in Twilio Console first\n        self.service_sid = verify_service_sid or os.environ[\"TWILIO_VERIFY_SID\"]\n\n    def send_verification(\n        self,\n        to: str,\n        channel: VerifyChannel = VerifyChannel.SMS,\n        locale: str = \"en\"\n    ) -> dict:\n        \"\"\"\n        Send verification code to phone\u002Femail.\n\n        Args:\n            to: Phone number (E.164) or email\n            channel: SMS, call, email, or whatsapp\n            locale: Language code for message\n\n        Returns:\n            Verification status\n        \"\"\"\n        try:\n            verification = self.client.verify \\\n                .v2 \\\n                .services(self.service_sid) \\\n                .verifications \\\n                .create(\n                    to=to,\n                    channel=channel.value,\n                    locale=locale\n                )\n\n            return {\n                \"success\": True,\n                \"status\": verification.status,  # \"pending\"\n                \"channel\": channel.value,\n                \"valid\": verification.valid\n            }\n\n        except TwilioRestException as e:\n            return self._handle_verify_error(e)\n\n    def check_verification(self, to: str, code: str) -> dict:\n        \"\"\"\n        Check if verification code is correct.\n\n        Args:\n            to: Phone number or email that received code\n            code: The code entered by user\n\n        Returns:\n            Verification result\n        \"\"\"\n        try:\n            check = self.client.verify \\\n                .v2 \\\n                .services(self.service_sid) \\\n                .verification_checks \\\n                .create(\n                    to=to,\n                    code=code\n                )\n\n            return {\n                \"success\": True,\n                \"valid\": check.status == \"approved\",\n                \"status\": check.status  # \"approved\" or \"pending\"\n            }\n\n        except TwilioRestException as e:\n            # Code was wrong or expired\n            return {\n                \"success\": False,\n                \"valid\": False,\n                \"error\": str(e)\n            }\n\n    def _handle_verify_error(self, error: TwilioRestException) -> dict:\n        \"\"\"Handle Verify-specific errors.\"\"\"\n        error_handlers = {\n            60200: \"Invalid phone number format\",\n            60203: \"Max send attempts reached for this number\",\n            60205: \"Service not found - check VERIFY_SID\",\n            60223: \"Failed to create verification - carrier rejected\",\n        }\n\n        return {\n            \"success\": False,\n            \"error_code\": error.code,\n            \"error\": error_handlers.get(error.code, error.msg)\n        }\n\n# Usage Example - Signup Flow\nverify = TwilioVerify()\n\n# Step 1: User enters phone number\nresult = verify.send_verification(\"+14155551234\", VerifyChannel.SMS)\nif result[\"success\"]:\n    print(\"Code sent! Check your phone.\")\n\n# Step 2: User enters the code they received\ncode = \"123456\"  # From user input\ncheck = verify.check_verification(\"+14155551234\", code)\n\nif check[\"valid\"]:\n    print(\"Phone verified! Create account.\")\nelse:\n    print(\"Invalid code. Try again.\")\n\n# Best Practice: Offer voice fallback\nasync def verify_with_fallback(phone: str, max_attempts: int = 3):\n    \"\"\"Verify with voice fallback if SMS fails.\"\"\"\n    for attempt in range(max_attempts):\n        channel = VerifyChannel.SMS if attempt == 0 else VerifyChannel.CALL\n        result = verify.send_verification(phone, channel)\n\n        if result[\"success\"]:\n            return result\n\n        # If SMS failed, wait and try voice\n        if channel == VerifyChannel.SMS:\n            await asyncio.sleep(30)\n            continue\n\n    return {\"success\": False, \"error\": \"All verification attempts failed\"}\n\n### Anti_patterns\n\n- Storing OTP codes in your database (Twilio handles this)\n- Not implementing rate limiting on your verify endpoint\n- Using same-code retries (let Verify generate new codes)\n- No fallback channel when SMS fails\n\n### TwiML IVR Pattern\n\nBuild Interactive Voice Response (IVR) systems using TwiML.\nTwiML (Twilio Markup Language) is XML that tells Twilio what to do\nwhen receiving calls.\n\nCore TwiML verbs:\n- \u003CSay>: Text-to-speech\n- \u003CPlay>: Play audio file\n- \u003CGather>: Collect keypad\u002Fspeech input\n- \u003CDial>: Connect to another number\n- \u003CRecord>: Record caller's voice\n- \u003CRedirect>: Move to another TwiML endpoint\n\nKey insight: Twilio makes HTTP request to your webhook, you return\nTwiML, Twilio executes it. Stateless, so use URL params or sessions.\n\n**When to use**: Phone menu systems (press 1 for sales...),Automated customer support,Appointment reminders with confirmation,Voicemail systems\n\nfrom flask import Flask, request, Response\nfrom twilio.twiml.voice_response import VoiceResponse, Gather\nfrom twilio.request_validator import RequestValidator\nimport os\n\napp = Flask(__name__)\n\ndef validate_twilio_request(f):\n    \"\"\"Decorator to validate requests are from Twilio.\"\"\"\n    def wrapper(*args, **kwargs):\n        validator = RequestValidator(os.environ[\"TWILIO_AUTH_TOKEN\"])\n\n        # Get request details\n        url = request.url\n        params = request.form.to_dict()\n        signature = request.headers.get(\"X-Twilio-Signature\", \"\")\n\n        if not validator.validate(url, params, signature):\n            return \"Invalid request\", 403\n\n        return f(*args, **kwargs)\n    wrapper.__name__ = f.__name__\n    return wrapper\n\n@app.route(\"\u002Fvoice\u002Fincoming\", methods=[\"POST\"])\n@validate_twilio_request\ndef incoming_call():\n    \"\"\"Handle incoming call with IVR menu.\"\"\"\n    response = VoiceResponse()\n\n    # Gather digits with timeout\n    gather = Gather(\n        num_digits=1,\n        action=\"\u002Fvoice\u002Fmenu-selection\",\n        method=\"POST\",\n        timeout=5\n    )\n    gather.say(\n        \"Welcome to Acme Corp. \"\n        \"Press 1 for sales. \"\n        \"Press 2 for support. \"\n        \"Press 3 to leave a message.\"\n    )\n    response.append(gather)\n\n    # If no input, repeat\n    response.redirect(\"\u002Fvoice\u002Fincoming\")\n\n    return Response(str(response), mimetype=\"text\u002Fxml\")\n\n@app.route(\"\u002Fvoice\u002Fmenu-selection\", methods=[\"POST\"])\n@validate_twilio_request\ndef menu_selection():\n    \"\"\"Route based on menu selection.\"\"\"\n    response = VoiceResponse()\n    digit = request.form.get(\"Digits\", \"\")\n\n    if digit == \"1\":\n        # Transfer to sales\n        response.say(\"Connecting you to sales.\")\n        response.dial(os.environ[\"SALES_PHONE\"])\n\n    elif digit == \"2\":\n        # Transfer to support\n        response.say(\"Connecting you to support.\")\n        response.dial(os.environ[\"SUPPORT_PHONE\"])\n\n    elif digit == \"3\":\n        # Voicemail\n        response.say(\"Please leave a message after the beep.\")\n        response.record(\n            action=\"\u002Fvoice\u002Fvoicemail-saved\",\n            max_length=120,\n            transcribe=True,\n            transcribe_callback=\"\u002Fvoice\u002Ftranscription\"\n        )\n\n    else:\n        response.say(\"Invalid selection.\")\n        response.redirect(\"\u002Fvoice\u002Fincoming\")\n\n    return Response(str(response), mimetype=\"text\u002Fxml\")\n\n@app.route(\"\u002Fvoice\u002Fvoicemail-saved\", methods=[\"POST\"])\n@validate_twilio_request\ndef voicemail_saved():\n    \"\"\"Handle saved voicemail.\"\"\"\n    response = VoiceResponse()\n\n    recording_url = request.form.get(\"RecordingUrl\")\n    recording_sid = request.form.get(\"RecordingSid\")\n\n    # Save to database, notify team, etc.\n    print(f\"Voicemail saved: {recording_url}\")\n\n    response.say(\"Thank you. Goodbye.\")\n    response.hangup()\n\n    return Response(str(response), mimetype=\"text\u002Fxml\")\n\n@app.route(\"\u002Fvoice\u002Ftranscription\", methods=[\"POST\"])\n@validate_twilio_request\ndef transcription_callback():\n    \"\"\"Handle voicemail transcription.\"\"\"\n    transcription = request.form.get(\"TranscriptionText\")\n    recording_sid = request.form.get(\"RecordingSid\")\n\n    # Save transcription, send to Slack, etc.\n    print(f\"Transcription: {transcription}\")\n\n    return \"\", 200\n\n# Outbound call example\nfrom twilio.rest import Client\n\ndef make_outbound_call(to: str, message: str):\n    \"\"\"Make outbound call with custom TwiML.\"\"\"\n    client = Client(\n        os.environ[\"TWILIO_ACCOUNT_SID\"],\n        os.environ[\"TWILIO_AUTH_TOKEN\"]\n    )\n\n    # TwiML Bin URL or your endpoint\n    call = client.calls.create(\n        to=to,\n        from_=os.environ[\"TWILIO_PHONE_NUMBER\"],\n        url=\"https:\u002F\u002Fyour-app.com\u002Fvoice\u002Foutbound-message\",\n        status_callback=\"https:\u002F\u002Fyour-app.com\u002Fvoice\u002Fstatus\"\n    )\n\n    return call.sid\n\nif __name__ == \"__main__\":\n    app.run(debug=True)\n\n### Anti_patterns\n\n- Not validating X-Twilio-Signature (security risk)\n- Returning non-XML responses to Twilio\n- Not handling timeout\u002Fno-input cases\n- Hardcoding phone numbers in TwiML\n\n### WhatsApp Business API Pattern\n\nSend and receive WhatsApp messages via Twilio API.\nUses the same Twilio Messages API as SMS with minor changes.\n\nKey WhatsApp rules:\n- 24-hour session window: Can only reply within 24 hours of user message\n- Template messages: Pre-approved templates for outside session window\n- Opt-in required: Users must explicitly consent to receive messages\n- Rate limit: 80 MPS default (up to 400 with approval)\n- Character limits: Non-template 1024 chars, templates ~550 chars\n\n**When to use**: Customer support with rich media,Order notifications with buttons,Marketing messages (with templates),Interactive flows (booking, surveys)\n\nfrom twilio.rest import Client\nfrom twilio.base.exceptions import TwilioRestException\nimport os\nfrom datetime import datetime, timedelta\nfrom typing import Optional\n\nclass TwilioWhatsApp:\n    \"\"\"\n    WhatsApp Business API via Twilio.\n    Handles session windows and template messages.\n    \"\"\"\n\n    def __init__(self):\n        self.client = Client(\n            os.environ[\"TWILIO_ACCOUNT_SID\"],\n            os.environ[\"TWILIO_AUTH_TOKEN\"]\n        )\n        # WhatsApp number format: whatsapp:+14155551234\n        self.from_number = os.environ[\"TWILIO_WHATSAPP_NUMBER\"]\n\n    def send_message(\n        self,\n        to: str,\n        body: str,\n        media_url: Optional[str] = None\n    ) -> dict:\n        \"\"\"\n        Send WhatsApp message within 24-hour session.\n\n        Args:\n            to: Recipient number (E.164, without whatsapp: prefix)\n            body: Message text (max 1024 chars for non-template)\n            media_url: Optional image\u002Fdocument URL\n\n        Returns:\n            Message result\n        \"\"\"\n        # Format for WhatsApp\n        to_whatsapp = f\"whatsapp:{to}\"\n        from_whatsapp = f\"whatsapp:{self.from_number}\"\n\n        try:\n            message_params = {\n                \"to\": to_whatsapp,\n                \"from_\": from_whatsapp,\n                \"body\": body\n            }\n\n            if media_url:\n                message_params[\"media_url\"] = [media_url]\n\n            message = self.client.messages.create(**message_params)\n\n            return {\n                \"success\": True,\n                \"message_sid\": message.sid,\n                \"status\": message.status\n            }\n\n        except TwilioRestException as e:\n            return self._handle_whatsapp_error(e)\n\n    def send_template_message(\n        self,\n        to: str,\n        content_sid: str,\n        content_variables: dict\n    ) -> dict:\n        \"\"\"\n        Send pre-approved template message.\n        Use this for messages outside 24-hour window.\n\n        Content templates must be approved by WhatsApp first.\n        Create them in Twilio Console > Content Template Builder.\n        \"\"\"\n        to_whatsapp = f\"whatsapp:{to}\"\n        from_whatsapp = f\"whatsapp:{self.from_number}\"\n\n        try:\n            message = self.client.messages.create(\n                to=to_whatsapp,\n                from_=from_whatsapp,\n                content_sid=content_sid,\n                content_variables=content_variables\n            )\n\n            return {\n                \"success\": True,\n                \"message_sid\": message.sid,\n                \"template\": True\n            }\n\n        except TwilioRestException as e:\n            return self._handle_whatsapp_error(e)\n\n    def _handle_whatsapp_error(self, error: TwilioRestException) -> dict:\n        \"\"\"Handle WhatsApp-specific errors.\"\"\"\n        error_handlers = {\n            63016: \"Outside 24-hour window. Use template message.\",\n            63018: \"Template not approved or doesn't exist.\",\n            63025: \"Too many template messages sent to this user.\",\n            63038: \"Rate limit exceeded for WhatsApp.\",\n        }\n\n        return {\n            \"success\": False,\n            \"error_code\": error.code,\n            \"error\": error_handlers.get(error.code, error.msg)\n        }\n\n# Flask webhook for incoming WhatsApp messages\nfrom flask import Flask, request\n\napp = Flask(__name__)\n\n@app.route(\"\u002Fwebhooks\u002Fwhatsapp\", methods=[\"POST\"])\ndef whatsapp_webhook():\n    \"\"\"Handle incoming WhatsApp messages.\"\"\"\n    from_number = request.form.get(\"From\", \"\").replace(\"whatsapp:\", \"\")\n    body = request.form.get(\"Body\", \"\")\n    media_url = request.form.get(\"MediaUrl0\")  # First attachment\n\n    # Track session start (24-hour window begins now)\n    session_start = datetime.now()\n    session_expires = session_start + timedelta(hours=24)\n\n    # Store in database for session tracking\n    # user_sessions[from_number] = session_expires\n\n    # Process message and respond\n    response = process_whatsapp_message(from_number, body, media_url)\n\n    # Reply within session\n    whatsapp = TwilioWhatsApp()\n    whatsapp.send_message(from_number, response)\n\n    return \"\", 200\n\ndef process_whatsapp_message(phone: str, text: str, media: str) -> str:\n    \"\"\"Process incoming message and generate response.\"\"\"\n    text_lower = text.lower()\n\n    if \"order status\" in text_lower:\n        return \"Your order #1234 is out for delivery!\"\n    elif \"support\" in text_lower:\n        return \"A support agent will contact you shortly.\"\n    else:\n        return \"Thanks for your message! Reply with 'order status' or 'support'.\"\n\n# Send typing indicator (2025 feature)\ndef send_typing_indicator(to: str):\n    \"\"\"Let user know you're typing.\"\"\"\n    # Requires Senders API setup\n    pass\n\n### Anti_patterns\n\n- Sending non-template messages outside 24-hour window\n- Not tracking session windows per user\n- Exceeding 1024 char limit for session messages\n- Not handling template rejection errors\n\n### Webhook Handler Pattern\n\nHandle Twilio webhooks for delivery status, incoming messages,\nand call events. Critical: always validate X-Twilio-Signature.\n\nTwilio sends webhooks for:\n- Message status updates (queued → sent → delivered\u002Ffailed)\n- Incoming SMS\u002FWhatsApp messages\n- Call events (initiated, ringing, answered, completed)\n- Recording\u002Ftranscription ready\n\n**When to use**: Tracking message delivery status,Receiving incoming messages,Call analytics and logging,Voicemail transcription processing\n\nfrom flask import Flask, request, abort\nfrom twilio.request_validator import RequestValidator\nfrom functools import wraps\nimport os\nimport logging\n\napp = Flask(__name__)\nlogger = logging.getLogger(__name__)\n\ndef validate_twilio_signature(f):\n    \"\"\"\n    Validate that request came from Twilio.\n    CRITICAL: Always use this for webhook endpoints.\n    \"\"\"\n    @wraps(f)\n    def wrapper(*args, **kwargs):\n        validator = RequestValidator(os.environ[\"TWILIO_AUTH_TOKEN\"])\n\n        # Build full URL (including query params)\n        url = request.url\n\n        # Get POST body as dict\n        params = request.form.to_dict()\n\n        # Get signature from header\n        signature = request.headers.get(\"X-Twilio-Signature\", \"\")\n\n        if not validator.validate(url, params, signature):\n            logger.warning(f\"Invalid Twilio signature from {request.remote_addr}\")\n            abort(403)\n\n        return f(*args, **kwargs)\n    return wrapper\n\n@app.route(\"\u002Fwebhooks\u002Ftwilio\u002Fsms\u002Fstatus\", methods=[\"POST\"])\n@validate_twilio_signature\ndef sms_status_callback():\n    \"\"\"\n    Handle SMS delivery status updates.\n\n    Status progression: queued → sending → sent → delivered\n    Or: queued → sending → undelivered\u002Ffailed\n    \"\"\"\n    message_sid = request.form.get(\"MessageSid\")\n    status = request.form.get(\"MessageStatus\")\n    error_code = request.form.get(\"ErrorCode\")\n    error_message = request.form.get(\"ErrorMessage\")\n\n    logger.info(f\"SMS {message_sid}: {status}\")\n\n    if status == \"delivered\":\n        # Message successfully delivered\n        update_message_status(message_sid, \"delivered\")\n\n    elif status == \"undelivered\":\n        # Carrier rejected or other failure\n        logger.error(f\"SMS failed: {error_code} - {error_message}\")\n        handle_failed_message(message_sid, error_code, error_message)\n\n    elif status == \"failed\":\n        # Twilio couldn't send\n        logger.error(f\"SMS send failed: {error_code}\")\n        handle_failed_message(message_sid, error_code, error_message)\n\n    return \"\", 200\n\n@app.route(\"\u002Fwebhooks\u002Ftwilio\u002Fsms\u002Fincoming\", methods=[\"POST\"])\n@validate_twilio_signature\ndef incoming_sms():\n    \"\"\"\n    Handle incoming SMS messages.\n    \"\"\"\n    from_number = request.form.get(\"From\")\n    to_number = request.form.get(\"To\")\n    body = request.form.get(\"Body\")\n    num_media = int(request.form.get(\"NumMedia\", 0))\n\n    # Handle media attachments\n    media_urls = []\n    for i in range(num_media):\n        media_urls.append(request.form.get(f\"MediaUrl{i}\"))\n\n    # Check for opt-out keywords\n    if body.strip().upper() in [\"STOP\", \"UNSUBSCRIBE\", \"CANCEL\"]:\n        handle_opt_out(from_number)\n        return \"\", 200\n\n    # Check for opt-in keywords\n    if body.strip().upper() in [\"START\", \"SUBSCRIBE\"]:\n        handle_opt_in(from_number)\n        return \"\", 200\n\n    # Process message\n    process_incoming_sms(from_number, body, media_urls)\n\n    return \"\", 200\n\n@app.route(\"\u002Fwebhooks\u002Ftwilio\u002Fvoice\u002Fstatus\", methods=[\"POST\"])\n@validate_twilio_signature\ndef voice_status_callback():\n    \"\"\"Handle call status updates.\"\"\"\n    call_sid = request.form.get(\"CallSid\")\n    status = request.form.get(\"CallStatus\")\n    duration = request.form.get(\"CallDuration\")\n    direction = request.form.get(\"Direction\")\n\n    # Call statuses: initiated, ringing, in-progress, completed, busy, no-answer, canceled, failed\n\n    logger.info(f\"Call {call_sid}: {status} ({duration}s)\")\n\n    if status == \"completed\":\n        # Call ended normally\n        log_call_completion(call_sid, duration)\n\n    elif status in [\"busy\", \"no-answer\", \"canceled\", \"failed\"]:\n        # Call didn't connect\n        handle_failed_call(call_sid, status)\n\n    return \"\", 200\n\n# Helper functions\ndef update_message_status(message_sid: str, status: str):\n    \"\"\"Update message status in database.\"\"\"\n    pass\n\ndef handle_failed_message(message_sid: str, error_code: str, error_msg: str):\n    \"\"\"Handle failed message delivery.\"\"\"\n    # Notify team, retry logic, etc.\n    pass\n\ndef handle_opt_out(phone: str):\n    \"\"\"Handle user opting out of messages.\"\"\"\n    # Mark user as opted out in database\n    # IMPORTANT: Must respect this!\n    pass\n\ndef handle_opt_in(phone: str):\n    \"\"\"Handle user opting back in.\"\"\"\n    pass\n\ndef process_incoming_sms(from_phone: str, body: str, media: list):\n    \"\"\"Process incoming SMS message.\"\"\"\n    pass\n\ndef log_call_completion(call_sid: str, duration: str):\n    \"\"\"Log completed call.\"\"\"\n    pass\n\ndef handle_failed_call(call_sid: str, status: str):\n    \"\"\"Handle call that didn't connect.\"\"\"\n    pass\n\n### Anti_patterns\n\n- Not validating X-Twilio-Signature\n- Exposing webhook URLs without authentication\n- Not handling opt-out keywords (STOP)\n- Blocking webhook response (should be fast)\n\n### Rate Limit and Retry Pattern\n\nHandle Twilio rate limits and implement proper retry logic.\n\nDefault limits:\n- SMS: 80 messages per second (MPS)\n- Voice: Varies by number type and region\n- API calls: 100 requests per second\n\nError codes:\n- 20429: Voice API rate limit\n- 30429: Messaging API rate limit\n\n**When to use**: High-volume messaging applications,Bulk SMS campaigns,Automated calling systems\n\nimport time\nimport random\nfrom functools import wraps\nfrom twilio.base.exceptions import TwilioRestException\nimport logging\n\nlogger = logging.getLogger(__name__)\n\ndef exponential_backoff_retry(\n    max_retries: int = 5,\n    base_delay: float = 1.0,\n    max_delay: float = 60.0,\n    rate_limit_codes: list = [20429, 30429]\n):\n    \"\"\"\n    Decorator for exponential backoff retry on rate limits.\n\n    Uses jitter to prevent thundering herd.\n    \"\"\"\n    def decorator(func):\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            last_exception = None\n\n            for attempt in range(max_retries + 1):\n                try:\n                    return func(*args, **kwargs)\n\n                except TwilioRestException as e:\n                    last_exception = e\n\n                    # Only retry on rate limit errors\n                    if e.code not in rate_limit_codes:\n                        raise\n\n                    if attempt == max_retries:\n                        logger.error(f\"Max retries exceeded: {e}\")\n                        raise\n\n                    # Calculate delay with jitter\n                    delay = min(\n                        base_delay * (2 ** attempt) + random.uniform(0, 1),\n                        max_delay\n                    )\n\n                    logger.warning(\n                        f\"Rate limited (attempt {attempt + 1}\u002F{max_retries}). \"\n                        f\"Retrying in {delay:.1f}s\"\n                    )\n                    time.sleep(delay)\n\n            raise last_exception\n\n        return wrapper\n    return decorator\n\n# Usage\nfrom twilio.rest import Client\n\nclient = Client(account_sid, auth_token)\n\n@exponential_backoff_retry(max_retries=5)\ndef send_sms(to: str, body: str):\n    return client.messages.create(\n        to=to,\n        from_=from_number,\n        body=body\n    )\n\n# Bulk sending with rate limiting\nimport asyncio\nfrom asyncio import Semaphore\n\nclass RateLimitedSender:\n    \"\"\"\n    Send messages with built-in rate limiting.\n    Stays under Twilio's 80 MPS limit.\n    \"\"\"\n\n    def __init__(self, client, from_number: str, mps: int = 50):\n        self.client = client\n        self.from_number = from_number\n        self.mps = mps\n        self.semaphore = Semaphore(mps)\n\n    async def send_bulk(self, messages: list[dict]) -> list[dict]:\n        \"\"\"\n        Send messages with rate limiting.\n\n        Args:\n            messages: List of {\"to\": \"+1...\", \"body\": \"...\"}\n\n        Returns:\n            Results for each message\n        \"\"\"\n        tasks = [\n            self._send_with_limit(msg[\"to\"], msg[\"body\"])\n            for msg in messages\n        ]\n\n        return await asyncio.gather(*tasks, return_exceptions=True)\n\n    async def _send_with_limit(self, to: str, body: str):\n        \"\"\"Send single message with semaphore-based rate limit.\"\"\"\n        async with self.semaphore:\n            try:\n                # Use sync client in thread pool\n                loop = asyncio.get_event_loop()\n                result = await loop.run_in_executor(\n                    None,\n                    lambda: self.client.messages.create(\n                        to=to,\n                        from_=self.from_number,\n                        body=body\n                    )\n                )\n                return {\"success\": True, \"sid\": result.sid, \"to\": to}\n\n            except TwilioRestException as e:\n                return {\"success\": False, \"error\": str(e), \"to\": to}\n\n            finally:\n                # Delay to maintain rate limit\n                await asyncio.sleep(1 \u002F self.mps)\n\n# Usage\nasync def send_campaign():\n    sender = RateLimitedSender(client, from_number, mps=50)\n\n    messages = [\n        {\"to\": \"+14155551234\", \"body\": \"Hello!\"},\n        {\"to\": \"+14155555678\", \"body\": \"Hello!\"},\n        # ... thousands of messages\n    ]\n\n    results = await sender.send_bulk(messages)\n\n    successful = sum(1 for r in results if r.get(\"success\"))\n    print(f\"Sent {successful}\u002F{len(messages)} messages\")\n\n### Anti_patterns\n\n- Retrying immediately without backoff\n- No jitter causing thundering herd\n- Retrying non-rate-limit errors\n- Exceeding Twilio's MPS limit\n\n## Sharp Edges\n\n### Sending to Users Who Opted Out (Error 21610)\n\nSeverity: HIGH\n\nSituation: Sending SMS to a phone number\n\nSymptoms:\nMessage fails with error code 21610. Twilio rejects the message.\nUser never receives the SMS. Same number worked before.\n\nWhy this breaks:\nThe recipient replied \"STOP\" (or UNSUBSCRIBE, CANCEL, etc.) to a previous\nmessage from your number. Twilio automatically honors opt-outs and blocks\nfurther messages to that number from your account.\n\nThis is legally required for US messaging (TCPA, CTIA guidelines).\nYou cannot override this - the user must reply \"START\" to opt back in.\n\nRecommended fix:\n\n## Track opt-out status in your database\n\n```python\n# In your webhook handler\n@app.route(\"\u002Fwebhooks\u002Fsms\u002Fincoming\", methods=[\"POST\"])\ndef incoming_sms():\n    from_number = request.form.get(\"From\")\n    body = request.form.get(\"Body\", \"\").strip().upper()\n\n    # Standard opt-out keywords\n    if body in [\"STOP\", \"UNSUBSCRIBE\", \"CANCEL\", \"END\", \"QUIT\"]:\n        mark_user_opted_out(from_number)\n        return \"\", 200\n\n    # Standard opt-in keywords\n    if body in [\"START\", \"SUBSCRIBE\", \"YES\", \"UNSTOP\"]:\n        mark_user_opted_in(from_number)\n        return \"\", 200\n\n    # Process other messages...\n\n# Before sending\ndef send_sms_safe(to: str, body: str):\n    if is_user_opted_out(to):\n        return {\"success\": False, \"error\": \"User has opted out\"}\n\n    try:\n        return send_sms(to, body)\n    except TwilioRestException as e:\n        if e.code == 21610:\n            # Update database - they opted out via carrier\n            mark_user_opted_out(to)\n        raise\n```\n\n## Include opt-out instructions\nAdd \"Reply STOP to unsubscribe\" to marketing messages.\n\n### Phone Unreachable But Valid (Error 30003)\n\nSeverity: MEDIUM\n\nSituation: Sending SMS to a mobile number\n\nSymptoms:\nMessage fails with error 30003. Number was valid and worked before.\nIntermittent - sometimes works, sometimes fails.\n\nWhy this breaks:\nError 30003 means \"Unreachable destination handset.\" The phone exists but\ncan't receive messages right now. Common causes:\n- Phone powered off\n- Airplane mode\n- Out of signal range\n- Carrier network issues\n- Phone storage full\n\nUnlike 30006 (permanent unreachable), 30003 is usually temporary.\n\nRecommended fix:\n\n## Implement retry logic for transient failures\n\n```python\nTRANSIENT_ERRORS = [30003, 30008, 30009]  # Retriable errors\n\nasync def send_with_retry(to: str, body: str, max_retries: int = 3):\n    for attempt in range(max_retries):\n        result = send_sms(to, body)\n\n        if result[\"success\"]:\n            return result\n\n        if result.get(\"error_code\") not in TRANSIENT_ERRORS:\n            # Don't retry permanent failures\n            return result\n\n        # Exponential backoff: 5min, 15min, 45min\n        delay = 300 * (3 ** attempt)\n        await asyncio.sleep(delay)\n\n    return {\"success\": False, \"error\": \"Max retries exceeded\"}\n```\n\n## Provide fallback channel\n\n```python\nasync def notify_user(user, message):\n    # Try SMS first\n    result = await send_sms(user.phone, message)\n\n    if result.get(\"error_code\") == 30003:\n        # Phone unreachable - try email\n        await send_email(user.email, message)\n        return {\"channel\": \"email\", \"status\": \"sent\"}\n\n    return {\"channel\": \"sms\", \"status\": result[\"status\"]}\n```\n\n### Messages Blocked by Carrier Filtering\n\nSeverity: HIGH\n\nSituation: Sending SMS to US phone numbers\n\nSymptoms:\nMessages show as \"sent\" but never \"delivered.\" No error from Twilio.\nUsers say they never received the message. Pattern in specific carriers\nor message content.\n\nWhy this breaks:\nUS carriers (Verizon, AT&T, T-Mobile) aggressively filter SMS for spam.\nYour message might be blocked if:\n- Contains URLs (especially short URLs or unknown domains)\n- Looks like phishing (urgent, account, verify, click now)\n- High volume from same number\n- Not using registered A2P 10DLC\n- Low sender reputation\n\nCarriers don't tell Twilio why messages are filtered - they just\nsilently drop them.\n\nRecommended fix:\n\n## Register for A2P 10DLC (US requirement)\n\n```\n1. Go to Twilio Console > Messaging > Trust Hub\n2. Register your business brand\n3. Create a messaging campaign (describes use case)\n4. Wait for approval (can take days)\n5. Associate phone numbers with campaign\n```\n\n## Message content best practices\n\n```python\ndef sanitize_message(text: str) -> str:\n    \"\"\"Make message less likely to be filtered.\"\"\"\n    # Avoid URL shorteners - use full domain\n    # Avoid spam trigger words\n    # Keep it conversational, not promotional\n\n    # Example: Instead of this\n    bad = \"URGENT: Verify your account now! Click: bit.ly\u002Fabc\"\n\n    # Do this\n    good = \"Hi! Your order #1234 is ready. Questions? Reply here.\"\n\n    return text\n\n# Use toll-free or short code for high volume\n# 10DLC is for \u003C10K msg\u002Fday\n# Toll-free: up to 10K msg\u002Fday\n# Short code: 100K+ msg\u002Fday\n```\n\n## Monitor delivery rates\n\n```python\ndef track_delivery_rate():\n    sent = get_messages_with_status(\"sent\")\n    delivered = get_messages_with_status(\"delivered\")\n\n    rate = len(delivered) \u002F len(sent) * 100\n\n    if rate \u003C 95:\n        alert_team(f\"Delivery rate dropped to {rate}%\")\n```\n\n### Not Validating Webhook Signatures\n\nSeverity: CRITICAL\n\nSituation: Receiving Twilio webhook callbacks\n\nSymptoms:\nAttackers send fake webhooks to your endpoint. Fraudulent transactions\nprocessed. Spoofed incoming messages trigger actions.\n\nWhy this breaks:\nTwilio signs all webhook requests with X-Twilio-Signature header.\nIf you don't validate this, anyone who knows your webhook URL can\nsend fake requests pretending to be Twilio.\n\nThis can lead to:\n- Fake message delivery confirmations\n- Spoofed incoming messages\n- Fraudulent verification approvals\n\nRecommended fix:\n\n## ALWAYS validate the signature\n\n```python\nfrom twilio.request_validator import RequestValidator\nfrom flask import Flask, request, abort\nfrom functools import wraps\nimport os\n\ndef require_twilio_signature(f):\n    \"\"\"Decorator to validate Twilio webhook requests.\"\"\"\n    @wraps(f)\n    def wrapper(*args, **kwargs):\n        validator = RequestValidator(os.environ[\"TWILIO_AUTH_TOKEN\"])\n\n        # Full URL including query string\n        url = request.url\n\n        # POST body as dict\n        params = request.form.to_dict()\n\n        # Signature header\n        signature = request.headers.get(\"X-Twilio-Signature\", \"\")\n\n        if not validator.validate(url, params, signature):\n            abort(403)\n\n        return f(*args, **kwargs)\n    return wrapper\n\n@app.route(\"\u002Fwebhooks\u002Ftwilio\", methods=[\"POST\"])\n@require_twilio_signature  # ALWAYS use this\ndef twilio_webhook():\n    # Safe to process\n    pass\n```\n\n## Common validation gotchas\n\n```python\n# URL must match EXACTLY what Twilio called\n# If behind proxy, you might need:\nurl = request.headers.get(\"X-Forwarded-Proto\", \"http\") + \":\u002F\u002F\" + \\\n      request.headers.get(\"X-Forwarded-Host\", request.host) + \\\n      request.path\n\n# If using ngrok, URL changes each restart\n# Use consistent URL in production\n```\n\n### WhatsApp Message Outside 24-Hour Window (Error 63016)\n\nSeverity: HIGH\n\nSituation: Sending WhatsApp message to a user\n\nSymptoms:\nMessage fails with error 63016. \"Message is outside the allowed window.\"\nTemplate messages work, but regular messages fail.\n\nWhy this breaks:\nWhatsApp has strict rules about unsolicited messages:\n- Users must message you first\n- You can only reply within 24 hours of their last message\n- After 24 hours, you must use pre-approved template messages\n\nThis prevents spam and maintains WhatsApp's trust as a platform.\n\nRecommended fix:\n\n## Track session windows per user\n\n```python\nfrom datetime import datetime, timedelta\n\nclass WhatsAppSession:\n    def __init__(self, redis_client):\n        self.redis = redis_client\n        self.window_hours = 24\n\n    def start_session(self, phone: str):\n        \"\"\"Start\u002Frefresh 24-hour session on incoming message.\"\"\"\n        key = f\"wa_session:{phone}\"\n        expires = datetime.now() + timedelta(hours=self.window_hours)\n        self.redis.set(key, expires.isoformat(), ex=self.window_hours * 3600)\n\n    def can_send_freeform(self, phone: str) -> bool:\n        \"\"\"Check if we can send non-template message.\"\"\"\n        key = f\"wa_session:{phone}\"\n        expires_str = self.redis.get(key)\n\n        if not expires_str:\n            return False\n\n        expires = datetime.fromisoformat(expires_str)\n        return datetime.now() \u003C expires\n\n    def send_message(self, phone: str, body: str, template_sid: str = None):\n        \"\"\"Send message, using template if outside window.\"\"\"\n        if self.can_send_freeform(phone):\n            return send_whatsapp_message(phone, body)\n        elif template_sid:\n            return send_whatsapp_template(phone, template_sid)\n        else:\n            return {\n                \"success\": False,\n                \"error\": \"Outside session window, template required\"\n            }\n```\n\n## Incoming message webhook\n\n```python\n@app.route(\"\u002Fwebhooks\u002Fwhatsapp\", methods=[\"POST\"])\ndef whatsapp_incoming():\n    from_phone = request.form.get(\"From\").replace(\"whatsapp:\", \"\")\n\n    # Start\u002Frefresh session\n    session.start_session(from_phone)\n\n    # Process message...\n```\n\n## Create approved templates for common messages\n\n```\n1. Twilio Console > Content Template Builder\n2. Create template with {{1}} placeholders\n3. Submit for WhatsApp approval (takes 24-48 hours)\n4. Use content_sid to send\n```\n\n### Exposed Account SID or Auth Token\n\nSeverity: CRITICAL\n\nSituation: Deploying Twilio integration\n\nSymptoms:\nUnauthorized charges on Twilio account. Messages sent you didn't send.\nPhone numbers purchased without authorization.\n\nWhy this breaks:\nIf attackers get your Account SID + Auth Token, they have FULL access\nto your Twilio account. They can:\n- Send messages (charging your account)\n- Buy phone numbers\n- Access call recordings\n- Modify your configuration\n\nCommon exposure points:\n- Hardcoded in source code (pushed to GitHub)\n- In client-side JavaScript\n- In Docker images\n- In logs\n\nRecommended fix:\n\n## Never hardcode credentials\n\n```python\n# BAD - never do this\nclient = Client(\"AC1234...\", \"abc123...\")\n\n# GOOD - environment variables\nclient = Client(\n    os.environ[\"TWILIO_ACCOUNT_SID\"],\n    os.environ[\"TWILIO_AUTH_TOKEN\"]\n)\n\n# GOOD - secrets manager\nfrom aws_secretsmanager import get_secret\ncreds = get_secret(\"twilio-credentials\")\nclient = Client(creds[\"sid\"], creds[\"token\"])\n```\n\n## Use API Key instead of Auth Token\n\n```python\n# Auth Token has full account access\n# API Keys can be scoped and revoked\n\n# Create API Key in Twilio Console\nclient = Client(\n    os.environ[\"TWILIO_API_KEY_SID\"],\n    os.environ[\"TWILIO_API_KEY_SECRET\"],\n    os.environ[\"TWILIO_ACCOUNT_SID\"]\n)\n\n# If compromised, revoke just that key\n```\n\n## Rotate tokens immediately if exposed\n\n```\n1. Twilio Console > Account > API credentials\n2. Rotate Auth Token\n3. Update all deployments with new token\n4. Review account activity for unauthorized use\n```\n\n### Verify Rate Limit Exceeded (Error 60203)\n\nSeverity: MEDIUM\n\nSituation: Sending verification codes\n\nSymptoms:\nVerification request fails with error 60203.\n\"Max send attempts reached for this phone number.\"\n\nWhy this breaks:\nTwilio Verify has built-in rate limits to prevent abuse:\n- 5 verification attempts per phone number per service per 10 minutes\n- Helps prevent SMS pumping fraud\n- Protects against brute-force attacks\n\nIf users legitimately need more attempts, you may have UX issues.\n\nRecommended fix:\n\n## Implement application-level rate limiting too\n\n```python\nfrom datetime import datetime, timedelta\nimport redis\n\nclass VerifyRateLimiter:\n    def __init__(self, redis_client):\n        self.redis = redis_client\n        # Stricter than Twilio's limit\n        self.max_attempts = 3\n        self.window_minutes = 10\n\n    def can_request(self, phone: str) -> bool:\n        key = f\"verify_rate:{phone}\"\n        attempts = self.redis.get(key)\n\n        if attempts and int(attempts) >= self.max_attempts:\n            return False\n\n        return True\n\n    def record_attempt(self, phone: str):\n        key = f\"verify_rate:{phone}\"\n        pipe = self.redis.pipeline()\n        pipe.incr(key)\n        pipe.expire(key, self.window_minutes * 60)\n        pipe.execute()\n\n    def get_wait_time(self, phone: str) -> int:\n        \"\"\"Return seconds until user can request again.\"\"\"\n        key = f\"verify_rate:{phone}\"\n        ttl = self.redis.ttl(key)\n        return max(0, ttl)\n\n# Usage\nlimiter = VerifyRateLimiter(redis_client)\n\n@app.route(\"\u002Fverify\u002Fsend\", methods=[\"POST\"])\ndef send_verification():\n    phone = request.json[\"phone\"]\n\n    if not limiter.can_request(phone):\n        wait = limiter.get_wait_time(phone)\n        return {\n            \"error\": f\"Too many attempts. Try again in {wait} seconds.\"\n        }, 429\n\n    result = twilio_verify.send_verification(phone)\n\n    if result[\"success\"]:\n        limiter.record_attempt(phone)\n\n    return result\n```\n\n## Provide clear user feedback\n\n```python\n# Show remaining attempts\n# Show countdown timer\n# Offer alternative (voice call, email)\n```\n\n## Validation Checks\n\n### Hardcoded Twilio Credentials\n\nSeverity: ERROR\n\nTwilio credentials must never be hardcoded\n\nMessage: Hardcoded Twilio SID detected. Use environment variables.\n\n### Auth Token in Source Code\n\nSeverity: ERROR\n\nAuth tokens should be in environment variables\n\nMessage: Hardcoded auth token. Use os.environ['TWILIO_AUTH_TOKEN'].\n\n### Webhook Without Signature Validation\n\nSeverity: ERROR\n\nTwilio webhooks must validate X-Twilio-Signature\n\nMessage: Webhook without signature validation. Add RequestValidator check.\n\n### Twilio Credentials in Client-Side Code\n\nSeverity: ERROR\n\nNever expose Twilio credentials to browsers\n\nMessage: Twilio credentials exposed client-side. Only use server-side.\n\n### No E.164 Phone Number Validation\n\nSeverity: WARNING\n\nPhone numbers should be validated before sending\n\nMessage: Sending to phone without E.164 validation.\n\n### Hardcoded Phone Numbers\n\nSeverity: WARNING\n\nPhone numbers should come from config or database\n\nMessage: Hardcoded phone number. Use config or environment variable.\n\n### No Twilio Exception Handling\n\nSeverity: WARNING\n\nTwilio calls should handle TwilioRestException\n\nMessage: Twilio API call without error handling. Catch TwilioRestException.\n\n### Not Handling Specific Error Codes\n\nSeverity: INFO\n\nHandle common Twilio error codes specifically\n\nMessage: Consider handling specific error codes (21610, 30003, etc.).\n\n### No Opt-Out Keyword Handling\n\nSeverity: WARNING\n\nSMS systems must handle STOP\u002FUNSUBSCRIBE keywords\n\nMessage: No opt-out handling. Check for STOP\u002FUNSUBSCRIBE keywords.\n\n### Not Checking Opt-Out Before Sending\n\nSeverity: WARNING\n\nCheck if user has opted out before sending SMS\n\nMessage: Consider checking opt-out status before sending.\n\n## Collaboration\n\n### Delegation Triggers\n\n- user needs AI voice assistant -> voice-agents (Twilio provides telephony, voice-agents skill for AI conversation)\n- user needs Slack notifications -> slack-bot-builder (Integrate SMS alerts with Slack notifications)\n- user needs full auth system -> auth-specialist (Twilio Verify is one component of broader auth)\n- user needs workflow automation -> workflow-automation (Trigger SMS\u002Fcalls from automated workflows)\n- user needs high-volume messaging -> devops (Scale webhooks, monitor delivery rates)\n\n## When to Use\n- User mentions or implies: twilio\n- User mentions or implies: send SMS\n- User mentions or implies: text message\n- User mentions or implies: voice call\n- User mentions or implies: phone verification\n- User mentions or implies: 2FA SMS\n- User mentions or implies: WhatsApp API\n- User mentions or implies: programmable messaging\n- User mentions or implies: IVR system\n- User mentions or implies: TwiML\n- User mentions or implies: phone number verification\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n","# Twilio Communications\n\nBuild communication features with Twilio: SMS messaging, voice calls,\nWhatsApp Business API, and user verification (2FA). Covers the full\nspectrum from simple notifications to complex IVR systems and multi-channel\nauthentication. Critical focus on compliance, rate limits, and error handling.\n\n## Patterns\n\n### SMS Sending Pattern\n\nBasic pattern for sending SMS messages with Twilio.\nHandles the fundamentals: phone number formatting, message delivery,\nand delivery status callbacks.\n\nKey considerations:\n- Phone numbers must be in E.164 format (+1234567890)\n- Default rate limit: 80 messages per second (MPS)\n- Messages over 160 characters are split (and cost more)\n- Carrier filtering can block messages (especially to US numbers)\n\n**When to use**: Sending notifications to users,Transactional messages (order confirmations, shipping),Alerts and reminders\n\nfrom twilio.rest import Client\nfrom twilio.base.exceptions import TwilioRestException\nimport os\nimport re\n\nclass TwilioSMS:\n    \"\"\"\n    SMS sending with proper error handling and validation.\n    \"\"\"\n\n    def __init__(self):\n        self.client = Client(\n            os.environ[\"TWILIO_ACCOUNT_SID\"],\n            os.environ[\"TWILIO_AUTH_TOKEN\"]\n        )\n        self.from_number = os.environ[\"TWILIO_PHONE_NUMBER\"]\n\n    def validate_e164(self, phone: str) -> bool:\n        \"\"\"Validate phone number is in E.164 format.\"\"\"\n        pattern = r'^\\+[1-9]\\d{1,14}$'\n        return bool(re.match(pattern, phone))\n\n    def send_sms(\n        self,\n        to: str,\n        body: str,\n        status_callback: str = None\n    ) -> dict:\n        \"\"\"\n        Send an SMS message.\n\n        Args:\n            to: Recipient phone number in E.164 format\n            body: Message text (160 chars = 1 segment)\n            status_callback: URL for delivery status webhooks\n\n        Returns:\n            Message SID and status\n        \"\"\"\n        # Validate phone number format\n        if not self.validate_e164(to):\n            return {\n                \"success\": False,\n                \"error\": \"Phone number must be in E.164 format (+1234567890)\"\n            }\n\n        # Check message length (warn about segmentation)\n        segment_count = (len(body) + 159) \u002F\u002F 160\n        if segment_count > 1:\n            print(f\"Warning: Message will be sent as {segment_count} segments\")\n\n        try:\n            message = self.client.messages.create(\n                to=to,\n                from_=self.from_number,\n                body=body,\n                status_callback=status_callback\n            )\n\n            return {\n                \"success\": True,\n                \"message_sid\": message.sid,\n                \"status\": message.status,\n                \"segments\": segment_count\n            }\n\n        except TwilioRestException as e:\n            return self._handle_error(e)\n\n    def _handle_error(self, error: TwilioRestException) -> dict:\n        \"\"\"Handle Twilio-specific errors.\"\"\"\n        error_handlers = {\n            21610: \"Recipient has opted out. They must reply START.\",\n            21614: \"Invalid 'To' phone number format.\",\n            21211: \"'From' phone number is not valid.\",\n            30003: \"Phone is unreachable (off, airplane mode, no signal).\",\n            30005: \"Unknown destination (invalid number or landline).\",\n            30006: \"Landline or unreachable carrier.\",\n            30429: \"Rate limit exceeded. Implement exponential backoff.\",\n        }\n\n        return {\n            \"success\": False,\n            \"error_code\": error.code,\n            \"error\": error_handlers.get(error.code, error.msg),\n            \"details\": str(error)\n        }\n\n# Usage\nsms = TwilioSMS()\nresult = sms.send_sms(\n    to=\"+14155551234\",\n    body=\"Your order #1234 has shipped!\",\n    status_callback=\"https:\u002F\u002Fyour-app.com\u002Fwebhooks\u002Ftwilio\u002Fstatus\"\n)\n\n### Anti_patterns\n\n- Not validating E.164 format before sending\n- Hardcoding Twilio credentials in code\n- Ignoring delivery status callbacks\n- Not handling the opted-out (21610) error\n\n### Twilio Verify Pattern (2FA\u002FOTP)\n\nUse Twilio Verify for phone number verification and 2FA.\nHandles code generation, delivery, rate limiting, and fraud prevention.\n\nKey benefits over DIY OTP:\n- Twilio manages code generation and expiration\n- Built-in fraud prevention (saved customers $82M+ blocking 747M attempts)\n- Handles rate limiting automatically\n- Multi-channel: SMS, Voice, Email, Push, WhatsApp\n\nGoogle found SMS 2FA blocks \"100% of automated bots, 96% of bulk\nphishing attacks, and 76% of targeted attacks.\"\n\n**When to use**: User phone number verification at signup,Two-factor authentication (2FA),Password reset verification,High-value transaction confirmation\n\nfrom twilio.rest import Client\nfrom twilio.base.exceptions import TwilioRestException\nimport os\nfrom enum import Enum\nfrom typing import Optional\n\nclass VerifyChannel(Enum):\n    SMS = \"sms\"\n    CALL = \"call\"\n    EMAIL = \"email\"\n    WHATSAPP = \"whatsapp\"\n\nclass TwilioVerify:\n    \"\"\"\n    Phone verification with Twilio Verify.\n    Never store OTP codes - Twilio handles it.\n    \"\"\"\n\n    def __init__(self, verify_service_sid: str = None):\n        self.client = Client(\n            os.environ[\"TWILIO_ACCOUNT_SID\"],\n            os.environ[\"TWILIO_AUTH_TOKEN\"]\n        )\n        # Create a Verify Service in Twilio Console first\n        self.service_sid = verify_service_sid or os.environ[\"TWILIO_VERIFY_SID\"]\n\n    def send_verification(\n        self,\n        to: str,\n        channel: VerifyChannel = VerifyChannel.SMS,\n        locale: str = \"en\"\n    ) -> dict:\n        \"\"\"\n        Send verification code to phone\u002Femail.\n\n        Args:\n            to: Phone number (E.164) or email\n            channel: SMS, call, email, or whatsapp\n            locale: Language code for message\n\n        Returns:\n            Verification status\n        \"\"\"\n        try:\n            verification = self.client.verify \\\n                .v2 \\\n                .services(self.service_sid) \\\n                .verifications \\\n                .create(\n                    to=to,\n                    channel=channel.value,\n                    locale=locale\n                )\n\n            return {\n                \"success\": True,\n                \"status\": verification.status,  # \"pending\"\n                \"channel\": channel.value,\n                \"valid\": verification.valid\n            }\n\n        except TwilioRestException as e:\n            return self._handle_verify_error(e)\n\n    def check_verification(self, to: str, code: str) -> dict:\n        \"\"\"\n        Check if verification code is correct.\n\n        Args:\n            to: Phone number or email that received code\n            code: The code entered by user\n\n        Returns:\n            Verification result\n        \"\"\"\n        try:\n            check = self.client.verify \\\n                .v2 \\\n                .services(self.service_sid) \\\n                .verification_checks \\\n                .create(\n                    to=to,\n                    code=code\n                )\n\n            return {\n                \"success\": True,\n                \"valid\": check.status == \"approved\",\n                \"status\": check.status  # \"approved\" or \"pending\"\n            }\n\n        except TwilioRestException as e:\n            # Code was wrong or expired\n            return {\n                \"success\": False,\n                \"valid\": False,\n                \"error\": str(e)\n            }\n\n    def _handle_verify_error(self, error: TwilioRestException) -> dict:\n        \"\"\"Handle Verify-specific errors.\"\"\"\n        error_handlers = {\n            60200: \"Invalid phone number format\",\n            60203: \"Max send attempts reached for this number\",\n            60205: \"Service not found - check VERIFY_SID\",\n            60223: \"Failed to create verification - carrier rejected\",\n        }\n\n        return {\n            \"success\": False,\n            \"error_code\": error.code,\n            \"error\": error_handlers.get(error.code, error.msg)\n        }\n\n# Usage Example - Signup Flow\nverify = TwilioVerify()\n\n# Step 1: User enters phone number\nresult = verify.send_verification(\"+14155551234\", VerifyChannel.SMS)\nif result[\"success\"]:\n    print(\"Code sent! Check your phone.\")\n\n# Step 2: User enters the code they received\ncode = \"123456\"  # From user input\ncheck = verify.check_verification(\"+14155551234\", code)\n\nif check[\"valid\"]:\n    print(\"Phone verified! Create account.\")\nelse:\n    print(\"Invalid code. Try again.\")\n\n# Best Practice: Offer voice fallback\nasync def verify_with_fallback(phone: str, max_attempts: int = 3):\n    \"\"\"Verify with voice fallback if SMS fails.\"\"\"\n    for attempt in range(max_attempts):\n        channel = VerifyChannel.SMS if attempt == 0 else VerifyChannel.CALL\n        result = verify.send_verification(phone, channel)\n\n        if result[\"success\"]:\n            return result\n\n        # If SMS failed, wait and try voice\n        if channel == VerifyChannel.SMS:\n            await asyncio.sleep(30)\n            continue\n\n    return {\"success\": False, \"error\": \"All verification attempts failed\"}\n\n### Anti_patterns\n\n- Storing OTP codes in your database (Twilio handles this)\n- Not implementing rate limiting on your verify endpoint\n- Using same-code retries (let Verify generate new codes)\n- No fallback channel when SMS fails\n\n### TwiML IVR Pattern\n\nBuild Interactive Voice Response (IVR) systems using TwiML.\nTwiML (Twilio Markup Language) is XML that tells Twilio what to do\nwhen receiving calls.\n\nCore TwiML verbs:\n- \u003CSay>: Text-to-speech\n- \u003CPlay>: Play audio file\n- \u003CGather>: Collect keypad\u002Fspeech input\n- \u003CDial>: Connect to another number\n- \u003CRecord>: Record caller's voice\n- \u003CRedirect>: Move to another TwiML endpoint\n\nKey insight: Twilio makes HTTP request to your webhook, you return\nTwiML, Twilio executes it. Stateless, so use URL params or sessions.\n\n**When to use**: Phone menu systems (press 1 for sales...),Automated customer support,Appointment reminders with confirmation,Voicemail systems\n\nfrom flask import Flask, request, Response\nfrom twilio.twiml.voice_response import VoiceResponse, Gather\nfrom twilio.request_validator import RequestValidator\nimport os\n\napp = Flask(__name__)\n\ndef validate_twilio_request(f):\n    \"\"\"Decorator to validate requests are from Twilio.\"\"\"\n    def wrapper(*args, **kwargs):\n        validator = RequestValidator(os.environ[\"TWILIO_AUTH_TOKEN\"])\n\n        # Get request details\n        url = request.url\n        params = request.form.to_dict()\n        signature = request.headers.get(\"X-Twilio-Signature\", \"\")\n\n        if not validator.validate(url, params, signature):\n            return \"Invalid request\", 403\n\n        return f(*args, **kwargs)\n    wrapper.__name__ = f.__name__\n    return wrapper\n\n@app.route(\"\u002Fvoice\u002Fincoming\", methods=[\"POST\"])\n@validate_twilio_request\ndef incoming_call():\n    \"\"\"Handle incoming call with IVR menu.\"\"\"\n    response = VoiceResponse()\n\n    # Gather digits with timeout\n    gather = Gather(\n        num_digits=1,\n        action=\"\u002Fvoice\u002Fmenu-selection\",\n        method=\"POST\",\n        timeout=5\n    )\n    gather.say(\n        \"Welcome to Acme Corp. \"\n        \"Press 1 for sales. \"\n        \"Press 2 for support. \"\n        \"Press 3 to leave a message.\"\n    )\n    response.append(gather)\n\n    # If no input, repeat\n    response.redirect(\"\u002Fvoice\u002Fincoming\")\n\n    return Response(str(response), mimetype=\"text\u002Fxml\")\n\n@app.route(\"\u002Fvoice\u002Fmenu-selection\", methods=[\"POST\"])\n@validate_twilio_request\ndef menu_selection():\n    \"\"\"Route based on menu selection.\"\"\"\n    response = VoiceResponse()\n    digit = request.form.get(\"Digits\", \"\")\n\n    if digit == \"1\":\n        # Transfer to sales\n        response.say(\"Connecting you to sales.\")\n        response.dial(os.environ[\"SALES_PHONE\"])\n\n    elif digit == \"2\":\n        # Transfer to support\n        response.say(\"Connecting you to support.\")\n        response.dial(os.environ[\"SUPPORT_PHONE\"])\n\n    elif digit == \"3\":\n        # Voicemail\n        response.say(\"Please leave a message after the beep.\")\n        response.record(\n            action=\"\u002Fvoice\u002Fvoicemail-saved\",\n            max_length=120,\n            transcribe=True,\n            transcribe_callback=\"\u002Fvoice\u002Ftranscription\"\n        )\n\n    else:\n        response.say(\"Invalid selection.\")\n        response.redirect(\"\u002Fvoice\u002Fincoming\")\n\n    return Response(str(response), mimetype=\"text\u002Fxml\")\n\n@app.route(\"\u002Fvoice\u002Fvoicemail-saved\", methods=[\"POST\"])\n@validate_twilio_request\ndef voicemail_saved():\n    \"\"\"Handle saved voicemail.\"\"\"\n    response = VoiceResponse()\n\n    recording_url = request.form.get(\"RecordingUrl\")\n    recording_sid = request.form.get(\"RecordingSid\")\n\n    # Save to database, notify team, etc.\n    print(f\"Voicemail saved: {recording_url}\")\n\n    response.say(\"Thank you. Goodbye.\")\n    response.hangup()\n\n    return Response(str(response), mimetype=\"text\u002Fxml\")\n\n@app.route(\"\u002Fvoice\u002Ftranscription\", methods=[\"POST\"])\n@validate_twilio_request\ndef transcription_callback():\n    \"\"\"Handle voicemail transcription.\"\"\"\n    transcription = request.form.get(\"TranscriptionText\")\n    recording_sid = request.form.get(\"RecordingSid\")\n\n    # Save transcription, send to Slack, etc.\n    print(f\"Transcription: {transcription}\")\n\n    return \"\", 200\n\n# Outbound call example\nfrom twilio.rest import Client\n\ndef make_outbound_call(to: str, message: str):\n    \"\"\"Make outbound call with custom TwiML.\"\"\"\n    client = Client(\n        os.environ[\"TWILIO_ACCOUNT_SID\"],\n        os.environ[\"TWILIO_AUTH_TOKEN\"]\n    )\n\n    # TwiML Bin URL or your endpoint\n    call = client.calls.create(\n        to=to,\n        from_=os.environ[\"TWILIO_PHONE_NUMBER\"],\n        url=\"https:\u002F\u002Fyour-app.com\u002Fvoice\u002Foutbound-message\",\n        status_callback=\"https:\u002F\u002Fyour-app.com\u002Fvoice\u002Fstatus\"\n    )\n\n    return call.sid\n\nif __name__ == \"__main__\":\n    app.run(debug=True)\n\n### Anti_patterns\n\n- Not validating X-Twilio-Signature (security risk)\n- Returning non-XML responses to Twilio\n- Not handling timeout\u002Fno-input cases\n- Hardcoding phone numbers in TwiML\n\n### WhatsApp Business API Pattern\n\nSend and receive WhatsApp messages via Twilio API.\nUses the same Twilio Messages API as SMS with minor changes.\n\nKey WhatsApp rules:\n- 24-hour session window: Can only reply within 24 hours of user message\n- Template messages: Pre-approved templates for outside session window\n- Opt-in required: Users must explicitly consent to receive messages\n- Rate limit: 80 MPS default (up to 400 with approval)\n- Character limits: Non-template 1024 chars, templates ~550 chars\n\n**When to use**: Customer support with rich media,Order notifications with buttons,Marketing messages (with templates),Interactive flows (booking, surveys)\n\nfrom twilio.rest import Client\nfrom twilio.base.exceptions import TwilioRestException\nimport os\nfrom datetime import datetime, timedelta\nfrom typing import Optional\n\nclass TwilioWhatsApp:\n    \"\"\"\n    WhatsApp Business API via Twilio.\n    Handles session windows and template messages.\n    \"\"\"\n\n    def __init__(self):\n        self.client = Client(\n            os.environ[\"TWILIO_ACCOUNT_SID\"],\n            os.environ[\"TWILIO_AUTH_TOKEN\"]\n        )\n        # WhatsApp number format: whatsapp:+14155551234\n        self.from_number = os.environ[\"TWILIO_WHATSAPP_NUMBER\"]\n\n    def send_message(\n        self,\n        to: str,\n        body: str,\n        media_url: Optional[str] = None\n    ) -> dict:\n        \"\"\"\n        Send WhatsApp message within 24-hour session.\n\n        Args:\n            to: Recipient number (E.164, without whatsapp: prefix)\n            body: Message text (max 1024 chars for non-template)\n            media_url: Optional image\u002Fdocument URL\n\n        Returns:\n            Message result\n        \"\"\"\n        # Format for WhatsApp\n        to_whatsapp = f\"whatsapp:{to}\"\n        from_whatsapp = f\"whatsapp:{self.from_number}\"\n\n        try:\n            message_params = {\n                \"to\": to_whatsapp,\n                \"from_\": from_whatsapp,\n                \"body\": body\n            }\n\n            if media_url:\n                message_params[\"media_url\"] = [media_url]\n\n            message = self.client.messages.create(**message_params)\n\n            return {\n                \"success\": True,\n                \"message_sid\": message.sid,\n                \"status\": message.status\n            }\n\n        except TwilioRestException as e:\n            return self._handle_whatsapp_error(e)\n\n    def send_template_message(\n        self,\n        to: str,\n        content_sid: str,\n        content_variables: dict\n    ) -> dict:\n        \"\"\"\n        Send pre-approved template message.\n        Use this for messages outside 24-hour window.\n\n        Content templates must be approved by WhatsApp first.\n        Create them in Twilio Console > Content Template Builder.\n        \"\"\"\n        to_whatsapp = f\"whatsapp:{to}\"\n        from_whatsapp = f\"whatsapp:{self.from_number}\"\n\n        try:\n            message = self.client.messages.create(\n                to=to_whatsapp,\n                from_=from_whatsapp,\n                content_sid=content_sid,\n                content_variables=content_variables\n            )\n\n            return {\n                \"success\": True,\n                \"message_sid\": message.sid,\n                \"template\": True\n            }\n\n        except TwilioRestException as e:\n            return self._handle_whatsapp_error(e)\n\n    def _handle_whatsapp_error(self, error: TwilioRestException) -> dict:\n        \"\"\"Handle WhatsApp-specific errors.\"\"\"\n        error_handlers = {\n            63016: \"Outside 24-hour window. Use template message.\",\n            63018: \"Template not approved or doesn't exist.\",\n            63025: \"Too many template messages sent to this user.\",\n            63038: \"Rate limit exceeded for WhatsApp.\",\n        }\n\n        return {\n            \"success\": False,\n            \"error_code\": error.code,\n            \"error\": error_handlers.get(error.code, error.msg)\n        }\n\n# Flask webhook for incoming WhatsApp messages\nfrom flask import Flask, request\n\napp = Flask(__name__)\n\n@app.route(\"\u002Fwebhooks\u002Fwhatsapp\", methods=[\"POST\"])\ndef whatsapp_webhook():\n    \"\"\"Handle incoming WhatsApp messages.\"\"\"\n    from_number = request.form.get(\"From\", \"\").replace(\"whatsapp:\", \"\")\n    body = request.form.get(\"Body\", \"\")\n    media_url = request.form.get(\"MediaUrl0\")  # First attachment\n\n    # Track session start (24-hour window begins now)\n    session_start = datetime.now()\n    session_expires = session_start + timedelta(hours=24)\n\n    # Store in database for session tracking\n    # user_sessions[from_number] = session_expires\n\n    # Process message and respond\n    response = process_whatsapp_message(from_number, body, media_url)\n\n    # Reply within session\n    whatsapp = TwilioWhatsApp()\n    whatsapp.send_message(from_number, response)\n\n    return \"\", 200\n\ndef process_whatsapp_message(phone: str, text: str, media: str) -> str:\n    \"\"\"Process incoming message and generate response.\"\"\"\n    text_lower = text.lower()\n\n    if \"order status\" in text_lower:\n        return \"Your order #1234 is out for delivery!\"\n    elif \"support\" in text_lower:\n        return \"A support agent will contact you shortly.\"\n    else:\n        return \"Thanks for your message! Reply with 'order status' or 'support'.\"\n\n# Send typing indicator (2025 feature)\ndef send_typing_indicator(to: str):\n    \"\"\"Let user know you're typing.\"\"\"\n    # Requires Senders API setup\n    pass\n\n### Anti_patterns\n\n- Sending non-template messages outside 24-hour window\n- Not tracking session windows per user\n- Exceeding 1024 char limit for session messages\n- Not handling template rejection errors\n\n### Webhook Handler Pattern\n\nHandle Twilio webhooks for delivery status, incoming messages,\nand call events. Critical: always validate X-Twilio-Signature.\n\nTwilio sends webhooks for:\n- Message status updates (queued → sent → delivered\u002Ffailed)\n- Incoming SMS\u002FWhatsApp messages\n- Call events (initiated, ringing, answered, completed)\n- Recording\u002Ftranscription ready\n\n**When to use**: Tracking message delivery status,Receiving incoming messages,Call analytics and logging,Voicemail transcription processing\n\nfrom flask import Flask, request, abort\nfrom twilio.request_validator import RequestValidator\nfrom functools import wraps\nimport os\nimport logging\n\napp = Flask(__name__)\nlogger = logging.getLogger(__name__)\n\ndef validate_twilio_signature(f):\n    \"\"\"\n    Validate that request came from Twilio.\n    CRITICAL: Always use this for webhook endpoints.\n    \"\"\"\n    @wraps(f)\n    def wrapper(*args, **kwargs):\n        validator = RequestValidator(os.environ[\"TWILIO_AUTH_TOKEN\"])\n\n        # Build full URL (including query params)\n        url = request.url\n\n        # Get POST body as dict\n        params = request.form.to_dict()\n\n        # Get signature from header\n        signature = request.headers.get(\"X-Twilio-Signature\", \"\")\n\n        if not validator.validate(url, params, signature):\n            logger.warning(f\"Invalid Twilio signature from {request.remote_addr}\")\n            abort(403)\n\n        return f(*args, **kwargs)\n    return wrapper\n\n@app.route(\"\u002Fwebhooks\u002Ftwilio\u002Fsms\u002Fstatus\", methods=[\"POST\"])\n@validate_twilio_signature\ndef sms_status_callback():\n    \"\"\"\n    Handle SMS delivery status updates.\n\n    Status progression: queued → sending → sent → delivered\n    Or: queued → sending → undelivered\u002Ffailed\n    \"\"\"\n    message_sid = request.form.get(\"MessageSid\")\n    status = request.form.get(\"MessageStatus\")\n    error_code = request.form.get(\"ErrorCode\")\n    error_message = request.form.get(\"ErrorMessage\")\n\n    logger.info(f\"SMS {message_sid}: {status}\")\n\n    if status == \"delivered\":\n        # Message successfully delivered\n        update_message_status(message_sid, \"delivered\")\n\n    elif status == \"undelivered\":\n        # Carrier rejected or other failure\n        logger.error(f\"SMS failed: {error_code} - {error_message}\")\n        handle_failed_message(message_sid, error_code, error_message)\n\n    elif status == \"failed\":\n        # Twilio couldn't send\n        logger.error(f\"SMS send failed: {error_code}\")\n        handle_failed_message(message_sid, error_code, error_message)\n\n    return \"\", 200\n\n@app.route(\"\u002Fwebhooks\u002Ftwilio\u002Fsms\u002Fincoming\", methods=[\"POST\"])\n@validate_twilio_signature\ndef incoming_sms():\n    \"\"\"\n    Handle incoming SMS messages.\n    \"\"\"\n    from_number = request.form.get(\"From\")\n    to_number = request.form.get(\"To\")\n    body = request.form.get(\"Body\")\n    num_media = int(request.form.get(\"NumMedia\", 0))\n\n    # Handle media attachments\n    media_urls = []\n    for i in range(num_media):\n        media_urls.append(request.form.get(f\"MediaUrl{i}\"))\n\n    # Check for opt-out keywords\n    if body.strip().upper() in [\"STOP\", \"UNSUBSCRIBE\", \"CANCEL\"]:\n        handle_opt_out(from_number)\n        return \"\", 200\n\n    # Check for opt-in keywords\n    if body.strip().upper() in [\"START\", \"SUBSCRIBE\"]:\n        handle_opt_in(from_number)\n        return \"\", 200\n\n    # Process message\n    process_incoming_sms(from_number, body, media_urls)\n\n    return \"\", 200\n\n@app.route(\"\u002Fwebhooks\u002Ftwilio\u002Fvoice\u002Fstatus\", methods=[\"POST\"])\n@validate_twilio_signature\ndef voice_status_callback():\n    \"\"\"Handle call status updates.\"\"\"\n    call_sid = request.form.get(\"CallSid\")\n    status = request.form.get(\"CallStatus\")\n    duration = request.form.get(\"CallDuration\")\n    direction = request.form.get(\"Direction\")\n\n    # Call statuses: initiated, ringing, in-progress, completed, busy, no-answer, canceled, failed\n\n    logger.info(f\"Call {call_sid}: {status} ({duration}s)\")\n\n    if status == \"completed\":\n        # Call ended normally\n        log_call_completion(call_sid, duration)\n\n    elif status in [\"busy\", \"no-answer\", \"canceled\", \"failed\"]:\n        # Call didn't connect\n        handle_failed_call(call_sid, status)\n\n    return \"\", 200\n\n# Helper functions\ndef update_message_status(message_sid: str, status: str):\n    \"\"\"Update message status in database.\"\"\"\n    pass\n\ndef handle_failed_message(message_sid: str, error_code: str, error_msg: str):\n    \"\"\"Handle failed message delivery.\"\"\"\n    # Notify team, retry logic, etc.\n    pass\n\ndef handle_opt_out(phone: str):\n    \"\"\"Handle user opting out of messages.\"\"\"\n    # Mark user as opted out in database\n    # IMPORTANT: Must respect this!\n    pass\n\ndef handle_opt_in(phone: str):\n    \"\"\"Handle user opting back in.\"\"\"\n    pass\n\ndef process_incoming_sms(from_phone: str, body: str, media: list):\n    \"\"\"Process incoming SMS message.\"\"\"\n    pass\n\ndef log_call_completion(call_sid: str, duration: str):\n    \"\"\"Log completed call.\"\"\"\n    pass\n\ndef handle_failed_call(call_sid: str, status: str):\n    \"\"\"Handle call that didn't connect.\"\"\"\n    pass\n\n### Anti_patterns\n\n- Not validating X-Twilio-Signature\n- Exposing webhook URLs without authentication\n- Not handling opt-out keywords (STOP)\n- Blocking webhook response (should be fast)\n\n### Rate Limit and Retry Pattern\n\nHandle Twilio rate limits and implement proper retry logic.\n\nDefault limits:\n- SMS: 80 messages per second (MPS)\n- Voice: Varies by number type and region\n- API calls: 100 requests per second\n\nError codes:\n- 20429: Voice API rate limit\n- 30429: Messaging API rate limit\n\n**When to use**: High-volume messaging applications,Bulk SMS campaigns,Automated calling systems\n\nimport time\nimport random\nfrom functools import wraps\nfrom twilio.base.exceptions import TwilioRestException\nimport logging\n\nlogger = logging.getLogger(__name__)\n\ndef exponential_backoff_retry(\n    max_retries: int = 5,\n    base_delay: float = 1.0,\n    max_delay: float = 60.0,\n    rate_limit_codes: list = [20429, 30429]\n):\n    \"\"\"\n    Decorator for exponential backoff retry on rate limits.\n\n    Uses jitter to prevent thundering herd.\n    \"\"\"\n    def decorator(func):\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            last_exception = None\n\n            for attempt in range(max_retries + 1):\n                try:\n                    return func(*args, **kwargs)\n\n                except TwilioRestException as e:\n                    last_exception = e\n\n                    # Only retry on rate limit errors\n                    if e.code not in rate_limit_codes:\n                        raise\n\n                    if attempt == max_retries:\n                        logger.error(f\"Max retries exceeded: {e}\")\n                        raise\n\n                    # Calculate delay with jitter\n                    delay = min(\n                        base_delay * (2 ** attempt) + random.uniform(0, 1),\n                        max_delay\n                    )\n\n                    logger.warning(\n                        f\"Rate limited (attempt {attempt + 1}\u002F{max_retries}). \"\n                        f\"Retrying in {delay:.1f}s\"\n                    )\n                    time.sleep(delay)\n\n            raise last_exception\n\n        return wrapper\n    return decorator\n\n# Usage\nfrom twilio.rest import Client\n\nclient = Client(account_sid, auth_token)\n\n@exponential_backoff_retry(max_retries=5)\ndef send_sms(to: str, body: str):\n    return client.messages.create(\n        to=to,\n        from_=from_number,\n        body=body\n    )\n\n# Bulk sending with rate limiting\nimport asyncio\nfrom asyncio import Semaphore\n\nclass RateLimitedSender:\n    \"\"\"\n    Send messages with built-in rate limiting.\n    Stays under Twilio's 80 MPS limit.\n    \"\"\"\n\n    def __init__(self, client, from_number: str, mps: int = 50):\n        self.client = client\n        self.from_number = from_number\n        self.mps = mps\n        self.semaphore = Semaphore(mps)\n\n    async def send_bulk(self, messages: list[dict]) -> list[dict]:\n        \"\"\"\n        Send messages with rate limiting.\n\n        Args:\n            messages: List of {\"to\": \"+1...\", \"body\": \"...\"}\n\n        Returns:\n            Results for each message\n        \"\"\"\n        tasks = [\n            self._send_with_limit(msg[\"to\"], msg[\"body\"])\n            for msg in messages\n        ]\n\n        return await asyncio.gather(*tasks, return_exceptions=True)\n\n    async def _send_with_limit(self, to: str, body: str):\n        \"\"\"Send single message with semaphore-based rate limit.\"\"\"\n        async with self.semaphore:\n            try:\n                # Use sync client in thread pool\n                loop = asyncio.get_event_loop()\n                result = await loop.run_in_executor(\n                    None,\n                    lambda: self.client.messages.create(\n                        to=to,\n                        from_=self.from_number,\n                        body=body\n                    )\n                )\n                return {\"success\": True, \"sid\": result.sid, \"to\": to}\n\n            except TwilioRestException as e:\n                return {\"success\": False, \"error\": str(e), \"to\": to}\n\n            finally:\n                # Delay to maintain rate limit\n                await asyncio.sleep(1 \u002F self.mps)\n\n# Usage\nasync def send_campaign():\n    sender = RateLimitedSender(client, from_number, mps=50)\n\n    messages = [\n        {\"to\": \"+14155551234\", \"body\": \"Hello!\"},\n        {\"to\": \"+14155555678\", \"body\": \"Hello!\"},\n        # ... thousands of messages\n    ]\n\n    results = await sender.send_bulk(messages)\n\n    successful = sum(1 for r in results if r.get(\"success\"))\n    print(f\"Sent {successful}\u002F{len(messages)} messages\")\n\n### Anti_patterns\n\n- Retrying immediately without backoff\n- No jitter causing thundering herd\n- Retrying non-rate-limit errors\n- Exceeding Twilio's MPS limit\n\n## Sharp Edges\n\n### Sending to Users Who Opted Out (Error 21610)\n\nSeverity: HIGH\n\nSituation: Sending SMS to a phone number\n\nSymptoms:\nMessage fails with error code 21610. Twilio rejects the message.\nUser never receives the SMS. Same number worked before.\n\nWhy this breaks:\nThe recipient replied \"STOP\" (or UNSUBSCRIBE, CANCEL, etc.) to a previous\nmessage from your number. Twilio automatically honors opt-outs and blocks\nfurther messages to that number from your account.\n\nThis is legally required for US messaging (TCPA, CTIA guidelines).\nYou cannot override this - the user must reply \"START\" to opt back in.\n\nRecommended fix:\n\n## Track opt-out status in your database\n\n```python\n# In your webhook handler\n@app.route(\"\u002Fwebhooks\u002Fsms\u002Fincoming\", methods=[\"POST\"])\ndef incoming_sms():\n    from_number = request.form.get(\"From\")\n    body = request.form.get(\"Body\", \"\").strip().upper()\n\n    # Standard opt-out keywords\n    if body in [\"STOP\", \"UNSUBSCRIBE\", \"CANCEL\", \"END\", \"QUIT\"]:\n        mark_user_opted_out(from_number)\n        return \"\", 200\n\n    # Standard opt-in keywords\n    if body in [\"START\", \"SUBSCRIBE\", \"YES\", \"UNSTOP\"]:\n        mark_user_opted_in(from_number)\n        return \"\", 200\n\n    # Process other messages...\n\n# Before sending\ndef send_sms_safe(to: str, body: str):\n    if is_user_opted_out(to):\n        return {\"success\": False, \"error\": \"User has opted out\"}\n\n    try:\n        return send_sms(to, body)\n    except TwilioRestException as e:\n        if e.code == 21610:\n            # Update database - they opted out via carrier\n            mark_user_opted_out(to)\n        raise\n```\n\n## Include opt-out instructions\nAdd \"Reply STOP to unsubscribe\" to marketing messages.\n\n### Phone Unreachable But Valid (Error 30003)\n\nSeverity: MEDIUM\n\nSituation: Sending SMS to a mobile number\n\nSymptoms:\nMessage fails with error 30003. Number was valid and worked before.\nIntermittent - sometimes works, sometimes fails.\n\nWhy this breaks:\nError 30003 means \"Unreachable destination handset.\" The phone exists but\ncan't receive messages right now. Common causes:\n- Phone powered off\n- Airplane mode\n- Out of signal range\n- Carrier network issues\n- Phone storage full\n\nUnlike 30006 (permanent unreachable), 30003 is usually temporary.\n\nRecommended fix:\n\n## Implement retry logic for transient failures\n\n```python\nTRANSIENT_ERRORS = [30003, 30008, 30009]  # Retriable errors\n\nasync def send_with_retry(to: str, body: str, max_retries: int = 3):\n    for attempt in range(max_retries):\n        result = send_sms(to, body)\n\n        if result[\"success\"]:\n            return result\n\n        if result.get(\"error_code\") not in TRANSIENT_ERRORS:\n            # Don't retry permanent failures\n            return result\n\n        # Exponential backoff: 5min, 15min, 45min\n        delay = 300 * (3 ** attempt)\n        await asyncio.sleep(delay)\n\n    return {\"success\": False, \"error\": \"Max retries exceeded\"}\n```\n\n## Provide fallback channel\n\n```python\nasync def notify_user(user, message):\n    # Try SMS first\n    result = await send_sms(user.phone, message)\n\n    if result.get(\"error_code\") == 30003:\n        # Phone unreachable - try email\n        await send_email(user.email, message)\n        return {\"channel\": \"email\", \"status\": \"sent\"}\n\n    return {\"channel\": \"sms\", \"status\": result[\"status\"]}\n```\n\n### Messages Blocked by Carrier Filtering\n\nSeverity: HIGH\n\nSituation: Sending SMS to US phone numbers\n\nSymptoms:\nMessages show as \"sent\" but never \"delivered.\" No error from Twilio.\nUsers say they never received the message. Pattern in specific carriers\nor message content.\n\nWhy this breaks:\nUS carriers (Verizon, AT&T, T-Mobile) aggressively filter SMS for spam.\nYour message might be blocked if:\n- Contains URLs (especially short URLs or unknown domains)\n- Looks like phishing (urgent, account, verify, click now)\n- High volume from same number\n- Not using registered A2P 10DLC\n- Low sender reputation\n\nCarriers don't tell Twilio why messages are filtered - they just\nsilently drop them.\n\nRecommended fix:\n\n## Register for A2P 10DLC (US requirement)\n\n```\n1. Go to Twilio Console > Messaging > Trust Hub\n2. Register your business brand\n3. Create a messaging campaign (describes use case)\n4. Wait for approval (can take days)\n5. Associate phone numbers with campaign\n```\n\n## Message content best practices\n\n```python\ndef sanitize_message(text: str) -> str:\n    \"\"\"Make message less likely to be filtered.\"\"\"\n    # Avoid URL shorteners - use full domain\n    # Avoid spam trigger words\n    # Keep it conversational, not promotional\n\n    # Example: Instead of this\n    bad = \"URGENT: Verify your account now! Click: bit.ly\u002Fabc\"\n\n    # Do this\n    good = \"Hi! Your order #1234 is ready. Questions? Reply here.\"\n\n    return text\n\n# Use toll-free or short code for high volume\n# 10DLC is for \u003C10K msg\u002Fday\n# Toll-free: up to 10K msg\u002Fday\n# Short code: 100K+ msg\u002Fday\n```\n\n## Monitor delivery rates\n\n```python\ndef track_delivery_rate():\n    sent = get_messages_with_status(\"sent\")\n    delivered = get_messages_with_status(\"delivered\")\n\n    rate = len(delivered) \u002F len(sent) * 100\n\n    if rate \u003C 95:\n        alert_team(f\"Delivery rate dropped to {rate}%\")\n```\n\n### Not Validating Webhook Signatures\n\nSeverity: CRITICAL\n\nSituation: Receiving Twilio webhook callbacks\n\nSymptoms:\nAttackers send fake webhooks to your endpoint. Fraudulent transactions\nprocessed. Spoofed incoming messages trigger actions.\n\nWhy this breaks:\nTwilio signs all webhook requests with X-Twilio-Signature header.\nIf you don't validate this, anyone who knows your webhook URL can\nsend fake requests pretending to be Twilio.\n\nThis can lead to:\n- Fake message delivery confirmations\n- Spoofed incoming messages\n- Fraudulent verification approvals\n\nRecommended fix:\n\n## ALWAYS validate the signature\n\n```python\nfrom twilio.request_validator import RequestValidator\nfrom flask import Flask, request, abort\nfrom functools import wraps\nimport os\n\ndef require_twilio_signature(f):\n    \"\"\"Decorator to validate Twilio webhook requests.\"\"\"\n    @wraps(f)\n    def wrapper(*args, **kwargs):\n        validator = RequestValidator(os.environ[\"TWILIO_AUTH_TOKEN\"])\n\n        # Full URL including query string\n        url = request.url\n\n        # POST body as dict\n        params = request.form.to_dict()\n\n        # Signature header\n        signature = request.headers.get(\"X-Twilio-Signature\", \"\")\n\n        if not validator.validate(url, params, signature):\n            abort(403)\n\n        return f(*args, **kwargs)\n    return wrapper\n\n@app.route(\"\u002Fwebhooks\u002Ftwilio\", methods=[\"POST\"])\n@require_twilio_signature  # ALWAYS use this\ndef twilio_webhook():\n    # Safe to process\n    pass\n```\n\n## Common validation gotchas\n\n```python\n# URL must match EXACTLY what Twilio called\n# If behind proxy, you might need:\nurl = request.headers.get(\"X-Forwarded-Proto\", \"http\") + \":\u002F\u002F\" + \\\n      request.headers.get(\"X-Forwarded-Host\", request.host) + \\\n      request.path\n\n# If using ngrok, URL changes each restart\n# Use consistent URL in production\n```\n\n### WhatsApp Message Outside 24-Hour Window (Error 63016)\n\nSeverity: HIGH\n\nSituation: Sending WhatsApp message to a user\n\nSymptoms:\nMessage fails with error 63016. \"Message is outside the allowed window.\"\nTemplate messages work, but regular messages fail.\n\nWhy this breaks:\nWhatsApp has strict rules about unsolicited messages:\n- Users must message you first\n- You can only reply within 24 hours of their last message\n- After 24 hours, you must use pre-approved template messages\n\nThis prevents spam and maintains WhatsApp's trust as a platform.\n\nRecommended fix:\n\n## Track session windows per user\n\n```python\nfrom datetime import datetime, timedelta\n\nclass WhatsAppSession:\n    def __init__(self, redis_client):\n        self.redis = redis_client\n        self.window_hours = 24\n\n    def start_session(self, phone: str):\n        \"\"\"Start\u002Frefresh 24-hour session on incoming message.\"\"\"\n        key = f\"wa_session:{phone}\"\n        expires = datetime.now() + timedelta(hours=self.window_hours)\n        self.redis.set(key, expires.isoformat(), ex=self.window_hours * 3600)\n\n    def can_send_freeform(self, phone: str) -> bool:\n        \"\"\"Check if we can send non-template message.\"\"\"\n        key = f\"wa_session:{phone}\"\n        expires_str = self.redis.get(key)\n\n        if not expires_str:\n            return False\n\n        expires = datetime.fromisoformat(expires_str)\n        return datetime.now() \u003C expires\n\n    def send_message(self, phone: str, body: str, template_sid: str = None):\n        \"\"\"Send message, using template if outside window.\"\"\"\n        if self.can_send_freeform(phone):\n            return send_whatsapp_message(phone, body)\n        elif template_sid:\n            return send_whatsapp_template(phone, template_sid)\n        else:\n            return {\n                \"success\": False,\n                \"error\": \"Outside session window, template required\"\n            }\n```\n\n## Incoming message webhook\n\n```python\n@app.route(\"\u002Fwebhooks\u002Fwhatsapp\", methods=[\"POST\"])\ndef whatsapp_incoming():\n    from_phone = request.form.get(\"From\").replace(\"whatsapp:\", \"\")\n\n    # Start\u002Frefresh session\n    session.start_session(from_phone)\n\n    # Process message...\n```\n\n## Create approved templates for common messages\n\n```\n1. Twilio Console > Content Template Builder\n2. Create template with {{1}} placeholders\n3. Submit for WhatsApp approval (takes 24-48 hours)\n4. Use content_sid to send\n```\n\n### Exposed Account SID or Auth Token\n\nSeverity: CRITICAL\n\nSituation: Deploying Twilio integration\n\nSymptoms:\nUnauthorized charges on Twilio account. Messages sent you didn't send.\nPhone numbers purchased without authorization.\n\nWhy this breaks:\nIf attackers get your Account SID + Auth Token, they have FULL access\nto your Twilio account. They can:\n- Send messages (charging your account)\n- Buy phone numbers\n- Access call recordings\n- Modify your configuration\n\nCommon exposure points:\n- Hardcoded in source code (pushed to GitHub)\n- In client-side JavaScript\n- In Docker images\n- In logs\n\nRecommended fix:\n\n## Never hardcode credentials\n\n```python\n# BAD - never do this\nclient = Client(\"AC1234...\", \"abc123...\")\n\n# GOOD - environment variables\nclient = Client(\n    os.environ[\"TWILIO_ACCOUNT_SID\"],\n    os.environ[\"TWILIO_AUTH_TOKEN\"]\n)\n\n# GOOD - secrets manager\nfrom aws_secretsmanager import get_secret\ncreds = get_secret(\"twilio-credentials\")\nclient = Client(creds[\"sid\"], creds[\"token\"])\n```\n\n## Use API Key instead of Auth Token\n\n```python\n# Auth Token has full account access\n# API Keys can be scoped and revoked\n\n# Create API Key in Twilio Console\nclient = Client(\n    os.environ[\"TWILIO_API_KEY_SID\"],\n    os.environ[\"TWILIO_API_KEY_SECRET\"],\n    os.environ[\"TWILIO_ACCOUNT_SID\"]\n)\n\n# If compromised, revoke just that key\n```\n\n## Rotate tokens immediately if exposed\n\n```\n1. Twilio Console > Account > API credentials\n2. Rotate Auth Token\n3. Update all deployments with new token\n4. Review account activity for unauthorized use\n```\n\n### Verify Rate Limit Exceeded (Error 60203)\n\nSeverity: MEDIUM\n\nSituation: Sending verification codes\n\nSymptoms:\nVerification request fails with error 60203.\n\"Max send attempts reached for this phone number.\"\n\nWhy this breaks:\nTwilio Verify has built-in rate limits to prevent abuse:\n- 5 verification attempts per phone number per service per 10 minutes\n- Helps prevent SMS pumping fraud\n- Protects against brute-force attacks\n\nIf users legitimately need more attempts, you may have UX issues.\n\nRecommended fix:\n\n## Implement application-level rate limiting too\n\n```python\nfrom datetime import datetime, timedelta\nimport redis\n\nclass VerifyRateLimiter:\n    def __init__(self, redis_client):\n        self.redis = redis_client\n        # Stricter than Twilio's limit\n        self.max_attempts = 3\n        self.window_minutes = 10\n\n    def can_request(self, phone: str) -> bool:\n        key = f\"verify_rate:{phone}\"\n        attempts = self.redis.get(key)\n\n        if attempts and int(attempts) >= self.max_attempts:\n            return False\n\n        return True\n\n    def record_attempt(self, phone: str):\n        key = f\"verify_rate:{phone}\"\n        pipe = self.redis.pipeline()\n        pipe.incr(key)\n        pipe.expire(key, self.window_minutes * 60)\n        pipe.execute()\n\n    def get_wait_time(self, phone: str) -> int:\n        \"\"\"Return seconds until user can request again.\"\"\"\n        key = f\"verify_rate:{phone}\"\n        ttl = self.redis.ttl(key)\n        return max(0, ttl)\n\n# Usage\nlimiter = VerifyRateLimiter(redis_client)\n\n@app.route(\"\u002Fverify\u002Fsend\", methods=[\"POST\"])\ndef send_verification():\n    phone = request.json[\"phone\"]\n\n    if not limiter.can_request(phone):\n        wait = limiter.get_wait_time(phone)\n        return {\n            \"error\": f\"Too many attempts. Try again in {wait} seconds.\"\n        }, 429\n\n    result = twilio_verify.send_verification(phone)\n\n    if result[\"success\"]:\n        limiter.record_attempt(phone)\n\n    return result\n```\n\n## Provide clear user feedback\n\n```python\n# Show remaining attempts\n# Show countdown timer\n# Offer alternative (voice call, email)\n```\n\n## Validation Checks\n\n### Hardcoded Twilio Credentials\n\nSeverity: ERROR\n\nTwilio credentials must never be hardcoded\n\nMessage: Hardcoded Twilio SID detected. Use environment variables.\n\n### Auth Token in Source Code\n\nSeverity: ERROR\n\nAuth tokens should be in environment variables\n\nMessage: Hardcoded auth token. Use os.environ['TWILIO_AUTH_TOKEN'].\n\n### Webhook Without Signature Validation\n\nSeverity: ERROR\n\nTwilio webhooks must validate X-Twilio-Signature\n\nMessage: Webhook without signature validation. Add RequestValidator check.\n\n### Twilio Credentials in Client-Side Code\n\nSeverity: ERROR\n\nNever expose Twilio credentials to browsers\n\nMessage: Twilio credentials exposed client-side. Only use server-side.\n\n### No E.164 Phone Number Validation\n\nSeverity: WARNING\n\nPhone numbers should be validated before sending\n\nMessage: Sending to phone without E.164 validation.\n\n### Hardcoded Phone Numbers\n\nSeverity: WARNING\n\nPhone numbers should come from config or database\n\nMessage: Hardcoded phone number. Use config or environment variable.\n\n### No Twilio Exception Handling\n\nSeverity: WARNING\n\nTwilio calls should handle TwilioRestException\n\nMessage: Twilio API call without error handling. Catch TwilioRestException.\n\n### Not Handling Specific Error Codes\n\nSeverity: INFO\n\nHandle common Twilio error codes specifically\n\nMessage: Consider handling specific error codes (21610, 30003, etc.).\n\n### No Opt-Out Keyword Handling\n\nSeverity: WARNING\n\nSMS systems must handle STOP\u002FUNSUBSCRIBE keywords\n\nMessage: No opt-out handling. Check for STOP\u002FUNSUBSCRIBE keywords.\n\n### Not Checking Opt-Out Before Sending\n\nSeverity: WARNING\n\nCheck if user has opted out before sending SMS\n\nMessage: Consider checking opt-out status before sending.\n\n## Collaboration\n\n### Delegation Triggers\n\n- user needs AI voice assistant -> voice-agents (Twilio provides telephony, voice-agents skill for AI conversation)\n- user needs Slack notifications -> slack-bot-builder (Integrate SMS alerts with Slack notifications)\n- user needs full auth system -> auth-specialist (Twilio Verify is one component of broader auth)\n- user needs workflow automation -> workflow-automation (Trigger SMS\u002Fcalls from automated workflows)\n- user needs high-volume messaging -> devops (Scale webhooks, monitor delivery rates)\n\n## When to Use\n- User mentions or implies: twilio\n- User mentions or implies: send SMS\n- User mentions or implies: text message\n- User mentions or implies: voice call\n- User mentions or implies: phone verification\n- User mentions or implies: 2FA SMS\n- User mentions or implies: WhatsApp API\n- User mentions or implies: programmable messaging\n- User mentions or implies: IVR system\n- User mentions or implies: TwiML\n- User mentions or implies: phone number verification\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n",{"name":5,"risk":24,"source":25,"date_added":26,"description":7},"unknown","vibeship-spawner-skills (Apache 2.0)",1772150400000,"main","87afe6ce96220accf50a345dea46784c8c9f210e","YYC3-Skills-技能包\u002FYYC3-Skills-技能包-antigravity-awesome-skills\u002Fplugins\u002Fantigravity-awesome-skills\u002Fskills\u002Ftwilio-communications",0,"https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base","https:\u002F\u002Fkb.yyc3.top\u002F","https:\u002F\u002Fgithub.com\u002FYYC-Cube","万象归元于云枢 | 深栈智启新纪元","2026-11-10 10:37:32",{"source":8,"skillName":5,"commands":37,"prompt":47,"zipAvailable":48},[38,41,44],{"tool":39,"command":40},"npx","npx skills add YYC-Cube\u002FYYC3-Knowledge-Base --skill twilio-communications",{"tool":42,"command":43},"bunx","bunx skills add YYC-Cube\u002FYYC3-Knowledge-Base --skill twilio-communications",{"tool":45,"command":46},"pnpm","pnpm dlx skills add YYC-Cube\u002FYYC3-Knowledge-Base --skill twilio-communications","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-Skills-技能包\u002FYYC3-Skills-技能包-antigravity-awesome-skills\u002Fplugins\u002Fantigravity-awesome-skills\u002Fskills\u002Ftwilio-communications\u002FSKILL.md",true,[],[51,58,65,72,79,86],{"skillNo":52,"name":53,"nameZh":6,"description":54,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":55,"categories":56,"keywords":57,"publishedAt":19},"01M7CTDWHERCXPQCP0K03A86AS","vercel-sandbox","Run agent-browser + Chrome inside Vercel Sandbox microVMs for browser automation from any Vercel-deployed app. Use when the user needs browser automation in a Vercel app (Next.js, SvelteKit, Nuxt, Remix, Astro, etc.), wants to run headless Chrome without binary size limits, needs persistent browser sessions across commands, or wants ephemeral isolated browser environments. Triggers include \"Vercel Sandbox browser\", \"microVM Chrome\", \"agent-browser in sandbox\", \"browser automation on Vercel\", or any task requiring Chrome in a Vercel Sandbox.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-WebSpec-Web标准\u002FYYC3-WebSpec-Web标准-standards\u002Fbrowser-agent\u002Fskills\u002Fvercel-sandbox\u002FSKILL.md",[],[],{"skillNo":59,"name":60,"nameZh":6,"description":61,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":62,"categories":63,"keywords":64,"publishedAt":19},"01M7CTD8D2BNJY54S65M47HEJA","slack","Interact with Slack workspaces using browser automation. Use when the user needs to check unread channels, navigate Slack, send messages, extract data, find information, search conversations, or automate any Slack task. Triggers include \"check my Slack\", \"what channels have unreads\", \"send a message to\", \"search Slack for\", \"extract from Slack\", \"find who said\", or any task requiring programmatic Slack interaction.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-WebSpec-Web标准\u002FYYC3-WebSpec-Web标准-standards\u002Fbrowser-agent\u002Fskills\u002Fslack\u002FSKILL.md",[],[],{"skillNo":66,"name":67,"nameZh":6,"description":68,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":69,"categories":70,"keywords":71,"publishedAt":19},"01M7CTCSSB8ZP3Q58C3YZDZK1W","electron","Automate Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify, etc.) using agent-browser via Chrome DevTools Protocol. Use when the user needs to interact with an Electron app, automate a desktop app, connect to a running app, control a native app, or test an Electron application. Triggers include \"automate Slack app\", \"control VS Code\", \"interact with Discord app\", \"test this Electron app\", \"connect to desktop app\", or any task requiring automation of a native Electron application.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-WebSpec-Web标准\u002FYYC3-WebSpec-Web标准-standards\u002Fbrowser-agent\u002Fskills\u002Felectron\u002FSKILL.md",[],[],{"skillNo":73,"name":74,"nameZh":6,"description":75,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":76,"categories":77,"keywords":78,"publishedAt":19},"01M7CTCBJY76688V4DCZGWFHGQ","dogfood","Systematically explore and test a web application to find bugs, UX issues, and other problems. Use when asked to \"dogfood\", \"QA\", \"exploratory test\", \"find issues\", \"bug hunt\", \"test this app\u002Fsite\u002Fplatform\", or review the quality of a web application. Produces a structured report with full reproduction evidence -- step-by-step screenshots, repro videos, and detailed repro steps for every issue -- so findings can be handed directly to the responsible teams.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-WebSpec-Web标准\u002FYYC3-WebSpec-Web标准-standards\u002Fbrowser-agent\u002Fskills\u002Fdogfood\u002FSKILL.md",[],[],{"skillNo":80,"name":81,"nameZh":6,"description":82,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":83,"categories":84,"keywords":85,"publishedAt":19},"01M7CTC4H0FFGVEAQR9PA4GA1A","agent-browser","Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to \"open a website\", \"fill out a form\", \"click a button\", \"take a screenshot\", \"scrape data from a page\", \"test this web app\", \"login to a site\", \"automate browser actions\", or any task requiring programmatic web interaction.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-WebSpec-Web标准\u002FYYC3-WebSpec-Web标准-standards\u002Fbrowser-agent\u002Fskills\u002Fagent-browser\u002FSKILL.md",[],[],{"skillNo":87,"name":88,"nameZh":6,"description":89,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":90,"categories":91,"keywords":92,"publishedAt":19},"01M7CTBKVFYBG78FDSABSDJTTQ","shadcn","Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI, including chat interfaces. Provides project context, component docs, and usage examples. Applies when working with shadcn\u002Fui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for \"shadcn init\", \"create an app with --preset\", \"switch to --preset\", and for building a design system or applying a DESIGN.md or brand spec to shadcn\u002Fui.","https:\u002F\u002Fgithub.com\u002FYYC-Cube\u002FYYC3-Knowledge-Base\u002Fblob\u002Fmain\u002FYYC3-UI-界面组件\u002FYYC3-UI-界面组件-ui\u002Fskills\u002Fshadcn\u002FSKILL.md",[],[]]