diff --git a/biscuit/core/settings.py b/biscuit/core/settings.py
index a73da45c99a63541f686be94f4a04dae4f262028..0230cf03e70bba8a8c60f2a32269785b93c679d5 100644
--- a/biscuit/core/settings.py
+++ b/biscuit/core/settings.py
@@ -324,4 +324,12 @@ CRON_CLASSES = [
 
 ANONYMIZE_ENABLED = _settings.get('maintenance.anonymisable', True)
 
+if _settings.get('2fa.twilio.sid', None):
+    MIDDLEWARE.insert(MIDDLEWARE.index('django_otp.middleware.OTPMiddleware')+1, 'two_factor.middleware.threadlocals.ThreadLocals')
+    TWILIO_SID = _settings.get('2fa.twilio.sid')
+    TWILIO_TOKEN = _settings.get('2fa.twilio.token')
+    TWILIO_CALLER_ID = _settings.get('2fa.twilio.callerid')
+    TWO_FACTOR_CALL_GATEWAY = 'two_factor.gateways.twilio.gateway.Twilio'
+    TWO_FACTOR_SMS_GATEWAY = 'two_factor.gateways.twilio.gateway.Twilio'
+
 _settings.populate_obj(sys.modules[__name__])
diff --git a/biscuit/core/urls.py b/biscuit/core/urls.py
index 82259c9a3c890e1763d2080efc512f417fdd85b0..35431ae22e4bb317263d4c7d837d6989d6346a6c 100644
--- a/biscuit/core/urls.py
+++ b/biscuit/core/urls.py
@@ -38,6 +38,11 @@ urlpatterns = [
     path('', include(tf_urls))
 ]
 
+# Add URLs for optional features
+if hasattr(settings, 'TWILIO_ACCOUNT_SID'):
+    from two_factor.gateways.twilio.urls import urlpatterns as tf_twilio_urls  # noqa
+    urlpatterns += [path('', include(tf_twilio_urls))]
+
 # Serve javascript-common if in development
 if settings.DEBUG:
     urlpatterns += static('/javascript/',
diff --git a/pyproject.toml b/pyproject.toml
index c4b7bea600fe8c204eca3b1c3370fcd9faa9d358..cde81ad91a14fa232df9caa3ee57c117da800d21 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -54,9 +54,11 @@ django-otp = "^0.7.4"
 django-formtools = "^2.1"
 django-two-factor-auth = "^1.9"
 django-otp-yubikey = '^0.5.2'
+twilio = { version = "^6.33", optional = true }
 
 [tool.poetry.extras]
 ldap = ["django-auth-ldap"]
+twilio = ["twilio"]
 
 [tool.poetry.dev-dependencies]
 sphinx = "^2.1"