SMS delivery
Customize SMS delivery process.
Overview
SuperTokens sends SMS in different authentication scenarios.
SMS delivery is configured by the Passwordless recipe. Phone OTP can be used as an MFA factor, but the MFA recipe
does not expose a separate SMS-delivery configuration.
The following page shows you how to configure the SMS delivery method and adjust the content that gets sent to your users.
Delivery methods
Default method
If you provide no configuration for SMS delivery, the Passwordless recipe uses the backend SDK’s built-in service at
https://api.supertokens.com/0/services/sms. This applies whether the Core is self-hosted or managed.
Twilio
Using this method, you can provide your own Twilio account details to the backend SDK, and the SMS is sent using those.
import supertokens from "supertokens-node";
import Passwordless from "supertokens-node/recipe/passwordless";
import Session from "supertokens-node/recipe/session";
import { TwilioService } from "supertokens-node/recipe/passwordless/smsdelivery";
supertokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "...",
},
recipeList: [
Passwordless.init({
flowType: "USER_INPUT_CODE",
contactMethod: "PHONE",
smsDelivery: {
service: new TwilioService({
twilioSettings: {
accountSid: "...",
authToken: "...",
opts: {
// optionally extra config to pass to Twilio client
},
// Use exactly one sender option. This example uses from.
from: "...",
},
}),
},
}),
Session.init(),
],
});import (
"github.com/supertokens/supertokens-golang/ingredients/smsdelivery"
"github.com/supertokens/supertokens-golang/recipe/passwordless"
"github.com/supertokens/supertokens-golang/recipe/passwordless/plessmodels"
"github.com/supertokens/supertokens-golang/supertokens"
)
func main() {
smsService, err := passwordless.MakeTwilioService(smsdelivery.TwilioServiceConfig{
Settings: smsdelivery.TwilioSettings{
AccountSid: "...",
AuthToken: "...",
// Use exactly one sender option. This example uses From.
From: "...",
},
})
if err != nil {
panic(err)
}
supertokens.Init(supertokens.TypeInput{
RecipeList: []supertokens.Recipe{
passwordless.Init(plessmodels.TypeInput{
ContactMethodPhone: plessmodels.ContactMethodPhoneConfig{Enabled: true},
FlowType: "USER_INPUT_CODE",
SmsDelivery: &smsdelivery.TypeInput{
Service: smsService,
},
}),
},
})
}from supertokens_python import init, InputAppInfo
from supertokens_python.recipe import passwordless
from supertokens_python.recipe.passwordless import ContactPhoneOnlyConfig
from supertokens_python.ingredients.smsdelivery.types import SMSDeliveryConfig, TwilioSettings
init(
app_info=InputAppInfo(
api_domain="...", app_name="...", website_domain="..."),
framework='...',
recipe_list=[
passwordless.init(
contact_config=ContactPhoneOnlyConfig(),
flow_type="USER_INPUT_CODE",
sms_delivery=SMSDeliveryConfig(
service=passwordless.TwilioService(
twilio_settings=TwilioSettings(
account_sid="...",
auth_token="...",
opts={
# Optional configs to pass to twilio client
},
# Use exactly one sender option. This example uses from_.
from_="...",
)
)
)
)
]
)To learn about how to customize the SMS templates, please see the next section.
SuperTokens SMS service
The backend SDKs also expose an API-key-based SuperTokensSMSService. It calls an external SMS endpoint directly and can
be used whether your Core is self-hosted or managed. Availability, pricing, credits, quotas, sender identity, key issuance,
and the Dashboard workflow are mutable service policy. Confirm them in your current Dashboard or contract before adopting
this option; they are not guaranteed by the released SDK interface.
If you have been issued an SMS API key, set it in the backend SDK configuration:
import supertokens from "supertokens-node";
import Passwordless from "supertokens-node/recipe/passwordless";
import Session from "supertokens-node/recipe/session";
import { SupertokensService } from "supertokens-node/recipe/passwordless/smsdelivery";
supertokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "...",
},
recipeList: [
Passwordless.init({
flowType: "USER_INPUT_CODE",
contactMethod: "PHONE",
smsDelivery: {
service: new SupertokensService("<SMS API KEY GOES HERE>"),
},
}),
Session.init(),
],
});import (
"github.com/supertokens/supertokens-golang/ingredients/smsdelivery"
"github.com/supertokens/supertokens-golang/recipe/passwordless"
"github.com/supertokens/supertokens-golang/recipe/passwordless/plessmodels"
"github.com/supertokens/supertokens-golang/supertokens"
)
func main() {
supertokens.Init(supertokens.TypeInput{
RecipeList: []supertokens.Recipe{
passwordless.Init(plessmodels.TypeInput{
ContactMethodPhone: plessmodels.ContactMethodPhoneConfig{Enabled: true},
FlowType: "USER_INPUT_CODE",
SmsDelivery: &smsdelivery.TypeInput{
Service: passwordless.MakeSupertokensSMSService("<SMS API KEY GOES HERE>"),
},
}),
},
})
}from supertokens_python import init, InputAppInfo
from supertokens_python.recipe import passwordless
from supertokens_python.recipe.passwordless import ContactPhoneOnlyConfig
from supertokens_python.ingredients.smsdelivery.types import SMSDeliveryConfig
init(
app_info=InputAppInfo(
api_domain="...", app_name="...", website_domain="..."),
framework='...',
recipe_list=[
passwordless.init(
contact_config=ContactPhoneOnlyConfig(),
flow_type="USER_INPUT_CODE",
sms_delivery=SMSDeliveryConfig(
service=passwordless.SuperTokensSMSService("<SMS API KEY GOES HERE>"))
)
]
)Custom method
This method allows you to send messages however you like. The input to the send function consists of SMS template variables, allowing you to create the content of the SMS as well. Use this method if you are:
- Using a third-party SMS service that is not Twilio.
- You want to use another delivery method like WhatsApp or Facebook Messenger.
- You want to do some custom spam protection before sending the SMS.
- You already have an SMS sending infrastructure and want to use that.
import supertokens from "supertokens-node";
import Passwordless from "supertokens-node/recipe/passwordless";
import Session from "supertokens-node/recipe/session";
supertokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "...",
},
recipeList: [
Passwordless.init({
flowType: "USER_INPUT_CODE",
contactMethod: "PHONE",
smsDelivery: {
override: (originalImplementation) => {
return {
...originalImplementation,
sendSms: async function ({
codeLifetime, // amount of time the code is alive for (in MS)
phoneNumber,
urlWithLinkCode, // magic link
userInputCode, // OTP
}) {
// TODO: create and send SMS
},
};
},
},
}),
Session.init(),
],
});import (
"fmt"
"github.com/supertokens/supertokens-golang/ingredients/smsdelivery"
"github.com/supertokens/supertokens-golang/recipe/passwordless"
"github.com/supertokens/supertokens-golang/recipe/passwordless/plessmodels"
"github.com/supertokens/supertokens-golang/supertokens"
)
func main() {
supertokens.Init(supertokens.TypeInput{
RecipeList: []supertokens.Recipe{
passwordless.Init(plessmodels.TypeInput{
ContactMethodPhone: plessmodels.ContactMethodPhoneConfig{Enabled: true},
FlowType: "USER_INPUT_CODE",
SmsDelivery: &smsdelivery.TypeInput{
Override: func(originalImplementation smsdelivery.SmsDeliveryInterface) smsdelivery.SmsDeliveryInterface {
(*originalImplementation.SendSms) = func(input smsdelivery.SmsType, userContext supertokens.UserContext) error {
// amount of time the code is alive for (in MS)
codeLifetime := input.PasswordlessLogin.CodeLifetime
phoneNumber := input.PasswordlessLogin.PhoneNumber
// magic link
urlWithLinkCode := input.PasswordlessLogin.UrlWithLinkCode
// OTP
userInputCode := input.PasswordlessLogin.UserInputCode
fmt.Println(codeLifetime)
fmt.Println(phoneNumber)
fmt.Println(urlWithLinkCode)
fmt.Println(userInputCode)
// TODO: create and send SMS
return nil
}
return originalImplementation
},
},
}),
},
})
}from supertokens_python import init, InputAppInfo
from supertokens_python.recipe.passwordless.types import SMSDeliveryOverrideInput, SMSTemplateVars
from supertokens_python.recipe import passwordless
from supertokens_python.recipe.passwordless import ContactPhoneOnlyConfig
from typing import Dict, Any
from supertokens_python.ingredients.smsdelivery.types import SMSDeliveryConfig
def custom_sms_deliver(original_implementation: SMSDeliveryOverrideInput) -> SMSDeliveryOverrideInput:
async def send_sms(template_vars: SMSTemplateVars, user_context: Dict[str, Any]) -> None:
# amount of time the code is alive for (in MS)
_ = template_vars.code_life_time
__ = template_vars.phone_number
___ = template_vars.url_with_link_code # magic link
____ = template_vars.user_input_code # OTP
# TODO: create and send SMS...
original_implementation.send_sms = send_sms
return original_implementation
init(
app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."),
framework='...',
recipe_list=[
passwordless.init(
contact_config=ContactPhoneOnlyConfig(),
flow_type="USER_INPUT_CODE",
sms_delivery=SMSDeliveryConfig(override=custom_sms_deliver)
)
]
)If you call the original implementation function for sendSms, it uses the service that you have configured. If you have not configured any service, it uses the default service.
SMS Customization
You can see the default SMS content:
- Default passwordless login with OTP template.
- Default passwordless login with magic link template.
- Default passwordless login with magic link and OTP template.
To change the content of the default SMS templates, you can override the getContent function in the smsDelivery object.
It allows you to return an object that has the following properties:
body: The SMS message body.toPhoneNumber: The phone number where the SMS is sent to.
import supertokens from "supertokens-node";
import Passwordless from "supertokens-node/recipe/passwordless";
import Session from "supertokens-node/recipe/session";
import { TwilioService } from "supertokens-node/recipe/passwordless/smsdelivery";
supertokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "...",
},
recipeList: [
Passwordless.init({
flowType: "USER_INPUT_CODE",
contactMethod: "PHONE",
smsDelivery: {
service: new TwilioService({
twilioSettings: {
/*...*/
},
override: (originalImplementation) => {
return {
...originalImplementation,
getContent: async function ({
isFirstFactor,
codeLifetime, // amount of time the code is alive for (in MS)
phoneNumber,
urlWithLinkCode, // magic link
userInputCode, // OTP
}) {
if (isFirstFactor) {
// send some custom SMS content
return {
toPhoneNumber: phoneNumber,
body: "SMS BODY",
};
} else {
// for second factor, urlWithLinkCode will always be
// undefined since we only support OTP based for second factor
return {
toPhoneNumber: phoneNumber,
body: "SMS BODY",
};
}
// You can even call the original implementation and
// modify its content:
/*let originalContent = await originalImplementation.getContent(input)
originalContent.body = "My custom body";
return originalContent;*/
},
};
},
}),
},
}),
Session.init(),
],
});import (
"fmt"
"github.com/supertokens/supertokens-golang/ingredients/smsdelivery"
"github.com/supertokens/supertokens-golang/recipe/passwordless"
"github.com/supertokens/supertokens-golang/recipe/passwordless/plessmodels"
"github.com/supertokens/supertokens-golang/supertokens"
)
func main() {
smsService, err := passwordless.MakeTwilioService(smsdelivery.TwilioServiceConfig{
Settings: smsdelivery.TwilioSettings{ /* ... */ },
Override: func(originalImplementation smsdelivery.TwilioInterface) smsdelivery.TwilioInterface {
// originalGetContent := *originalImplementation.GetContent
(*originalImplementation.GetContent) = func(input smsdelivery.SmsType, userContext supertokens.UserContext) (smsdelivery.SMSContent, error) {
// amount of time the code is alive for (in MS)
codeLifetime := input.PasswordlessLogin.CodeLifetime
phoneNumber := input.PasswordlessLogin.PhoneNumber
// magic link
urlWithLinkCode := input.PasswordlessLogin.UrlWithLinkCode
// OTP
userInputCode := input.PasswordlessLogin.UserInputCode
fmt.Println(codeLifetime)
fmt.Println(phoneNumber)
fmt.Println(urlWithLinkCode)
fmt.Println(userInputCode)
// send custom SMS content
return smsdelivery.SMSContent{
Body: "SMS BODY",
ToPhoneNumber: phoneNumber,
}, nil
// Or call the original implementation and change its content:
/*
originalResponse, err := originalGetContent(input, userContext)
if err != nil {
return smsdelivery.SMSContent{}, nil
}
originalResponse.body = "SMS Body"
return originalResponse
*/
}
return originalImplementation
},
})
if err != nil {
panic(err)
}
supertokens.Init(supertokens.TypeInput{
RecipeList: []supertokens.Recipe{
passwordless.Init(plessmodels.TypeInput{
ContactMethodPhone: plessmodels.ContactMethodPhoneConfig{Enabled: true},
FlowType: "USER_INPUT_CODE",
SmsDelivery: &smsdelivery.TypeInput{
Service: smsService,
},
}),
},
})
}from supertokens_python import init, InputAppInfo
from supertokens_python.recipe import passwordless
from supertokens_python.recipe.passwordless import ContactPhoneOnlyConfig
from supertokens_python.recipe.passwordless.types import TwilioOverrideInput, SMSTemplateVars
from supertokens_python.ingredients.smsdelivery.types import SMSDeliveryConfig, SMSContent, TwilioSettings
from typing import Dict, Any
def custom_sms_content_override(original_implementation: TwilioOverrideInput) -> TwilioOverrideInput:
# original_get_content = original_implementation.get_content
async def get_content(template_vars: SMSTemplateVars, user_context: Dict[str, Any]) -> SMSContent:
# amount of time the code is alive for (in MS)
_ = template_vars.code_life_time
phone_number = template_vars.phone_number
__ = template_vars.url_with_link_code # magic link
___ = template_vars.user_input_code # OTP
# send custom SMS content
return SMSContent(body="SMS BODY", to_phone=phone_number)
# you can even call the original implementation and modify that
# original_content = await original_get_content(template_vars, user_context)
# original_content.body = "My custom body"
# return original_content
original_implementation.get_content = get_content
return original_implementation
init(
app_info=InputAppInfo(
api_domain="...", app_name="...", website_domain="..."),
framework='...',
recipe_list=[
passwordless.init(
contact_config=ContactPhoneOnlyConfig(),
flow_type="USER_INPUT_CODE",
sms_delivery=SMSDeliveryConfig(
service=passwordless.TwilioService(
twilio_settings=TwilioSettings(...),
override=custom_sms_content_override
)
)
)
]
)Overrides
You can use the override functionality to trigger any kind of behavior before and after SMS sending. This can include things like:
- Logging
- Spam protection actions
- Modifying the SMS template variables before sending messages
import supertokens from "supertokens-node";
import Passwordless from "supertokens-node/recipe/passwordless";
import Session from "supertokens-node/recipe/session";
supertokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "...",
},
recipeList: [
Passwordless.init({
flowType: "USER_INPUT_CODE",
contactMethod: "PHONE",
smsDelivery: {
override: (originalImplementation) => {
return {
...originalImplementation,
sendSms: async function (input) {
// TODO: before sending SMS
await originalImplementation.sendSms(input);
// TODO: after sending SMS
},
};
},
},
}),
Session.init(),
],
});import (
"github.com/supertokens/supertokens-golang/ingredients/smsdelivery"
"github.com/supertokens/supertokens-golang/recipe/passwordless"
"github.com/supertokens/supertokens-golang/recipe/passwordless/plessmodels"
"github.com/supertokens/supertokens-golang/supertokens"
)
func main() {
supertokens.Init(supertokens.TypeInput{
RecipeList: []supertokens.Recipe{
passwordless.Init(plessmodels.TypeInput{
ContactMethodPhone: plessmodels.ContactMethodPhoneConfig{Enabled: true},
FlowType: "USER_INPUT_CODE",
SmsDelivery: &smsdelivery.TypeInput{
Override: func(originalImplementation smsdelivery.SmsDeliveryInterface) smsdelivery.SmsDeliveryInterface {
originalSendSms := *originalImplementation.SendSms
(*originalImplementation.SendSms) = func(input smsdelivery.SmsType, userContext supertokens.UserContext) error {
// TODO: before sending SMS
err := originalSendSms(input, userContext)
if err != nil {
return err
}
// TODO: after sending SMS
return nil
}
return originalImplementation
},
},
}),
},
})
}from supertokens_python import init, InputAppInfo
from supertokens_python.recipe.passwordless.types import SMSDeliveryOverrideInput, SMSTemplateVars
from supertokens_python.recipe import passwordless
from supertokens_python.recipe.passwordless import ContactPhoneOnlyConfig
from typing import Dict, Any
from supertokens_python.ingredients.smsdelivery.types import SMSDeliveryConfig
def custom_sms_deliver(original_implementation: SMSDeliveryOverrideInput) -> SMSDeliveryOverrideInput:
original_send_sms = original_implementation.send_sms
async def send_sms(template_vars: SMSTemplateVars, user_context: Dict[str, Any]) -> None:
# TODO: before sending SMS
await original_send_sms(template_vars, user_context)
# TODO: after sending SMS
original_implementation.send_sms = send_sms
return original_implementation
init(
app_info=InputAppInfo(
api_domain="...", app_name="...", website_domain="..."),
framework='...',
recipe_list=[
passwordless.init(
contact_config=ContactPhoneOnlyConfig(),
flow_type="USER_INPUT_CODE",
sms_delivery=SMSDeliveryConfig(override=custom_sms_deliver)
)
]
)