Este artículo actualmente está disponible solo en inglés. La traducción estará disponible próximamente.
By the end of this recipe, your Odoo 19 instance will send SMS via Twilio for order confirmations, appointment reminders, OTP codes, and abandoned-cart recovery — with delivery status tracked, opt-out handled automatically, and multi-country sender-ID compliance enforced. Skill required: Odoo developer with REST API basics. Time required: 90 minutes setup, 60 minutes testing. ECOSIRE has built this for clients running outbound campaigns to 10,000+ customers, and the recipe below is the playbook.
The trap that kills most Twilio-Odoo deployments: sending SMS without country-specific compliance. The US needs a 10DLC registered campaign or A2P pre-approval. India requires DLT registration. UAE blocks unregistered international SMS. The recipe below configures sender IDs and templates per country to stay compliant.
What you will need
- Twilio account with verified billing.
- Twilio phone number purchased for the country you're sending from. US numbers cost about $1.15/month, UK ~$1, India needs a registered Sender ID (different process).
- Odoo version: 17, 18, or 19. The native
smsmodule ships with all three but only supports Odoo's IAP (in-app-purchase) SMS. Twilio is a per-account override. - Time: 90 minutes setup, 60 minutes testing.
Step-by-step
1. Create Twilio API credentials
In Twilio Console > API keys & tokens, create a new Standard API key. Save the Account SID, API Key SID, and API Key Secret. Restrict the key to only Programmable Messaging capability.
Verification: a curl -u "API_KEY_SID:API_KEY_SECRET" https://api.twilio.com/2010-04-01/Accounts/ACCOUNT_SID/Messages.json?PageSize=1 returns the most recent message.
2. Build the Odoo SMS service override
Create a small custom module that overrides the IAP _send_sms_batch to route through Twilio:
from odoo import models, api
from odoo.exceptions import UserError
import requests
import logging
_logger = logging.getLogger(__name__)
class IAPAccount(models.Model):
_inherit = 'iap.account'
@api.model
def get_provider_credentials(self, service_name):
if service_name == 'sms':
ICP = self.env['ir.config_parameter'].sudo()
return {
'provider': ICP.get_param('sms.provider', 'iap'),
'twilio_account_sid': ICP.get_param('twilio.account_sid'),
'twilio_api_key_sid': ICP.get_param('twilio.api_key_sid'),
'twilio_api_key_secret': ICP.get_param('twilio.api_key_secret'),
'twilio_from_number': ICP.get_param('twilio.from_number'),
}
return super().get_provider_credentials(service_name)
class SMSApi(models.AbstractModel):
_inherit = 'sms.api'
@api.model
def _send_sms_batch(self, messages, delivery_reports_url=None):
creds = self.env['iap.account'].get_provider_credentials('sms')
if creds['provider'] != 'twilio':
return super()._send_sms_batch(messages, delivery_reports_url)
results = []
for msg in messages:
try:
resp = requests.post(
f"https://api.twilio.com/2010-04-01/Accounts/{creds['twilio_account_sid']}/Messages.json",
auth=(creds['twilio_api_key_sid'], creds['twilio_api_key_secret']),
data={
'To': msg['number'],
'From': creds['twilio_from_number'],
'Body': msg['content'],
'StatusCallback': delivery_reports_url,
},
timeout=30,
)
resp.raise_for_status()
data = resp.json()
results.append({'res_id': msg['res_id'], 'state': 'success', 'twilio_sid': data['sid']})
except Exception as e:
_logger.error('Twilio send failed: %s', e)
results.append({'res_id': msg['res_id'], 'state': 'server_error'})
return results
Verification: from odoo-bin shell, run self.env['mail.message'].create({'message_type':'sms','body':'Test','partner_ids':[(6,0,[1])]}) and confirm the message arrives on the test phone within 5 seconds.
3. Configure Twilio in Odoo settings
Go to Settings > Technical > Parameters > System Parameters. Add:
sms.provider=twiliotwilio.account_sid=ACxxxxxxxxxxxxxxxxxxxxxxxxxtwilio.api_key_sid=SKxxxxxxxxxxxxxxxxxxxxxxxxxtwilio.api_key_secret=xxxxxxxxxxxxxxxxxxxxxxxxxxxxtwilio.from_number=+15551234567(or your registered Sender ID)
Verification: trigger a system "Send SMS" action; the SMS reaches the test phone.
4. Set up the delivery status webhook
Twilio sends delivery callbacks (queued, sent, delivered, failed) to a URL you configure on each request. Build the webhook controller:
@http.route('/twilio/sms/status', auth='public', csrf=False, methods=['POST'])
def status(self, **kwargs):
sid = kwargs.get('MessageSid')
status = kwargs.get('MessageStatus')
error_code = kwargs.get('ErrorCode')
sms = request.env['sms.sms'].sudo().search([('twilio_sid', '=', sid)], limit=1)
if sms:
sms.write({
'state': {'queued':'process', 'sent':'process', 'delivered':'sent',
'failed':'error', 'undelivered':'error'}.get(status, 'process'),
'twilio_status': status,
'twilio_error_code': error_code,
})
return request.make_response('OK', status=200)
In your _send_sms_batch override, set 'StatusCallback': 'https://your-odoo.com/twilio/sms/status' on every send.
Verification: send to a real number, watch the Odoo SMS record progress queued > sent > delivered within 10 seconds.
5. Configure inbound for opt-out (STOP/HELP)
For US/UK SMS, the carrier requires honoring STOP, UNSTOP, HELP keywords automatically. Set up an inbound webhook:
@http.route('/twilio/sms/inbound', auth='public', csrf=False, methods=['POST'])
def inbound(self, **kwargs):
From = kwargs.get('From')
Body = (kwargs.get('Body') or '').strip().upper()
partner = request.env['res.partner'].sudo().search([
('phone', 'ilike', From[-10:]),
], limit=1)
if Body == 'STOP':
if partner:
partner.sms_optout = True
return request.make_response(
'<Response><Message>You have been unsubscribed. Reply START to resubscribe.</Message></Response>',
headers=[('Content-Type', 'application/xml')]
)
elif Body == 'START':
if partner:
partner.sms_optout = False
elif Body == 'HELP':
return request.make_response(
'<Response><Message>For help call us at +1-555-1234. Reply STOP to unsubscribe.</Message></Response>',
headers=[('Content-Type', 'application/xml')]
)
# Default: log to a CRM lead or pass to a support inbox
return request.make_response('<Response/>', headers=[('Content-Type', 'application/xml')])
In Twilio Console > Phone Numbers > your number > Messaging Configuration > A message comes in: set Webhook = https://your-odoo.com/twilio/sms/inbound, HTTP POST.
Verification: text STOP from a real phone; Odoo flips sms_optout=True on the matching contact and the customer gets an unsubscribe confirmation.
6. Wire to automated actions
Use Odoo's automation engine to trigger SMS on key events:
- New SO confirmed: SMS the customer with the order number.
- Picking shipped: SMS the tracking number.
- Invoice overdue 7 days: SMS a payment reminder.
Go to Settings > Technical > Automation > Automated Actions > Create. Set Model = sale.order, Trigger = On Save, Apply on = state = sale and last_state != sale. Action Type = "Send SMS". Body = Hi {{ object.partner_id.name }}, your order {{ object.name }} ($${{ object.amount_total }}) is confirmed. Track: yourdomain.com/orders/{{ object.id }}.
Don't forget to filter out opted-out partners: add a domain ('partner_id.sms_optout','=',False).
Verification: confirm a new SO; the customer receives the SMS within 5 seconds.
7. Configure 10DLC / A2P registration (US-specific)
For volume above 1,000 SMS/day in the US, register a 10DLC campaign in Twilio Console > Trust Hub > A2P 10DLC. Submit business info, message samples, opt-in flow. Approval takes 1 to 5 business days.
Verification: campaign status shows "Active" and your throughput limit increases from 30/min to 4,500/min for tier-3 brands.
8. Add OTP support (high-priority verification)
For login OTP, signup verification, password reset — use Twilio's Verify API instead of raw Messaging:
def _send_otp(self, phone):
resp = requests.post(
f"https://verify.twilio.com/v2/Services/{verify_service_sid}/Verifications",
auth=(api_key_sid, api_key_secret),
data={'To': phone, 'Channel': 'sms'},
timeout=15,
)
return resp.json()['sid']
def _check_otp(self, phone, code):
resp = requests.post(
f"https://verify.twilio.com/v2/Services/{verify_service_sid}/VerificationCheck",
auth=(api_key_sid, api_key_secret),
data={'To': phone, 'Code': code},
timeout=15,
)
return resp.json().get('valid', False)
Twilio Verify handles fraud filtering, retries, and abuse detection automatically.
Verification: trigger an OTP send to your phone, receive the 6-digit code, validate via the check endpoint.
Common mistakes
- Sending without 10DLC registration in the US. Carriers filter unregistered traffic aggressively.
- Ignoring STOP keyword. FTC fine territory in the US.
- Hardcoding the from number. Different countries need different sender IDs (US 10DLC, UK alphanumeric, India DLT-registered).
- Sending OTPs via Messaging API instead of Verify API. Verify has fraud protection; raw Messaging does not.
- Not respecting timezones. SMS at 3 AM customer local time triggers complaints. Always check
partner.tzand gate sends to 9 AM to 8 PM local.
Going further
Multi-channel fallback: try WhatsApp first, fall back to SMS if WhatsApp delivery fails. Build with the messaging engine in Twilio. Twilio's Conversations API can orchestrate this automatically with a single integration point.
Two-way conversations: route inbound to a mail.channel so support reps can chat with customers from Odoo Discuss. Each customer phone number maps to a channel; reps reply in Discuss and Twilio sends the SMS back.
Flow Builder integration: Twilio Studio Flow can drive complex IVR/SMS flows with branching logic. Trigger from Odoo via the Execution API. Useful for appointment-confirmation flows where customers reply YES/NO/RESCHEDULE.
MMS for delivery photos: send proof-of-delivery photos via MMS instead of SMS. Costs 3-4x more but customer satisfaction jumps. Pair with carrier APIs that return delivery photos automatically.
Voice fallback for missed deliveries: if SMS delivery fails (invalid number), trigger an outbound voice call via Twilio Voice with a recorded message. Catches cases where SMS was the wrong channel.
Compliance archiving: archive every sent and received SMS in Odoo for 7+ years. Required in some jurisdictions (financial services, healthcare). Pair with retention policy enforcement.
Segment by velocity: customers who reply quickly are higher-engagement. Tag them and send more campaigns. Slow responders get fewer messages to preserve sending reputation.
Per-carrier delivery analytics: Twilio reports delivery rates by carrier. Some carriers throttle aggressively; route through different channels per carrier when delivery is critical.
Toll-free messaging: for high-volume B2C, get a toll-free number registered with carriers for higher throughput than a regular 10DLC.
Number warming: when launching a new sender number, ramp volume gradually over 7-14 days. Carriers' anti-spam systems flag sudden bursts; warming avoids the flag.
MMS group send: send the same image to 100 customers in one API call (technically multiple calls but Twilio handles the fanout). Useful for promotional broadcasts.
Conversational AI: integrate Twilio with Claude or GPT to auto-reply to common inbound queries. Escalate to human only when AI confidence is low.
Number portability: when changing providers (e.g., switching from another to Twilio), port your existing number rather than getting a new one. Customers know your number; switching it confuses them.
For full SMS + WhatsApp + Email omnichannel deployment including campaign UI, opt-in management, and analytics, ECOSIRE custom Odoo development ships fixed-price engagements. Pair this with how to integrate WhatsApp Business API with Odoo for the WhatsApp side.
Frequently Asked Questions
What does Twilio SMS cost?
US outbound: $0.0079/segment. UK outbound: $0.04/segment. India: $0.0072 + DLT fees. A "segment" is 160 GSM-7 chars or 70 Unicode chars. Long messages split into multiple segments.
Can I send from a short code?
Yes, but short codes cost $1,000+ in the US to provision. For most ECOSIRE clients, a 10DLC long code is sufficient.
How do I handle customers in many countries?
Buy a Twilio number in each major country and configure the from_number per partner based on partner.country_id. Otherwise carriers filter your traffic as cross-border.
What if Twilio goes down?
Configure a fallback provider (MessageBird, Plivo, Vonage) and a health check that flips Odoo's sms.provider parameter automatically. We've built this for clients running mission-critical SMS.
For complex multi-region SMS deployments including 10DLC registration, DLT compliance for India, and global routing optimization, our Odoo integration team handles the full rollout. Or read how to integrate WhatsApp Business API with Odoo for the increasingly preferred WhatsApp channel.
Escrito por
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
ECOSIRE
Transforme su negocio con Odoo ERP
Implementación, personalización y soporte experto de Odoo para optimizar sus operaciones.
Artículos relacionados
Cómo agregar un botón personalizado a una vista de formulario de Odoo (2026)
Agregue botones de acción personalizados a las vistas de formulario de Odoo 19: método de acción de Python, herencia de vistas, visibilidad condicional, cuadros de diálogo de confirmación. Probado en producción.
Cómo agregar un campo personalizado en Odoo sin Studio (2026)
Agregue campos personalizados a través de un módulo personalizado en Odoo 19: herencia de modelo, extensión de vista, campos calculados, decisiones de tienda/no tienda. Código primero, controlado por versiones.
Cómo agregar un informe personalizado en Odoo usando un diseño externo
Cree un informe PDF con su marca en Odoo 19 usando web.external_layout: plantilla QWeb, formato de papel, enlace de acción. Con logotipo impreso + anulaciones de pie de página.