Build-an-X for SMS-flow tests - uses Twilio Magic Numbers (`+15005550006` valid recipient, `+15005550001` invalid number, `+15005550002` cannot route, `+15005550003` international restriction, etc.) and Test Credentials for safe assertion-only Twilio interactions; covers segment-counting (GSM-7 vs UCS-2 encoding); rate-limit + opt-out keyword (STOP / HELP / UNSUBSCRIBE) handling; alphanumeric sender vs short-code vs 10DLC differences. Use when authoring tests for any Twilio-backed SMS flow.
77
97%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Twilio is the dominant SMS provider for B2B SaaS; this skill targets Twilio test patterns. The principles transfer to other providers (Vonage, MessageBird, Plivo) with provider-specific sandbox patterns.
The core challenge: SMS sends cost money + actually deliver to real phones. Test patterns avoid both via:
accountSid + authToken for
testing; calls succeed/fail per the test pattern but never
actually deliver+15005550000 - +15005550009 range with documented behaviorPer twilio.com/docs/iam/test-credentials, every Twilio account has two credential pairs:
| Credential | Purpose |
|---|---|
Live (AccountSID, AuthToken) | Real sends; cost money; deliver to real phones |
Test (AccountSID, AuthToken) | Test sends; never deliver; respond per Magic-Number pattern |
In CI, set the SDK to use Test Credentials:
from twilio.rest import Client
client = Client(
os.environ["TWILIO_TEST_ACCOUNT_SID"],
os.environ["TWILIO_TEST_AUTH_TOKEN"],
)Per twilio.com/docs/iam/test-credentials#magic-phone-numbers, the canonical test numbers (current, verify against live docs):
| Number | Behavior |
|---|---|
+15005550006 | Valid recipient; SMS send succeeds |
+15005550001 | Invalid number; returns 400 |
+15005550002 | Cannot route to number; returns 400 |
+15005550003 | International restriction (when sending from US-only number); returns 400 |
+15005550004 | Number is blacklisted; returns 400 |
+15005550009 | Sender number cannot send SMS; returns 400 |
For sender numbers, similar magic numbers exist (e.g.,
+15005550000 is "not owned by your account").
def test_send_2fa_code_to_valid_number():
client = twilio_test_client()
message = client.messages.create(
from_="+15005550006", # valid sender
to="+15005550006", # valid recipient
body="Your code is: 123456",
)
assert message.sid is not None
assert message.status in ["queued", "sent"]Note: Test Credentials don't support messaging_service_sid
sends - use direct from_ numbers only in test mode.
def test_invalid_number_raises():
client = twilio_test_client()
with pytest.raises(twilio.base.exceptions.TwilioRestException) as exc:
client.messages.create(
from_="+15005550006",
to="+15005550001", # invalid
body="test",
)
assert exc.value.status == 400SMS messages over 160 characters split into multiple segments. Encoding affects the per-segment limit:
| Encoding | Per-segment chars | Per-segment with concatenation header |
|---|---|---|
| GSM-7 | 160 | 153 |
| UCS-2 (Unicode) | 70 | 67 |
Including a single emoji or non-GSM character forces UCS-2, quartering the per-segment capacity.
def test_message_segments_correctly():
# 160-char ASCII message → 1 segment
msg = "x" * 160
assert calculate_segments(msg) == 1
# 161-char ASCII → 2 segments (first segment now 153)
msg = "x" * 161
assert calculate_segments(msg) == 2
# ASCII + 1 emoji → UCS-2; 70-char limit
msg = "x" * 70 + "🎉"
assert calculate_segments(msg) == 2 # one emoji forces multi-segmentFor calculate_segments, libraries exist per language (e.g.,
split-sms for Node, smssegment for Python). Validate against
Twilio's actual segment count via the API after a send (Twilio
returns num_segments on the message resource).
Twilio enforces per-account + per-sender rate limits. Tests shouldn't hit them in normal use, but for resilience:
def test_rate_limit_handling(client):
# Configure app for low-rate scenario
sender = mock_rate_limited_response()
response = my_app.send_sms(to="+15005550006", body="test")
# Verify retry-with-backoff or graceful queue:
assert response.status == "queued_for_retry"Mock the Twilio response (HTTP 429) at the SDK boundary to test without hitting actual rate limits.
US carriers (and CTIA guidelines) require handling of these inbound keywords:
| Keyword | Required action |
|---|---|
| STOP, STOPALL, UNSUBSCRIBE, CANCEL, END, QUIT | Stop sending; reply confirmation |
| HELP, INFO | Reply with help text + opt-out instructions |
| START, YES, UNSTOP | Resume sending after a previous STOP |
Test pattern: simulate an inbound SMS webhook from Twilio:
def test_stop_keyword_unsubscribes_user(client):
# Simulate Twilio inbound webhook
response = client.post(
"/webhooks/twilio/sms",
data={
"From": "+15555551234",
"To": "+15005550006",
"Body": "STOP",
"MessageSid": "SM1234567890",
},
)
assert response.status_code == 200
assert "<Response>" in response.text # TwiML response
assert "unsubscribed" in response.text.lower()
# Verify user is unsubscribed:
user = User.objects.get(phone="+15555551234")
assert user.sms_subscribed is FalseDifferent sender types have different requirements:
| Type | Use | Cost | Throughput |
|---|---|---|---|
| 10DLC (10-digit long code) | US transactional + marketing | low | 1 - 100 MPS depending on tier |
| Short code (5-6 digit) | high-volume marketing | high | 100+ MPS |
| Alphanumeric sender | International only (no US) | low | varies by country |
| Toll-free | US transactional | low | 3 MPS |
Test that the app uses the correct sender type per geography:
def test_us_recipient_uses_10dlc_sender():
sent_message = capture_sms_send(to="+15555551234")
assert is_10dlc_number(sent_message.from_)
def test_international_uses_alphanumeric():
sent_message = capture_sms_send(to="+447700900000") # UK
assert sent_message.from_ == "MyBrand" # alphanumeric IDFor each SMS flow:
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Send to real numbers in CI | Cost + delivery to actual phones | Twilio Test Credentials + Magic Numbers (Steps 1 - 2) |
| Skip emoji / Unicode tests | Segment count quadruples; budget surprise + truncation | UCS-2 segment tests (Step 5) |
| Skip STOP-keyword test | Compliance failure (CTIA, A2P 10DLC) + carrier delisting | Inbound webhook test (Step 7) |
Hardcode from_ | Sender-type mismatch per geography | Per-recipient sender selection (Step 8) |
| Use Magic Numbers in production code paths | Magic Numbers are reserved; production sends fail | Magic Numbers ONLY in test mode |
email-flow-test-author -
sister channel: opt-out + bounce patterns rhymewebhook-delivery-tester -
companion: STOP-keyword webhook is a webhook that needs verificationpush-notification-test-author - sister channel