[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-2b8e6552-1e75-4ff0-b91a-78798fdead9b":3,"$fa2D9qzKr2UmzcDjmV-H18r3CcYv0nADVL7KGBsayVMY":43},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":34},"2b8e6552-1e75-4ff0-b91a-78798fdead9b","twilio-communications","使用Twilio构建通信功能：短信消息、语音","cat_life_career","mod_other","sickn33,other","---\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","","imported","https:\u002F\u002Fgithub.com\u002Fsickn33\u002Fantigravity-awesome-skills","user_system_seed","SkillOPIC",true,88,724,"2026-05-16 13:44:58",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"其他","other","mdi-page-next-outline","其他类型Skill",5,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":32,"skillCount":33,"createdAt":26},"职场发展","career","mdi-briefcase-outline","面试准备、简历优化、职业规划",4,575,[35],{"id":36,"skillId":4,"version":37,"fileName":38,"fileSize":39,"filePath":40,"fileHash":41,"manifest":42,"createdAt":19},"46afa77c-2853-4982-b85b-6475b7c16846","1.0.0","twilio-communications.zip",13463,"uploads\u002Fskills\u002F2b8e6552-1e75-4ff0-b91a-78798fdead9b\u002Ftwilio-communications.zip","87082a0cb7c9adf9ca45e0d4162feb22171bdf7d9488d7b82ee6e14862118c88","[{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":45777}]",{"code":44,"message":45,"data":46},200,"success",{"items":47,"stats":48,"page":51},[],{"averageRating":49,"totalRatings":49,"ratingCounts":50},0,[49,49,49,49,49],{"limit":52,"offset":49,"hasMore":53,"nextOffset":52,"ratedOnly":16},15,false]