Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Build communication features with Twilio: SMS messaging, voice calls, WhatsApp Business API, and user verification (2FA). Covers the full spectrum from simple notifications to complex IVR systems and multi-channel authentication. Critical focus on compliance, rate limits, and error handling. Use when: twilio, send SMS, text message, voice call, phone verification.
.claude/skills/davila7-twilio-communications/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-16 | ✗→✓ | ▲ Improved | 185% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 76% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 59% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 42% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 72% | 0% |
Basic pattern for sending SMS messages with Twilio. Handles the fundamentals: phone number formatting, message delivery, and delivery status callbacks.
Key considerations:
When to use: 'Sending notifications to users', 'Transactional messages (order confirmations, shipping)', 'Alerts and reminders']
pythonfrom twilio.rest import Client from twilio.base.exceptions import TwilioRestException import os import re class TwilioSMS: """ SMS sending with proper error handling and validation. """ def __init__(self): self.client = Client( os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"] ) self.from_number = os.environ["TWILIO_PHONE_NUMBER"] def validate_e164(self, phone: str) -> bool: """Validate phone number is in E.164 format.""" pattern = r'^\+[1-9]\d{1,14}$' return bool(re.match(pattern, phone)) def send_sms( self, to: str, body: str, status_callback: str = None ) -> dict: """ Send an SMS message. Args: to: Recipient phone number in E.164 format body: Message text (160 chars = 1 segment) status_callback: URL for delivery status webhooks Returns: Message SID and status """ # Validate phone number format if not self.validate_e164(to): return { "success": False, "error": "Phone number must be in E.164 format (+1234567890)" } # Check message length (warn about segmentation) segment_count = (len(body) + 159) // 160 if segment_count > 1: print(f"Warning: Message will be sent as {segment_count} segments") try: message = self.client.messages.create( to=to, from_=self.from_number, body=body, status_callback=status_callback ) return { "success": True, "message_sid": message.sid, "status": message.status, "segments": segment_count } except TwilioRestException as e: return self._handle_error(e) def _handle_error(self, error: Twilio
Use Twilio Verify for phone number verification and 2FA. Handles code generation, delivery, rate limiting, and fraud prevention.
Key benefits over DIY OTP:
Google found SMS 2FA blocks "100% of automated bots, 96% of bulk phishing attacks, and 76% of targeted attacks."
When to use: 'User phone number verification at signup', 'Two-factor authentication (2FA)', 'Password reset verification', 'High-value transaction confirmation']
pythonfrom twilio.rest import Client from twilio.base.exceptions import TwilioRestException import os from enum import Enum from typing import Optional class VerifyChannel(Enum): SMS = "sms" CALL = "call" EMAIL = "email" WHATSAPP = "whatsapp" class TwilioVerify: """ Phone verification with Twilio Verify. Never store OTP codes - Twilio handles it. """ def __init__(self, verify_service_sid: str = None): self.client = Client( os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"] ) # Create a Verify Service in Twilio Console first self.service_sid = verify_service_sid or os.environ["TWILIO_VERIFY_SID"] def send_verification( self, to: str, channel: VerifyChannel = VerifyChannel.SMS, locale: str = "en" ) -> dict: """ Send verification code to phone/email. Args: to: Phone number (E.164) or email channel: SMS, call, email, or whatsapp locale: Language code for message Returns: Verification status """ try: verification = self.client.verify \ .v2 \ .services(self.service_sid) \ .verifications \ .create( to=to, channel=channel.value, locale=locale ) return { "success": True, "status": verification.status, # "pending" "channel": channel.value, "valid": verification.valid } except TwilioRestException as e: return self._handle_verify_error(e) def check_verification(self, to: str, code: str) -> dict: """ Check if verification code is correct. Args: to: Phone number or email that received code code: The code entered by user R
Build Interactive Voice Response (IVR) systems using TwiML. TwiML (Twilio Markup Language) is XML that tells Twilio what to do when receiving calls.
Core TwiML verbs:
Key insight: Twilio makes HTTP request to your webhook, you return TwiML, Twilio executes it. Stateless, so use URL params or sessions.
When to use: 'Phone menu systems (press 1 for sales...)', 'Automated customer support', 'Appointment reminders with confirmation', 'Voicemail systems']
pythonfrom flask import Flask, request, Response from twilio.twiml.voice_response import VoiceResponse, Gather from twilio.request_validator import RequestValidator import os app = Flask(__name__) def validate_twilio_request(f): """Decorator to validate requests are from Twilio.""" def wrapper(*args, **kwargs): validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"]) # Get request details url = request.url params = request.form.to_dict() signature = request.headers.get("X-Twilio-Signature", "") if not validator.validate(url, params, signature): return "Invalid request", 403 return f(*args, **kwargs) wrapper.__name__ = f.__name__ return wrapper @app.route("/voice/incoming", methods=["POST"]) @validate_twilio_request def incoming_call(): """Handle incoming call with IVR menu.""" response = VoiceResponse() # Gather digits with timeout gather = Gather( num_digits=1, action="/voice/menu-selection", method="POST", timeout=5 ) gather.say( "Welcome to Acme Corp. " "Press 1 for sales. " "Press 2 for support. " "Press 3 to leave a message." ) response.append(gather) # If no input, repeat response.redirect("/voice/incoming") return Response(str(response), mimetype="text/xml") @app.route("/voice/menu-selection", methods=["POST"]) @validate_twilio_request def menu_selection(): """Route based on menu selection.""" response = VoiceResponse() digit = request.form.get("Digits", "") if digit == "1": # Transfer to sales response.say("Connecting you to sales.") response.dial(os.environ["SALES_PHONE"]) elif digit == "2": # Transfer to support response.say("Connecting you to support.") response.dial(os.environ["SUPPORT_PHONE"]) elif digit == "3": # Voicemail response.say("Please leave a message after
| Issue | Severity | Solution | |-------|----------|----------| | Issue | high | ## Track opt-out status in your database | | Issue | medium | ## Implement retry logic for transient failures | | Issue | high | ## Register for A2P 10DLC (US requirement) | | Issue | critical | ## ALWAYS validate the signature | | Issue | high | ## Track session windows per user | | Issue | critical | ## Never hardcode credentials | | Issue | medium | ## Implement application-level rate limiting too |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 11,105 | 11,407 | +3% | 1 | 1 | 0% | 2,540 | 4,481 | +76% | 0 | 0 | — |
case-02 | fail→fail | 18,849 | 17,375 | -8% | 1 | 1 | 0% | 3,474 | 5,587 | +61% | 0 | 0 | — |
case-03 | pass→pass | 14,577 | 10,332 | -29% | 1 | 1 | 0% | 2,593 | 4,131 | +59% | 0 | 0 | — |
case-04 | pass→pass | 15,192 | 9,466 | -38% | 1 | 1 | 0% | 2,699 | 3,825 | +42% | 0 | 0 | — |
case-05 | pass→pass | 12,469 | 10,385 | -17% | 1 | 1 | 0% | 2,466 | 4,235 | +72% | 0 | 0 | — |
case-06 | pass→pass | 11,762 | 7,993 | -32% | 1 | 1 | 0% | 2,073 | 3,616 | +74% | 0 | 0 | — |
case-07 | pass→pass | 10,133 | 6,559 | -35% | 1 | 1 | 0% | 1,914 | 3,305 | +73% | 0 | 0 | — |
case-08 | pass→pass | 17,634 | 20,078 | +14% | 1 | 1 | 0% | 2,872 | 5,775 | +101% | 0 | 0 | — |
case-09 | pass→pass | 19,662 | 20,065 | +2% | 1 | 1 | 0% | 3,218 | 5,784 | +80% | 0 | 0 | — |
case-10 | pass→pass | 10,056 | 6,757 | -33% | 1 | 1 | 0% | 1,896 | 3,396 | +79% | 0 | 0 | — |
case-11 | pass→pass | 5,626 | 5,589 | -1% | 1 | 1 | 0% | 1,001 | 3,312 | +231% | 0 | 0 | — |
case-12 | pass→pass | 8,691 | 4,570 | -47% | 1 | 1 | 0% | 1,620 | 2,912 | +80% | 0 | 0 | — |
case-13 | pass→pass | 11,984 | 12,517 | +4% | 1 | 1 | 0% | 2,148 | 4,396 | +105% | 0 | 0 | — |
case-14 | pass→pass | 11,228 | 10,008 | -11% | 1 | 1 | 0% | 2,202 | 4,029 | +83% | 0 | 0 | — |
case-15 | pass→pass | 16,158 | 13,383 | -17% | 1 | 1 | 0% | 1,912 | 4,274 | +124% | 0 | 0 | — |
case-16 | fail→pass | 6,838 | 3,718 | -46% | 1 | 1 | 0% | 1,022 | 2,911 | +185% | 0 | 0 | — |
case-17 | pass→pass | 10,644 | 8,086 | -24% | 1 | 1 | 0% | 1,989 | 3,753 | +89% | 0 | 0 | — |
case-18 | pass→pass | 5,754 | 4,327 | -25% | 1 | 1 | 0% | 1,060 | 2,881 | +172% | 0 | 0 | — |
case-19 | pass→pass | 3,654 | 4,458 | +22% | 1 | 1 | 0% | 545 | 2,889 | +430% | 0 | 0 | — |
case-20 | pass→pass | 10,508 | 9,707 | -8% | 1 | 1 | 0% | 1,938 | 4,248 | +119% | 0 | 0 | — |
case-21 | pass→pass | 10,799 | 10,694 | -1% | 1 | 1 | 0% | 2,021 | 4,211 | +108% | 0 | 0 | — |
case-22 | pass→pass | 11,127 | 9,275 | -17% | 1 | 1 | 0% | 2,174 | 3,854 | +77% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +5 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.