Building a production SMS system has multiple technical and regulatory hurdles. While sending a single SMS is straightforward, creating a scalable, compliant, and reliable system involves architecting for deliverability, managing costs, handling edge cases, and navigating complex regional requirements.
Choosing your sending identity
Your sending identity, which is the number that appears as the sender, significantly impacts deliverability, cost, and user trust. Each option serves different use cases and comes with distinct tradeoffs that can make or break your notification strategy.
Long codes (10DLC)
Long codes (10DLC) are standard 10-digit phone numbers that historically were meant for person-to-person texts. Today, 10DLC refers to sanctioned use of these numbers for Application-to-Person (A2P) messaging.
Key characteristics of long codes:
- Cost: $1-10/month to provision.
- Setup time: Usually instant.
- Throughput: ~1 message/second unregistered, up to 60+ messages/minute registered.
- Trust score factors: Business age, website quality, industry vertical.
- Best for: Local alerts, two-factor codes, low-to-medium volume notifications.
The registration process through The Campaign Registry is now mandatory for US A2P messaging. Without registration, you'll face severe throttling and filtering. But even with registration, long codes can't match the performance of premium options.
Shortcodes
Shortcodes represent the premium tier of SMS sending. These 5-6 digit numbers are explicitly designed for high-volume A2P messaging, offering throughput of 100+ messages per second. Because carriers pre-approve shortcode traffic, messages bypass the aggressive spam filtering that plagues long codes.
The trade-offs are significant:
- Cost: $500-1,500/month (vanity codes cost more. E.g., Target uses 827438).
- Approval time: 8-12 weeks in the U.S.
- Application requirements: Use case documentation, example messages, opt-out wording.
- Geographic limitation: Only work nationally, need separate codes per country.
- No voice capability: SMS/MMS only.
For large-scale operations sending millions of messages, the reliability and throughput make shortcodes essential infrastructure. Marketing campaigns particularly benefit from the memorability factor. "Text JOIN to 12345" is far easier to remember than a 10-digit number.
Toll-free numbers
Toll-free numbers occupy a middle ground that many developers overlook:
- Moderate throughput (~3-10 messages/second).
- Simpler verification than shortcodes (days vs weeks).
- Free for recipients in U.S./Canada.
- Support both SMS and voice.
- Cost $10-50/month.
- Convey nationwide legitimacy.
These work particularly well for customer service use cases where two-way communication matters and you want to project a professional image without shortcode costs.
Integration approaches and third-party services
The SMS ecosystem offers multiple integration levels, each with different complexity and control tradeoffs. Understanding these options helps you choose the right abstraction level for your needs.
Direct carrier integration
Direct carrier integration provides maximum control but demands significant engineering investment. You'll need to:
- Implement SMPP protocol connections.
- Negotiate separate agreements with each carrier.
- Manage multiple APIs and protocols.
- Handle number portability databases.
- Build complex routing logic.
- Maintain 24/7 operations team.
This approach only makes sense if you're sending millions of messages monthly and have dedicated telecommunications expertise on staff.
SMS aggregators
SMS aggregators like Twilio, Vonage, and Plivo hit the sweet spot for most applications. They provide a single API for global reach, handle carrier routing automatically, and offer pay-per-message pricing with no upfront infrastructure costs.
Here's what a basic Twilio integration looks like:
const twilio = require("twilio");
const client = twilio(accountSid, authToken);
async function sendSMS(to, body) {
try {
const message = await client.messages.create({
body: body,
from: "+15551234567", // Your Twilio number
to: to,
statusCallback: "https://yourapp.com/sms/status",
});
console.log(`Message sent: ${message.sid}`);
return message.sid;
} catch (error) {
console.error("SMS send failed:", error);
throw error;
}
}
Aggregators alone don't solve higher-level concerns. You still need to handle:
- User preference management.
- Template versioning and localization.
- Multi-channel orchestration.
- Delivery tracking across channels.
- Cost optimization and routing.
Notification infrastructure platforms
Notification infrastructure platforms like Knock offer the highest abstraction level. They sit above SMS providers, offering SMS integrations for major providers and managing the entire notification lifecycle.
Benefits of using notification infrastructure:
- Unified API: Single interface for SMS, email, push, in-app.
- Automatic failover: Falls back to other channels if SMS fails.
- Built-in preferences: Users can manage their notification settings.
- Template management: Visual editors, version control, A/B testing.
- Provider abstraction: Switch SMS providers without code changes.
With Knock, for example, you trigger workflows rather than individual messages:
const { Knock } = require("@knocklabs/node");
const knock = new Knock(process.env.KNOCK_API_KEY);
await knock.workflows.trigger("order_shipped", {
recipients: ["user_123"],
data: {
orderId: "12345",
trackingUrl: "https://track.example.com/12345",
},
});
This single call can then result in an in-app notification, push notification, email, and SMS based on user preferences and channel availability.
Location-specific considerations and compliance
SMS regulations vary dramatically by country, and non-compliance can result in hefty fines, blocked messages, or legal action. Building a compliant system requires understanding both the letter and spirit of these regulations.
United States: TCPA and 10DLC requirements
The Telephone Consumer Protection Act (TCPA) sets strict requirements for SMS marketing in the U.S.
- Explicit consent for promotional messages.
- Clear disclosure of message frequency and charges.
- Time restrictions: 8 AM - 9 PM recipient's local time.
- Mandatory opt-out instructions.
- Penalties: Up to $1,500 per violation.
The 10DLC registration adds another layer. You receive a trust score that changes over time depending on the success of your messaging. For instance, a low trust score might limit you to 2,000 daily messages initially, whereas senders with a high trust score can send 100,000 daily messages.
European Union: GDPR and ePrivacy
GDPR treats phone numbers as personal data, requiring careful handling:
- Lawful basis required: Either explicit consent or legitimate interest.
- Right to erasure: Must delete phone numbers upon request.
- Data portability: Users can request their SMS history.
- Audit requirements: Maintain consent logs with timestamps and IP addresses.
- Data localization: Some member states require EU storage.
Beyond that, each country has quirks you must accommodate. France doesn’t allow marketing SMS on Sundays or holidays, and you can only send them from 8 AM to 10 PM on other days. Germany requires double opt-in for marketing messages.
Building compliance into your architecture
Compliance can't be an afterthought. Here's a basic consent management schema:
CREATE TABLE sms_consent (
user_id UUID PRIMARY KEY,
phone_number VARCHAR(20),
promotional_consent BOOLEAN DEFAULT FALSE,
promotional_consent_date TIMESTAMP,
promotional_consent_ip INET,
promotional_consent_language TEXT,
transactional_consent BOOLEAN DEFAULT TRUE,
timezone VARCHAR(50),
country_code VARCHAR(2),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE sms_opt_outs (
phone_number VARCHAR(20) PRIMARY KEY,
opted_out_at TIMESTAMP,
opt_out_method VARCHAR(50), -- 'SMS_STOP', 'WEB', 'SUPPORT'
opt_out_campaign VARCHAR(100),
reason TEXT
);
Your sending logic must check these tables before every message. Implement automatic STOP handling, so when users reply with STOP, UNSUBSCRIBE, or CANCEL, you can immediately process the opt-out and send a confirmation.
Building for deliverability
High deliverability requires both technical excellence and reputation management. Carriers use algorithms to detect and block spam, and once your number is flagged, recovery can take weeks. The challenge isn't just avoiding obvious spam triggers; it's understanding the subtle patterns that modern machine learning systems detect.
Watch out for these content red flags:
- Generic URL shorteners (like bit.ly).
- All caps text (except single words for emphasis).
- Special characters used excessively (!!!, $$$, ...).
- Suspicious phrases ("Act now", "Limited time", "Risk-free").
- Inconsistent sender identification.
- Generic, non-personalized content.
Rate limiting serves as your first line of defense against both carrier blocking and runaway costs. Implement multiple layers: global limits prevent overwhelming carriers, per-recipient limits prevent harassment complaints, and campaign limits control your budget. The key is making these limits intelligent rather than static.
Your retry strategy must adapt to different failure types:
- Temporary failures (carrier congestion): Exponential backoff starting at 30 seconds.
- Rate limits: Wait the specified time, then resume.
- Invalid numbers: Never retry, add to suppression list.
- Content blocks: Modify message and retry once.
- Opt-outs: Never retry, permanent block.
Monitor your sender reputation continuously. Track delivery rates by carrier, time of day, and message type. A sudden drop from 95% to 85% delivery might seem minor, but it's often a warning of bigger problems ahead. Set up alerts for anomalies and investigate immediately, as the longer a problem persists, the harder it becomes to fix.
Managing user preferences and consent
User preference management builds trust and optimizes engagement. A well-designed preference system reduces opt-outs, improves delivery rates, and ultimately saves money by not sending unwanted messages.
Start with granular preference categories. Rather than a binary "SMS on/off" toggle, offer users control over what types of messages they receive and when. This prevents the all-too-common scenario where users who only want to stop marketing messages end up opting out entirely, missing important transactional alerts.
Here's a preference model that balances user control with operational needs:
CREATE TABLE user_preferences (
user_id UUID PRIMARY KEY,
-- Channel preferences
sms_enabled BOOLEAN DEFAULT TRUE,
sms_quiet_hours_start TIME,
sms_quiet_hours_end TIME,
-- Category preferences
security_alerts_sms BOOLEAN DEFAULT TRUE, -- Can't be disabled
order_updates_sms BOOLEAN DEFAULT TRUE,
marketing_sms BOOLEAN DEFAULT FALSE,
product_updates_sms BOOLEAN DEFAULT TRUE,
-- Frequency controls
max_marketing_per_week INTEGER DEFAULT 2,
min_hours_between_sms INTEGER DEFAULT 24,
-- Consent tracking
marketing_consent_date TIMESTAMP,
marketing_consent_ip INET,
marketing_consent_method VARCHAR(50),
updated_at TIMESTAMP DEFAULT NOW()
);
The consent capture process must be bulletproof. When users opt in to SMS, record not just their consent but the entire context: timestamp, IP address, the exact language they agreed to, and the method of consent. This audit trail protects you during compliance reviews and helps resolve disputes.
Double opt-in workflows provide the strongest consent verification:
- User submits phone number on website.
- System sends verification code via SMS.
- User enters code to confirm ownership.
- System sends welcome message with clear opt-out instructions.
- Consent is recorded with full context.

A diagram of a double opt-in flow
Handle opt-outs gracefully across all channels. When someone texts STOP, they expect to stop receiving messages immediately, not after your next system sync. Implement real-time opt-out processing:
async function handleIncomingSMS(from, body) {
const normalizedBody = body.trim().toUpperCase();
const optOutKeywords = ["STOP", "UNSUBSCRIBE", "CANCEL", "END", "QUIT"];
if (optOutKeywords.includes(normalizedBody)) {
// Immediately block future sends
await redis.set(`sms:optout:${from}`, Date.now());
// Update database
await db.query(
"INSERT INTO sms_opt_outs (phone_number, opted_out_at, method) VALUES ($1, $2, $3)",
[from, new Date(), "SMS_REPLY"],
);
// Send confirmation (required by CTIA)
await sendSMS(
from,
"You have been unsubscribed from SMS. Reply START to resubscribe.",
);
// Sync with other systems
await syncOptOutToMarketingPlatform(from);
await knockClient.users.updatePreferences(userId, { sms: false });
}
}
Finally, respect preferences consistently across all systems. Nothing frustrates users more than opting out of SMS only to receive messages from a different department or system that didn't get the memo. Centralize preference management and ensure all sending systems check the same source of truth before dispatching messages.