{"id":"d84ef35c-7868-433a-b4ad-2a96b1839fd5","shortId":"QyczRG","kind":"skill","title":"twilio-communications","tagline":"\"Build communication features with Twilio: SMS messaging, voice","description":"# 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) // 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://your-app.com/webhooks/twilio/status\"\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/OTP)\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/email.\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- <Say>: Text-to-speech\n- <Play>: Play audio file\n- <Gather>: Collect keypad/speech input\n- <Dial>: Connect to another number\n- <Record>: Record caller's voice\n- <Redirect>: 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(\"/voice/incoming\", 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=\"/voice/menu-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(\"/voice/incoming\")\n\n    return Response(str(response), mimetype=\"text/xml\")\n\n@app.route(\"/voice/menu-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=\"/voice/voicemail-saved\",\n            max_length=120,\n            transcribe=True,\n            transcribe_callback=\"/voice/transcription\"\n        )\n\n    else:\n        response.say(\"Invalid selection.\")\n        response.redirect(\"/voice/incoming\")\n\n    return Response(str(response), mimetype=\"text/xml\")\n\n@app.route(\"/voice/voicemail-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/xml\")\n\n@app.route(\"/voice/transcription\", 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://your-app.com/voice/outbound-message\",\n        status_callback=\"https://your-app.com/voice/status\"\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/no-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/document 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(\"/webhooks/whatsapp\", 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/failed)\n- Incoming SMS/WhatsApp messages\n- Call events (initiated, ringing, answered, completed)\n- Recording/transcription 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(\"/webhooks/twilio/sms/status\", 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/failed\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(\"/webhooks/twilio/sms/incoming\", 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(\"/webhooks/twilio/voice/status\", 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}/{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 / 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}/{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(\"/webhooks/sms/incoming\", 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/abc\"\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 <10K msg/day\n# Toll-free: up to 10K msg/day\n# Short code: 100K+ msg/day\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) / len(sent) * 100\n\n    if rate < 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(\"/webhooks/twilio\", 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\") + \"://\" + \\\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/refresh 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() < 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(\"/webhooks/whatsapp\", methods=[\"POST\"])\ndef whatsapp_incoming():\n    from_phone = request.form.get(\"From\").replace(\"whatsapp:\", \"\")\n\n    # Start/refresh 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(\"/verify/send\", 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/UNSUBSCRIBE keywords\n\nMessage: No opt-out handling. Check for STOP/UNSUBSCRIBE 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/calls 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.","tags":["twilio","communications","antigravity","awesome","skills","sickn33","agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding","ai-workflows"],"capabilities":["skill","source-sickn33","skill-twilio-communications","topic-agent-skills","topic-agentic-skills","topic-ai-agent-skills","topic-ai-agents","topic-ai-coding","topic-ai-workflows","topic-antigravity","topic-antigravity-skills","topic-claude-code","topic-claude-code-skills","topic-codex-cli","topic-codex-skills"],"categories":["antigravity-awesome-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/sickn33/antigravity-awesome-skills/twilio-communications","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add sickn33/antigravity-awesome-skills","source_repo":"https://github.com/sickn33/antigravity-awesome-skills","install_from":"skills.sh"}},"qualityScore":"0.700","qualityRationale":"deterministic score 0.70 from registry signals: · indexed on github topic:agent-skills · 34997 github stars · SKILL.md body (45,392 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-04-25T06:52:12.104Z","embedding":null,"createdAt":"2026-04-18T20:38:41.859Z","updatedAt":"2026-04-25T06:52:12.104Z","lastSeenAt":"2026-04-25T06:52:12.104Z","tsv":"'+1':2978 '+1234567890':88,261 '+14155551234':405,831,858,1613,3093 '+14155555678':3097 '-48':4349 '-9':188 '/abc':3773 '/verify/send':4759 '/voice/incoming':1147,1205,1290 '/voice/menu-selection':1174,1213 '/voice/outbound-message':1424 '/voice/status':1429 '/voice/transcription':1284,1344 '/voice/voicemail-saved':1276,1298 '/webhooks/sms/incoming':3259 '/webhooks/twilio':4010 '/webhooks/twilio/sms/incoming':2340 '/webhooks/twilio/sms/status':2231 '/webhooks/twilio/status':416 '/webhooks/twilio/voice/status':2438 '/webhooks/whatsapp':1878,4304 '0':907,2369,2848,4751 '1':187,190,227,277,823,1058,1172,1185,1234,2799,2849,2858,3077,3110,3693,4330,4340,4543 '1.0':2756 '10':4617,4678 '100':503,2698,3839 '100k':3812 '1024':1539,1653,2057 '10dlc':3667,3690,3798 '10k':3801,3808 '120':1279 '1234':409,1987,3780 '123456':851 '14':191 '159':272 '15min':3520 '160':99,225,273 '1f':2865 '2':843,1189,1249,2845,3701,4336,4549 '200':1374,1957,2338,2405,2424,2436,2519,3294,3314 '2025':2020 '20429':2704,2765 '21211':350 '21610':335,440,3156,3172,3354,4993 '21614':344 '24':1490,1498,1638,1745,1824,1906,1920,2047,4072,4125,4132,4178,4186,4348 '2fa':29,454,501,530,5158 '2fa/otp':445 '3':889,1193,1264,3485,3524,3706,4342,4553,4675 '30':932 '300':3523 '30003':357,3386,3401,3417,3453,3468,3559,4994 '30005':366 '30006':373,3450 '30008':3469 '30009':3470 '30429':378,2709,2766 '3600':4209 '4':3714,4351,4560 '400':1531 '403':1137,2223,4002 '429':4789 '45min':3521 '5':1178,2752,2892,3721,4608 '50':2946,3090 '550':1542 '5min':3519 '60':4724 '60.0':2760 '60200':779 '60203':784,4572,4585 '60205':792 '60223':799 '63016':1822,4076,4091 '63018':1830 '63025':1838 '63038':1847 '747m':484 '76':513 '80':92,1526,2684,2934 '82m':482 '95':3842 '96':507 'a2p':3666,3689 'abc123':4454 'abort':2132,2222,3943,4001 'abus':4607 'ac1234':4453 'access':4400,4415,4502 'account':159,594,867,1400,1603,2883,3219,3654,3768,4358,4374,4393,4404,4411,4462,4501,4530,4546,4562 'acm':1182 'action':1173,1275,3877 'activ':4563 'add':3373,4881 'addr':2221 'agent':2000,5057,5063 'aggress':3631 'ai':5052,5066 'airplan':362,3437 'alert':127,3843,5078 'allow':4096 'altern':4814 'alway':2083,2164,3928,4016 'analyt':2121 'anoth':1018,1026 'answer':2106,2476,2505 'anti':417,942,1438,2039,2641,3125 'anyon':3898 'api':25,1466,1475,1481,1587,2036,2696,2706,2711,4490,4503,4511,4520,4525,4547,4967,5165 'app':1089,1874,2146 'app.route':1146,1212,1297,1343,1877,2230,2339,2437,3258,4009,4303,4758 'app.run':1435 'applic':2721,4645 'application-level':4644 'appoint':1064 'approv':741,744,1507,1533,1737,1752,1833,3717,3925,4139,4325,4346 'arg':214,636,704,1106,1140,1641,2174,2226,2788,2803,2973,3965,4005 'ask':5223 'assist':5054 'associ':3722 'async':879,2958,3002,3021,3080,3473,3540 'asyncio':2915,2917 'asyncio.gather':2997 'asyncio.get':3032 'asyncio.sleep':931,3076,3527 'attach':1902,2372 'attack':511,516,3863,4390,4629 'attempt':485,787,887,898,902,906,940,2794,2827,2846,2856,2857,3487,3525,4588,4610,4635,4674,4691,4695,4698,4700,4707,4783,4798,4809 'audio':1011 'auth':163,598,1112,1404,1607,2180,2885,3971,4361,4395,4466,4494,4497,4551,4839,4846,4855,4860,5085,5088,5097 'auth-specialist':5087 'authent':45,529,2653 'author':4385 'autom':505,1061,2725,5101,5104,5108 'automat':489,3205 'avoid':3745,3751 'aw':4472 'await':930,2996,3036,3075,3104,3526,3550,3564 'back':2593,3241 'backoff':384,2747,2770,2888,3130,3518 'bad':3764,4447 'base':1223,2753,2843,3018 'basic':59 'beep':1273 'begin':1909 'behind':4038 'benefit':465 'best':874,3729 'bin':1407 'bit.ly':3772 'bit.ly/abc':3771 'block':109,483,502,2661,3211,3581,3640 'bodi':203,222,271,297,298,406,1626,1649,1690,1691,1894,1896,1943,2192,2361,2363,2432,2603,2898,2907,2908,2979,2990,3010,3048,3049,3094,3098,3269,3271,3280,3301,3326,3347,3480,3496,3981,4255,4278 'body.strip':2393,2413 'book':1562 'bool':177,193,4217,4685 'bot':506,5074 'boundari':5231 'brand':3705 'break':3188,3415,3622,3880,4107,4388,4596 'broader':5096 'browser':4898 'brute':4627 'brute-forc':4626 'build':4,14,980,2182 'builder':1763,4335,5075 'built':476,2926,4601 'built-in':475,2925,4600 'bulk':509,2722,2909,2961,3106 'busi':24,1465,1586,2473,2502,3704 'button':1555 'buy':4412 'calcul':2837 'call':22,562,563,645,1002,1155,1158,1376,1385,1392,1412,2080,2102,2120,2449,2452,2465,2481,2482,2490,2494,2496,2508,2514,2515,2614,2616,2623,2628,2629,2635,2697,2726,4036,4416,4816,4961,4968,5147 'call.sid':1431 'callback':78,206,230,300,302,413,433,1283,1352,1426,2240,2447,3861 'calldur':2461 'caller':1021 'callsid':2455 'callstatus':2458 'came':2160 'campaign':2724,3083,3710,3726 'cancel':2398,2477,2506,3195,3284 'cannot':3231 'carrier':106,377,804,2292,3361,3443,3583,3616,3624,3671 'case':1458,3713 'catch':4972 'caus':3133,3433 'chang':1486,4061 'channel':44,492,624,643,668,678,903,914,928,973,3538,3570,3575 'channel.value':669,679 'char':226,1540,1543,1654,2058 'charact':100,1534 'charg':4371,4409 'check':262,690,698,723,730,796,839,855,861,2386,2406,4218,4819,4883,5016,5021,5029,5040 'check.status':740,743 'clarif':5225 'class':142,557,568,1583,2920,4166,4658 'clear':4803,5198 'click':3656,3770 'client':133,156,542,591,1381,1396,1397,1567,1600,2880,2881,2882,2940,2948,3027,3086,4172,4175,4433,4451,4452,4458,4459,4483,4484,4516,4517,4664,4667,4757,4888,4904 'client-sid':4432,4887,4903 'client.calls.create':1413 'client.messages.create':2901 'code':389,429,456,471,578,633,651,695,701,712,713,715,734,735,751,810,837,847,850,859,871,946,964,970,1857,2264,2302,2311,2327,2334,2545,2703,2763,2824,3171,3505,3558,3794,3811,4427,4578,4843,4890,4978,4985,4992 'collabor':5047 'collect':1013 'come':4940 'common':3432,4026,4328,4421,4982 'communic':3,5,13,15 'complet':2107,2472,2489,2495,2615,2622 'complex':38 'complianc':49 'compon':5094 'compromis':4533 'config':4942,4950 'configur':4420 'confirm':125,538,1067,3919 'connect':1016,1239,1254,2511,2639 'consent':1520 'consid':4988,5039 'consider':80 'consist':4065 'consol':606,1760,3697,4332,4515,4545 'contact':2002 'contain':3642 'content':1727,1730,1748,1761,1784,1786,1788,1790,3619,3728,4333,4353 'continu':933 'convers':3757,5067 'core':1003 'corp':1183 'correct':703 'cost':104 'couldn':2318 'count':269,276,287,313 'countdown':4811 'cover':30 'creat':600,665,731,802,866,1756,3707,4324,4337,4510 'cred':4477,4485,4487 'credenti':427,4445,4482,4548,4822,4826,4885,4896,4901 'criteria':5234 'critic':46,2082,2163,3856,4364 'ctia':3228 'custom':481,1062,1394,1547 'd':189 'databas':949,1322,1923,2535,2578,3252,3356,4944 'datetim':1575,1577,4162,4164,4652,4654 'datetime.fromisoformat':4243 'datetime.now':1913,4198,4247 'day':3720 'debug':1436 'decor':1097,2767,2782,2875,3955 'def':152,171,197,321,582,618,689,764,880,1092,1104,1153,1219,1304,1350,1382,1596,1620,1720,1807,1881,1958,2022,2152,2172,2237,2346,2444,2522,2537,2560,2584,2596,2612,2625,2745,2781,2786,2893,2937,2959,3003,3081,3262,3320,3474,3541,3732,3818,3950,3963,4019,4168,4179,4210,4249,4307,4660,4679,4705,4726,4762 'default':89,1528,2681 'delay':2754,2758,2838,2841,2844,2851,2864,2868,3070,3522,3528 'deleg':5048 'deliv':2251,2279,2282,2288,3601,3828,3833,3836 'delivered/failed':2098 'deliveri':74,76,233,431,458,1991,2075,2115,2243,2553,3815,3820,3846,3918,5120 'deploy':4366,4556 'describ':3711,5202 'destin':368,3420 'detail':395,1116 'detect':4835 'devop':5116 'dict':209,327,630,697,771,1121,1633,1732,1733,1814,2194,2197,2965,2967,3983,3986 'didn':2509,2637,4378 'digit':1165,1171,1229,1231,1233,1248,1263 'direct':2462,2464 'diy':467 'docker':4437 'doesn':1835 'domain':3649,3750 'drop':3683,3848 'durat':2459,2485,2498,2619 'e':317,320,685,688,750,763,1716,1719,1803,1806,2808,2811,2835,3060,3066,3351 'e.164':86,183,220,259,421,640,1645,4912,4930 'e.code':2819,3353 'e164':173,248 'edg':3148 'elif':1247,1262,1992,2289,2314,2499,4279 'els':868,908,1285,2005,4289 'email':495,564,565,642,646,709,3563,3566,3571,4817 'en':629 'end':2491,3285 'endpoint':960,1028,1411,2169,3869 'enter':716,825,845 'enum':550,552,559 'environ':4456,4837,4851,4952,5214 'environment-specif':5213 'error':53,148,253,323,325,332,333,388,391,397,441,761,767,769,776,777,809,812,937,1810,1812,1819,1820,1856,1859,2067,2263,2267,2301,2303,2310,2312,2326,2333,2335,2544,2547,2702,2817,3064,3141,3155,3170,3337,3385,3400,3416,3467,3472,3504,3509,3532,3557,3603,4075,4090,4293,4571,4584,4779,4824,4845,4867,4892,4970,4977,4984,4991 'error.code':390,393,811,814,1858,1861 'error.msg':394,815,1862 'error_handlers.get':392,813,1860 'errorcod':2266 'errormessag':2270 'especi':111,3644 'etc':1325,1368,2558,3196,4995 'event':2081,2103,3033 'ex':4206 'exact':4033 'exampl':817,1377,3760 'exceed':381,1850,2056,2834,3142,3535,4570 'except':314,682,747,1713,1800,2791,2805,2810,2871,3000,3057,3348,4956 'execut':1042 'executor':3039 'exist':1837,3424 'expert':5219 'expir':474,755,1915,1932,4197,4232,4238,4242,4244,4248 'expires.isoformat':4205 'explicit':1519 'exponenti':383,2746,2769,2887,3517 'expos':2649,4357,4542,4894,4902 'exposur':4422 'f':279,1096,1139,1327,1370,1672,1677,1766,1771,2156,2171,2215,2225,2272,2298,2322,2383,2480,2831,2853,2861,3119,3845,3954,3962,4004,4193,4228,4687,4712,4742,4780 'f.__name__':1143 'factor':528 'fail':800,896,922,941,976,2300,2306,2316,2325,2329,2478,2507,2513,2539,2551,2627,3168,3398,3412,4088,4104,4582 'failur':2296,3464,3514 'fake':3865,3906,3916 'fallback':878,883,893,972,3537 'fals':252,387,758,760,808,936,1855,3063,3336,3531,4241,4292,4702 'fast':2666 'featur':6,16,2021 'feedback':4805 'file':1012 'filter':107,3584,3632,3679,3744 'final':3069 'first':607,1755,1901,3548,4119 'fix':3244,3458,3686,3927,4154,4442,4642 'flask':1071,1073,1090,1863,1870,1872,1875,2128,2130,2147,3939,3941 'float':2755,2759 'flow':819,1561 'focus':47 'forc':4628 'format':72,87,184,221,244,260,349,422,783,1611,1667 'forward':4047,4053 'found':499,795 'fraud':462,478,4623 'fraudul':3870,3923 'free':3791,3805 'freeform':4213,4271 'full':32,2183,3448,3749,3973,4399,4500,5084 'func':2783,2785,2802 'function':2521 'functool':2139,2733,3945 'fundament':69 'gather':1081,1164,1168,1169,1199 'gather.say':1179 'generat':457,472,968,1973 'get':1114,2190,2198,3823,3829,4391,4475,4478,4727 'github':4430 'go':3694 'good':3776,4455,4468 'goodby':1335 'googl':498 'gotcha':4028 'guidelin':3229 'handl':54,67,149,322,328,435,455,486,580,765,772,951,1156,1307,1353,1456,1590,1808,1815,1884,2064,2071,2241,2305,2328,2349,2370,2399,2418,2448,2512,2538,2550,2561,2566,2585,2590,2626,2634,2655,2672,4957,4963,4971,4975,4981,4989,5001,5007,5015 'handler':334,778,1821,2069,3257 'handset':3421 'hardcod':425,1459,4424,4444,4820,4830,4832,4854,4932,4946 'header':2201,3891,3988 'hello':3095,3099 'help':4619 'helper':2520 'herd':2780,3135 'hi':3777 'high':535,2718,3158,3586,3658,3796,4078,5113 'high-valu':534 'high-volum':2717,5112 'honor':3206 'host':4054 'hour':1491,1499,1639,1746,1825,1907,1919,2048,4073,4126,4133,4177,4187,4200,4202,4208,4350 'http':1033,4049 'hub':3700 'ignor':430 'imag':4438 'image/document':1662 'immedi':3128,4540 'implement':382,954,2677,3459,4643 'impli':5128,5133,5139,5145,5151,5157,5163,5169,5175,5181,5186 'import':132,136,138,140,541,545,547,551,555,1072,1079,1085,1087,1380,1566,1570,1572,1576,1581,1871,2129,2136,2140,2142,2144,2579,2728,2730,2734,2738,2740,2879,2914,2918,3936,3940,3946,3948,4163,4474,4653,4656 'in-progress':2469 'includ':2185,3368,3975 'incom':1154,1157,1866,1885,1970,2077,2099,2118,2347,2350,2428,2598,2608,3263,3874,3921,4190,4299,4309 'indic':2019,2025 'info':4980 'init':153,583,1597,2938,4169,4661 'initi':2104,2467 'input':854,1015,1202,5228 'insight':1030 'instead':3761,4492 'instruct':3372 'int':888,2366,2751,2945,3484,4697,4733 'integr':4368,5076 'interact':981,1560 'intermitt':3408 'invalid':345,369,780,870,1135,1287,2216 'issu':3445,4640 'ivr':39,978,984,1160,5176 'javascript':4435 'jitter':2776,2840,3132 'keep':3755 'key':79,464,1029,1487,4192,4204,4227,4235,4491,4504,4512,4521,4526,4537,4686,4693,4711,4719,4721,4741,4748 'keypad/speech':1014 'keyword':2391,2411,2659,3278,3299,5000,5009,5019 'know':2030,3900 'kwarg':1107,1141,2175,2227,2789,2804,3966,4006 'lambda':3041 'landlin':372,374 'languag':650,991 'last':2790,2809,2870,4129 'lead':3914 'leav':1195,1268 'legal':3222 'legitim':4632 'len':270,3122,3835,3837 'length':264,1278 'less':3740 'let':966,2028 'level':4646 'like':3651,3741 'limit':51,91,380,460,488,956,1525,1535,1849,2059,2668,2675,2682,2708,2713,2762,2774,2816,2823,2855,2913,2929,2936,2972,3006,3020,3074,3140,3146,4569,4604,4648,4672,4754,5190 'limiter.can':4770 'limiter.get':4774 'limiter.record':4797 'list':2606,2764,2964,2966,2975 'local':627,649,670,671 'log':2123,2145,2493,2613,2621,2741,4440 'logger':2149,2742 'logger.error':2297,2321,2830 'logger.info':2271,2479 'logger.warning':2214,2852 'logging.getlogger':2150,2743 'logic':2557,2680,3461 'look':3650 'loop':3031,3034 'loop.run':3037 'low':3668 'lower':1976,1983,1996 'main':1434 'maintain':3072,4146 'make':1032,1383,1390,3738 'manag':470,4470 'mani':1840,4782 'mark':2572,3287,3307,3362 'market':1556,3379 'markup':990 'match':4032,5199 'max':785,886,901,1277,1652,2749,2757,2797,2828,2832,2850,2859,2890,3482,3490,3533,4586,4750 'may':4637 'mean':3418 'media':1551,1628,1659,1693,1697,1699,1897,1944,1966,2365,2371,2373,2380,2433,2605 'media_urls.append':2381 'mediaurl':2384 'mediaurl0':1900 'medium':3388,4574 'mention':5126,5131,5137,5143,5149,5155,5161,5167,5173,5179,5184 'menu':1055,1161,1220,1225 'messag':10,20,64,73,93,97,110,123,213,223,237,263,281,290,306,653,1197,1270,1388,1472,1480,1502,1504,1523,1557,1595,1622,1636,1650,1665,1682,1695,1701,1703,1708,1723,1739,1743,1776,1795,1829,1842,1868,1887,1934,1940,1952,1961,1971,2010,2045,2062,2078,2093,2101,2114,2119,2256,2268,2274,2280,2284,2286,2304,2307,2308,2313,2330,2331,2336,2352,2426,2524,2526,2532,2540,2541,2552,2571,2610,2685,2710,2720,2923,2963,2969,2974,2984,2994,3014,3091,3102,3107,3123,3124,3167,3176,3200,3213,3226,3317,3380,3397,3429,3545,3554,3568,3580,3595,3612,3618,3637,3677,3698,3709,3727,3734,3739,3824,3830,3875,3917,3922,4070,4082,4087,4092,4099,4103,4114,4117,4130,4141,4191,4226,4251,4262,4276,4300,4323,4329,4375,4408,4831,4853,4876,4899,4925,4945,4965,4987,5010,5038,5115,5141,5171 'message.sid':308,1710,1797 'message.status':310,1712 'messagesid':2259 'messagestatus':2262 'method':1148,1175,1214,1299,1345,1879,2232,2341,2439,3260,4011,4305,4760 'might':3638,4041 'mimetyp':1210,1295,1341 'min':2842 'minor':1485 'minut':4618,4677,4723 'miss':5236 'mobil':3394,3630 'mode':363,3438 'modifi':4418 'monitor':3814,5119 'move':1024 'mps':96,1527,2688,2935,2944,2954,2957,3089,3145 'msg':2548,2987,2989,2992 'msg/day':3802,3809,3813 'multi':43,491 'multi-channel':42,490 'must':83,256,341,1518,1750,2580,3236,4031,4116,4135,4827,4870,5006 'name':1091,1433,1876,2148,2151,2744 'need':4042,4633,5051,5069,5083,5099,5111 'network':3444 'never':575,3178,3600,3609,4443,4448,4828,4893 'new':969,4558 'ngrok':4059 'no-answ':2474,2503 'non':1450,1537,1657,2043,3138,4224 'non-rate-limit':3137 'non-templ':1536,1656,2042,4223 'non-xml':1449 'none':208,589,1632,2792,3040,4260 'normal':2492 'notif':36,119,1553,5071,5081 'notifi':1323,2554,3542 'num':1170,2364,2379 'number':71,82,114,166,170,180,218,243,255,296,348,353,370,451,522,639,707,782,791,827,1019,1420,1461,1610,1615,1619,1644,1680,1774,1889,1930,1942,1954,2354,2358,2403,2422,2431,2692,2906,2942,2950,2952,3047,3088,3165,3183,3203,3216,3266,3292,3312,3395,3402,3593,3662,3724,4382,4414,4593,4613,4914,4919,4934,4938,4948,5188 'nummedia':2368 'offer':876,4813 'one':5093 'opt':338,438,1514,2389,2400,2409,2419,2562,2568,2575,2586,2592,2657,3153,3208,3240,3247,3276,3289,3297,3309,3331,3340,3358,3364,3370,4998,5013,5023,5033,5042 'opt-in':1513,2408,3296 'opt-out':2388,2656,3207,3246,3275,3369,4997,5012,5022,5041 'opted-out':437 'option':556,1582,1630,1661 'order':124,408,1552,1979,1986,2013,3779 'os':139,548,1088,1573,2143,3949 'os.environ':157,161,167,592,596,614,1110,1244,1259,1398,1402,1417,1601,1605,1616,2178,3969,4460,4464,4518,4523,4528,4858 'otp':468,577,945 'out':3209 'outbound':1375,1384,1391 'output':5208 'outsid':1510,1744,1823,2046,4071,4094,4266,4294 'overrid':3232 'param':1048,1119,1132,1683,1696,1704,2187,2195,2212,3984,3999 'pass':2038,2536,2559,2583,2595,2611,2624,2640,4025 'password':531 'pattern':55,58,60,185,195,418,444,943,979,1439,1467,2040,2070,2642,2671,3126,3613 'pend':677,746 'per':94,2054,2686,2700,4158,4611,4614,4616 'perman':3451,3513 'permiss':5229 'phish':510,3652 'phone':70,81,169,175,179,196,217,242,254,347,352,358,450,521,570,638,706,781,826,841,864,884,913,1054,1246,1261,1419,1460,1962,2564,2588,2601,3164,3381,3423,3434,3446,3560,3592,3723,4183,4196,4215,4231,4253,4272,4277,4286,4311,4321,4381,4413,4592,4612,4683,4690,4709,4715,4731,4745,4765,4767,4772,4777,4793,4799,4913,4918,4928,4933,4937,4947,5152,5187 'phone/email':635 'pipe':4716 'pipe.execute':4725 'pipe.expire':4720 'pipe.incr':4718 'placehold':4341 'platform':4152 'play':1010 'pleas':1267 'point':4423 'pool':3030 'post':1149,1176,1215,1300,1346,1880,2191,2233,2342,2440,3261,3980,4012,4306,4761 'power':3435 'practic':875,3730 'pre':1506,1736,4138 'pre-approv':1505,1735,4137 'prefix':1648 'press':1057,1184,1188,1192 'pretend':3908 'prevent':463,479,2778,4143,4606,4620 'previous':3199 'print':278,836,863,869,1326,1369,3118 'process':1933,1938,1959,1969,2126,2425,2427,2597,2607,3315,3872,4024,4322 'product':4068 'programm':5170 'progress':2247,2471 'promot':3759 'proper':147,2678 'protect':4624 'proto':4048 'provid':3536,4802,5059 'proxi':4039 'pump':4622 'purchas':4383 'push':496,4428 'python':3253,3465,3539,3731,3817,3932,4029,4160,4302,4446,4496,4650,4806 'queri':2186,3976 'question':3783 'queu':2096,2248,2253 'quit':3286 'r':186,3112 'r.get':3116 'rais':2825,2836,2869,3367 'random':2731 'random.uniform':2847 'rang':900,2378,2796,3442,3489 'rate':50,90,379,459,487,955,1524,1848,2667,2674,2707,2712,2761,2773,2815,2822,2854,2912,2928,2971,3019,3073,3139,3816,3821,3834,3841,3847,3850,4568,4603,4647,4689,4714,4744,5121 'ratelimitedsend':2921,3085 're':141,2032 're.match':194 'reach':788,4589 'readi':2109,3782 'receiv':711,849,1001,1470,1522,2117,3179,3428,3610,3858 'recipi':216,336,1643,3190 'recommend':3243,3457,3685,3926,4153,4441,4641 'record':1020,1312,1316,1330,1359,4417,4706 'recording/transcription':2108 'recordingsid':1319,1362 'recordingurl':1315 'redi':4171,4174,4657,4663,4666,4756 'region':2695 'regist':3665,3687,3702 'regular':4102 'reject':805,2066,2293,3174 'remain':4808 'remind':129,1065 'repeat':1203 'replac':1892,4314 'repli':342,1496,1946,2011,3191,3237,3374,3784,4123 'reput':3670 'request':1034,1074,1095,1100,1115,1136,1152,1218,1303,1349,1873,2131,2159,2699,3885,3907,3942,3960,4581,4681,4739,4771 'request.form.get':1230,1314,1318,1357,1361,1890,1895,1899,2258,2261,2265,2269,2355,2359,2362,2367,2382,2454,2457,2460,2463,3267,3270,4312 'request.form.to':1120,2196,3985 'request.headers.get':1123,2203,3990,4044,4050 'request.host':4055 'request.json':4766 'request.path':4056 'request.remote':2220 'request.url':1118,2189,3979 'requestvalid':1086,1109,2137,2177,3937,3968,4882 'requir':1516,2034,3223,3692,3951,4013,4298,5227 'reset':532 'respect':2581 'respond':1936 'respons':983,1075,1078,1162,1207,1209,1227,1292,1294,1310,1338,1340,1452,1937,1955,1974,2663 'response.append':1198 'response.dial':1243,1258 'response.hangup':1336 'response.record':1274 'response.redirect':1204,1289 'response.say':1238,1253,1266,1286,1332 'restart':4063 'result':401,721,828,834,910,916,919,1666,2981,3035,3103,3114,3492,3498,3501,3516,3549,3578,4790,4795,4801 'result.get':3503,3556 'result.sid':3054 'retri':965,2556,2670,2679,2748,2750,2771,2798,2813,2829,2833,2860,2862,2889,2891,3127,3136,3460,3477,3483,3491,3512,3534 'retriabl':3471 'return':192,236,250,303,318,385,654,672,686,719,736,756,806,918,934,1039,1134,1138,1144,1206,1291,1337,1373,1430,1448,1664,1705,1717,1792,1804,1853,1956,1984,1997,2006,2224,2228,2337,2404,2423,2435,2518,2801,2872,2874,2900,2980,2995,2999,3050,3061,3293,3313,3334,3343,3500,3515,3529,3569,3574,3786,4003,4007,4240,4246,4273,4282,4290,4701,4703,4734,4749,4778,4800 'review':4561,5220 'revok':4509,4534 'rich':1550 'right':3430 'ring':2105,2468 'risk':1447 'rotat':4538,4550 'rout':1222 'rule':1489,4111 'safe':3323,4022 'safeti':5230 'sale':1060,1187,1237,1242,1245 'same-cod':962 'sanit':3733 'save':480,1306,1308,1320,1329,1363 'say':3607 'scale':5117 'scope':4507,5201 'second':95,2687,2701,4735,4788 'secret':4469,4476,4479,4527 'secretsmanag':4473 'secur':1446 'segment':228,267,268,275,286,288,311,312 'select':1221,1226,1288 'self':154,174,200,324,584,621,692,768,1598,1623,1724,1811,2939,2962,3007,4170,4182,4214,4252,4662,4682,4708,4730 'self._handle_error':319 'self._handle_verify_error':687 'self._handle_whatsapp_error':1718,1805 'self._send_with_limit':2986 'self.can':4269 'self.client':155,590,1599,2947 'self.client.messages.create':291,1702,1777,3042 'self.client.verify':659,724 'self.from':165,295,1614,1679,1773,2949,3046 'self.max':4673,4699 'self.mps':2953,3078 'self.redis':4173,4665 'self.redis.get':4234,4692 'self.redis.pipeline':4717 'self.redis.set':4203 'self.redis.ttl':4747 'self.semaphore':2955,3023 'self.service':608,662,727 'self.validate':247 'self.window':4176,4201,4207,4676,4722 'semaphor':2919,2956,3017 'semaphore-bas':3016 'send':57,62,118,145,198,210,424,619,631,786,1365,1468,1621,1634,1721,1734,2017,2023,2041,2090,2249,2254,2320,2324,2894,2910,2922,2960,2968,3004,3012,3082,3149,3160,3319,3321,3344,3390,3475,3493,3551,3565,3588,3864,3905,4080,4212,4222,4250,4261,4270,4274,4283,4356,4380,4407,4576,4587,4763,4924,4926,5026,5036,5046,5134 'sender':2035,3084,3669 'sender.send':3105 'sent':284,838,1843,2097,2250,3120,3573,3598,3822,3827,3838,4376 'server':4909 'server-sid':4908 'servic':586,603,611,661,726,793,4615 'session':1050,1492,1511,1591,1640,1904,1911,1914,1916,1925,1928,1931,1948,2052,2061,4156,4181,4188,4195,4230,4295,4317,4319 'session.start':4318 'setup':2037 'sever':3157,3387,3585,3855,4077,4363,4573,4823,4844,4866,4891,4916,4935,4958,4979,5002,5027 'sharp':3147 'ship':126,411 'short':2004,3645,3793,3810 'shorten':3747 'show':3596,4807,4810 'sid':160,238,307,587,595,609,612,617,663,728,798,1317,1360,1401,1604,1709,1728,1785,1787,1796,2257,2275,2287,2309,2332,2453,2483,2497,2516,2527,2542,2617,2630,2884,3053,4258,4281,4288,4354,4359,4394,4463,4486,4522,4531,4834 'side':4434,4889,4905,4910 'sign':3882 'signal':365,3441 'signatur':1122,1127,1133,1445,2088,2155,2199,2202,2207,2213,2218,2236,2345,2443,2648,3854,3890,3931,3953,3987,3989,3994,4000,4015,4864,4875,4879 'signup':525,818 'silent':3682 'simpl':35 'singl':3013 'situat':3159,3389,3587,3857,4079,4365,4575 'skill':5064,5193 'skill-twilio-communications' 'slack':1367,5070,5073,5080 'slack-bot-build':5072 'sms':9,19,56,63,144,199,212,399,403,493,500,560,561,644,895,921,975,1483,2238,2242,2273,2299,2323,2348,2351,2429,2599,2609,2683,2723,2895,3161,3181,3264,3322,3345,3391,3494,3547,3552,3576,3589,3633,4621,5004,5037,5077,5135,5159 'sms.send':402 'sms/calls':5106 'sms/whatsapp':2100 'sometim':3409,3411 'sourc':4426,4842 'source-sickn33' 'spam':3635,3752,4144 'specialist':5089 'specif':331,775,1818,3615,4976,4986,4990,5215 'spectrum':33 'speech':1009 'split':102 'spoof':3873,3920 'standard':3274,3295 'start':343,1905,1912,1917,2416,3238,3303,4180 'start/refresh':4185,4316 'stateless':1044 'status':77,205,229,234,240,299,301,309,412,432,656,675,742,1425,1711,1980,2014,2076,2094,2116,2239,2244,2246,2260,2276,2278,2285,2290,2315,2446,2450,2456,2466,2484,2488,2500,2517,2525,2529,2533,2632,3249,3572,3577,3579,3826,3832,5044 'stay':2930 'step':822,842 'stop':2396,2660,3192,3282,3375,5221 'stop/unsubscribe':5008,5018 'storag':3447 'store':576,944,1921 'str':176,202,204,207,396,588,623,628,694,696,762,885,1208,1293,1339,1387,1389,1625,1627,1631,1726,1729,1963,1965,1967,1968,2027,2528,2530,2543,2546,2549,2565,2589,2602,2604,2618,2620,2631,2633,2897,2899,2943,3009,3011,3065,3325,3327,3479,3481,3736,3737,4184,4216,4233,4239,4245,4254,4256,4259,4684,4710,4732 'strict':4110 'stricter':4668 'string':3977 'strip':3272 'submit':4343 'subscrib':2417,3304 'substitut':5211 'success':251,304,386,673,737,757,807,835,917,935,1706,1793,1854,2281,3051,3062,3108,3117,3121,3335,3499,3530,4291,4796,5233 'sum':3109 'support':1063,1191,1252,1257,1260,1548,1993,1999,2016 'survey':1563 'symptom':3166,3396,3594,3862,4086,4369,4579 'sync':3026 'system':40,985,1056,1069,2727,5005,5086,5177 't-mobil':3628 'take':3719,4347 'target':515 'task':2985,2998,5197 'tcpa':3227 'team':1324,2555,3844 'telephoni':5060 'tell':995,3674 'templat':1503,1508,1538,1541,1559,1594,1658,1722,1738,1749,1762,1798,1828,1831,1841,2044,2065,4098,4140,4225,4257,4264,4280,4285,4287,4297,4326,4334,4338 'temporari':3456 'test':5217 'text':224,1007,1651,1964,1975,1982,1995,3735,3787,5140 'text-to-speech':1006 'text.lower':1977 'text/xml':1211,1296,1342 'thank':1333,2007 'thousand':3100 'thread':3029 'thunder':2779,3134 'time':2729,4729,4776 'time.sleep':2867 'timedelta':1578,1918,4165,4199,4655 'timeout':1167,1177 'timeout/no-input':1457 'timer':4812 'token':164,599,1113,1405,1608,2181,2886,3972,4362,4396,4467,4488,4495,4498,4539,4552,4559,4840,4847,4856,4861 'toll':3790,3804 'toll-fre':3789,3803 'topic-agent-skills' 'topic-agentic-skills' 'topic-ai-agent-skills' 'topic-ai-agents' 'topic-ai-coding' 'topic-ai-workflows' 'topic-antigravity' 'topic-antigravity-skills' 'topic-claude-code' 'topic-claude-code-skills' 'topic-codex-cli' 'topic-codex-skills' 'track':1903,1926,2051,2113,3245,3819,4155 'transact':122,537,3871 'transcrib':1280,1282 'transcript':1351,1355,1356,1364,1371,1372,2125 'transcriptiontext':1358 'transfer':1235,1250 'transient':3463,3466,3508 'treat':5206 'tri':289,657,722,872,925,1681,1775,2800,3024,3342,3546,3562,4784 'trigger':3753,3876,5049,5105 'true':305,674,738,1281,1437,1707,1794,1799,3001,3052,4704 'trust':3699,4149 'ttl':4746,4752 'twilio':2,8,12,18,66,158,162,168,330,426,442,447,469,573,579,593,597,605,615,950,989,996,1031,1041,1094,1103,1111,1126,1151,1217,1302,1348,1399,1403,1418,1444,1454,1474,1479,1589,1602,1606,1617,1759,2072,2087,2089,2154,2162,2179,2206,2217,2235,2317,2344,2442,2647,2673,2932,3143,3173,3204,3605,3675,3696,3859,3881,3889,3911,3952,3958,3970,3993,4014,4020,4035,4331,4367,4373,4403,4461,4465,4481,4514,4519,4524,4529,4544,4597,4670,4821,4825,4833,4859,4868,4874,4884,4895,4900,4955,4960,4966,4983,5058,5090,5129 'twilio-commun':1 'twilio-credenti':4480 'twilio-specif':329 'twilio.base.exceptions':135,544,1569,2737 'twilio.request':1083,2134,3934 'twilio.rest':131,540,1379,1565,2878 'twilio.twiml.voice':1077 'twilio_verify.send':4791 'twiliorestexcept':137,315,326,546,683,748,770,1571,1714,1801,1813,2739,2806,3058,3349,4964,4973 'twiliosm':143,400 'twilioverifi':569,821 'twiliowhatsapp':1584,1950 'twiml':977,987,988,1004,1027,1040,1395,1406,1463,5182 'two':527 'two-factor':526 'type':554,1580,2018,2024,2033,2693 'unauthor':4370,4565 'undeliv':2291 'undelivered/failed':2255 'unknown':367,3648 'unlik':3449 'unreach':360,376,3382,3419,3452,3561 'unsolicit':4113 'unstop':3306 'unsubscrib':2397,3194,3283,3377 'updat':2095,2245,2283,2451,2523,2531,3355,4554 'upper':2394,2414,3273 'urgent':3653,3765 'url':231,1047,1117,1131,1313,1331,1408,1421,1629,1660,1663,1694,1698,1700,1898,1945,2184,2188,2211,2374,2434,2651,3643,3646,3746,3903,3974,3978,3998,4030,4043,4060,4066 'us':113,3225,3591,3623,3691 'usag':398,816,2876,3079,4753 'use':117,446,519,961,986,1046,1053,1476,1546,1740,1827,2112,2165,2716,2775,3025,3664,3712,3748,3788,4017,4058,4064,4136,4263,4352,4489,4566,4836,4857,4907,4949,5124,5191 'user':27,121,520,718,824,844,853,1501,1517,1846,1927,2029,2055,2567,2573,2591,3151,3177,3235,3288,3308,3330,3338,3363,3543,3544,3606,4085,4115,4159,4631,4737,4804,5031,5050,5068,5082,5098,5110,5125,5130,5136,5142,5148,5154,5160,5166,5172,5178,5183 'user.email':3567 'user.phone':3553 'usual':3455 'ux':4639 'v2':660,725 'valid':151,172,178,241,356,420,680,739,759,862,1084,1093,1099,1108,1150,1216,1301,1347,1441,2084,2135,2153,2157,2176,2234,2343,2441,2644,3384,3404,3852,3896,3929,3935,3957,3967,4027,4818,4865,4871,4880,4915,4922,4931,5216 'validator.validate':1130,2210,3997 'valu':536 'vari':2690 'variabl':1731,1789,1791,4457,4838,4852,4953 'verb':1005 'verif':28,452,523,533,571,620,632,655,658,664,691,700,720,729,803,830,857,912,939,3924,4577,4580,4609,4764,4792,5153,5189 'verifi':443,448,574,585,602,610,616,766,774,797,820,865,881,890,959,967,3655,3766,4567,4598,4688,4713,4743,5091 'verification.status':676 'verification.valid':681 'verify-specif':773 'verify.check':856 'verify.send':829,911 'verifychannel':558,625 'verifychannel.call':909 'verifychannel.sms':626,832,904,929 'verifyratelimit':4659,4755 'verizon':3625 'via':1473,1588,3360 'voic':11,21,494,877,892,926,982,1023,2445,2689,2705,4815,5053,5056,5062,5146 'voice-ag':5055,5061 'voicemail':1068,1265,1305,1309,1328,1354,2124 'voicerespons':1080,1163,1228,1311 'volum':2719,3659,3797,5114 'wa':4194,4229 'wait':923,3715,4728,4773,4775,4787 'warn':265,280,4917,4936,4959,5003,5028 'webhook':235,1037,1864,1883,2068,2073,2091,2168,2650,2662,3256,3853,3860,3866,3884,3902,3959,4021,4301,4862,4869,4877,5118 'welcom':1180 'whatsapp':23,497,566,567,648,1464,1471,1488,1585,1609,1612,1618,1635,1647,1669,1671,1673,1676,1678,1686,1689,1754,1765,1767,1770,1772,1780,1783,1809,1817,1852,1867,1882,1886,1893,1939,1949,1960,4069,4081,4108,4147,4275,4284,4308,4315,4345,5164 'whatsapp-specif':1816 'whatsapp.send':1951 'whatsappsess':4167 'window':1493,1512,1592,1747,1826,1908,2049,2053,4074,4097,4157,4267,4296 'within':1497,1637,1947,4124 'without':1646,2652,3129,4384,4863,4878,4929,4969 'word':3754 'work':3184,3406,3410,4100 'workflow':5100,5103,5109 'workflow-autom':5102 'wrap':2141,2170,2735,2784,3947,3961 'wrapper':1105,1145,2173,2229,2787,2873,3964,4008 'wrapper.__name__':1142 'wrong':753 'x':1125,1443,2086,2205,2646,3888,3992,4046,4052,4873 'x-forwarded-host':4051 'x-forwarded-proto':4045 'x-twilio-signatur':1124,1442,2085,2204,2645,3887,3991,4872 'xml':993,1451 'yes':3305 'your-app.com':415,1423,1428 'your-app.com/voice/outbound-message':1422 'your-app.com/voice/status':1427 'your-app.com/webhooks/twilio/status':414","prices":[{"id":"7f44a6f9-e342-4df0-b0f5-d3be3c0081f3","listingId":"d84ef35c-7868-433a-b4ad-2a96b1839fd5","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"sickn33","category":"antigravity-awesome-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T20:38:41.859Z"}],"sources":[{"listingId":"d84ef35c-7868-433a-b4ad-2a96b1839fd5","source":"github","sourceId":"sickn33/antigravity-awesome-skills/twilio-communications","sourceUrl":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/twilio-communications","isPrimary":false,"firstSeenAt":"2026-04-18T21:46:36.011Z","lastSeenAt":"2026-04-25T06:52:12.104Z"},{"listingId":"d84ef35c-7868-433a-b4ad-2a96b1839fd5","source":"skills_sh","sourceId":"sickn33/antigravity-awesome-skills/twilio-communications","sourceUrl":"https://skills.sh/sickn33/antigravity-awesome-skills/twilio-communications","isPrimary":true,"firstSeenAt":"2026-04-18T20:38:41.859Z","lastSeenAt":"2026-04-24T22:40:46.908Z"}],"details":{"listingId":"d84ef35c-7868-433a-b4ad-2a96b1839fd5","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"sickn33","slug":"twilio-communications","github":{"repo":"sickn33/antigravity-awesome-skills","stars":34997,"topics":["agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding","ai-workflows","antigravity","antigravity-skills","claude-code","claude-code-skills","codex-cli","codex-skills","cursor","cursor-skills","developer-tools","gemini-cli","gemini-skills","kiro","mcp","skill-library"],"license":"mit","html_url":"https://github.com/sickn33/antigravity-awesome-skills","pushed_at":"2026-04-25T06:33:17Z","description":"Installable GitHub library of 1,400+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and more. Includes installer CLI, bundles, workflows, and official/community skill collections.","skill_md_sha":"cfe45d4e4b0c0dde635ef08e6e8b6a56853a6fee","skill_md_path":"skills/twilio-communications/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/twilio-communications"},"layout":"multi","source":"github","category":"antigravity-awesome-skills","frontmatter":{"name":"twilio-communications","description":"\"Build communication features with Twilio: SMS messaging, voice"},"skills_sh_url":"https://skills.sh/sickn33/antigravity-awesome-skills/twilio-communications"},"updatedAt":"2026-04-25T06:52:12.104Z"}}