SMS

SMS sends text messages through a provider and logs every one. Its main use is phone sign-in: a code by SMS the first time, then a 6-digit PIN.

What it is

PartWhereWhat it does
SMS settingsAdmin, Settings, SMS (/admin/settings/sms)The provider and its keys, the countries people can sign in from and the default one, and a test send. It also says whether phone sign-in is on, with a link to its switch in User Settings.
SMS LogsAdmin, Email + SMS, SMS Logs (/admin/sms-logs)Every message, sent or only recorded, with its status
Phone sign-inThe sign-in page, and the profile's sign-in methodsSign up and sign in with a mobile number, a one-time code and a PIN

Two providers exist:

ProviderSendsNeeds
Test (log only)Nothing. The full message, code included, goes to SMS Logs.Nothing. It is the provider when no settings are saved.
MSG91One-time codes, through MSG91's OTP APIAn MSG91 auth key and the id of a DLT-approved OTP template that contains ##OTP##

MSG91 sends only registered templates (Indian regulation), so it sends codes and nothing else; there is no free-text SMS.

Turn it on or off

SMS is a switchable feature, id sms, on by default and needing no other feature. To turn it off, list it in src/custom/features.ts:

export const CUSTOM_FEATURES: FeatureChoice = {
    off: ['sms'],
};

With it off:

Settings and logs stay in Firestore. How the switch works for every feature: Feature ids.

Set it up

  1. Open Admin, Settings, SMS.

  2. Under Countries, list the countries people can sign in from: type a name, an ISO id or a code (44 or +44) in the search box and pick one. Each shows with its flag, name and code. Only numbers from these countries can sign in by phone, and the list is never empty: the last country has no remove button. A new site has India only.

  3. With more than one country, choose the Default country. The sign-in page's country chip starts on it, and numbers typed without a code belong to it. Removing the default makes the first country left the default. With one country there is nothing to choose.

  4. Saving stores the countries and their calling codes in Settings/sms; the server reads the codes. It also copies the countries to Settings/users (phoneCountry, phoneCountryCode, phoneCountries), which the sign-in page can read before anyone signs in, while Settings/sms stays admin only. Opening this page puts the copy right if it is missing or out of step. A site saved before countries were stored reads each saved code as its main country (91 India, 44 the United Kingdom, 1 the United States) until it is saved again.

  5. To send real messages, choose MSG91 and enter the auth key and the OTP template id. The key is never shown again: leave the field empty to keep the saved one.

  6. Click Save SMS settings. Then, under Send a test SMS, pick your country beside the box, enter your own number and click Send test. With MSG91 the test is your OTP template with a made-up code; with the test provider it is only recorded in SMS Logs.

Then turn phone sign-in on under Admin, Settings, User Settings (the SMS page links there). The switch first checks the one Google Cloud setting phone sign-in needs, and shows how to fix it if it is missing (see Google Cloud setup for phone sign-in). With phone sign-in on and the test provider, User Settings also warns that codes are only logged.

With the test provider, no SMS is sent and the sign-in page shows the sign-up code on screen, so a sign-up does not prove the person owns the number. PIN reset codes are only in SMS Logs, unless you turn on Show PIN reset codes on screen (it appears with the test provider, off by default). Turn it on only while you test: anyone who knows a number could then reset that PIN and take over the account. Choosing MSG91 turns it off. Link codes are never shown on screen. Switch to MSG91 before real people use phone sign-in.

Use it

Phone sign-in

  1. The person types their mobile number. Numbers in any common shape work (+91 98765-43210, 098765 43210); each is stored as E.164, such as +919876543210.
  2. A new number gets a 6-digit code by SMS. After the code, the person enters their name and chooses a 6-digit PIN, and the account is created with the default role from User Settings. New numbers are refused while sign-ups are closed.
  3. A registered number signs in with its PIN. After 5 wrong PINs, the PIN is locked and the person resets it with a new code.
  4. Forgot PIN? sends a code; after it, the person chooses a new PIN, and every other session of that account is signed out within the hour.

Codes last 10 minutes and allow 5 tries. A number can get a code for the same purpose once a minute and 5 codes an hour; a request inside the minute sends nothing and the page shows the code already sent, and neither it nor an SMS the provider refuses counts towards the 5. A code for another purpose, such as a PIN reset right after a sign-up code, goes at once. Asking again while the code still works sends the same code, with another 10 minutes, so two messages never carry different codes. PINs such as repeated digits or 123456 are refused. A signed-in person can also add a number to their account, or change their PIN, from their profile. Adding a number sends a code; a number verified this way moves from any other account that had it (see Sign-in methods).

SMS Logs

Each message shows the time, the number (To), the message, its type (Sign-in code or Test), its status and the provider. The status is Logged (test provider, stored as logged), Sent (sent) or Failed (failed), with the provider's error after the message. For a real provider the code is replaced by dots in the stored text, so the log never holds a live code. Filter by status to find failures.

Extend it

To send a text from your own Cloud Functions, call sendSms() from functions/src/sms/sendSms.ts. It sends through the configured provider and writes the log:

import { sendSms } from '../sms/sendSms.js';

const code = '482913';
const result = await sendSms({ to: '+919876543210', purpose: 'otp', code, text: `${code} is your code.` });
// result.status: 'logged' | 'sent' | 'failed'

With MSG91 only messages with a code can be sent, and the text is ignored: the OTP template is what arrives. Check isFeatureOn('sms') (from functions/src/feature-flags.ts) first if your app may turn SMS off.

A new provider is a change to core (functions/src/sms/providers.ts), not to an app. Raise it as a feature request.

What it stores

WhereHolds
Settings/smsprovider, defaultCountry and allowedCountries (ISO ids such as IN), defaultCountryCode and allowedCountryCodes (their calling codes, which the server reads), msg91AuthKey, msg91OtpTemplateId
SmsLogsOne record per message
phone_otpsThe current code for each number, hashed, with its purpose, expiry and tries
phone_indexWhich user record owns each number (keyed by a hash of the number)
auth_pinsEach account's PIN, hashed, and its failed tries
_rate_limitsCounters for the per-number and per-caller limits

The user record keeps phone and phoneVerified. Nothing is kept in Storage.

FunctionDoes
sendTestSmsThe test send (admin)
checkPhoneSignInWhether phone sign-in can sign people in on this project, and the fix when it cannot (admin; see Google Cloud setup)
checkPhoneAccountWhether a number has an account and a PIN
requestPhoneOtp, verifyPhoneOtpSend and check a code
completePhoneSignupCreate the account after a sign-up code
signInWithPin, resetPin, setPinSign in, reset the PIN after a code, change the PIN when signed in

All are callables, deployed as arccms-<name>. See Cloud Functions and Data model.

Who can do what

WhoCan
AdminRead and write Settings/sms; read SmsLogs; send a test SMS
Anyone, signed in or notCall the phone sign-up and sign-in functions while phone sign-in is on; code requests, account checks and PIN tries are rate limited
Signed-in personChange their own PIN (setPin)
Nobody from a browserRead or write phone_otps, phone_index, auth_pins, _rate_limits, or write SmsLogs. Only Cloud Functions do.

Troubleshooting

You seeWhy, and what to do
"Phone sign-in is not turned on for this site."Turn it on in Admin, Settings, User Settings.
"This site takes mobile numbers from India only." on the sign-in page, or "Only numbers starting +91 can be used here." from the serverAdd the person's country under Countries.
"We couldn't send the SMS."The provider refused it. SMS Logs has the failed record and the provider's error. For MSG91, check the auth key and the template id.
"MSG91 is not set up"The auth key or the template id is missing in Settings, SMS.
"We sent a code a moment ago." with a countdown, or "Too many codes. Try again after 5:42 PM."The once-a-minute or 5-an-hour limit per number. Use the code already sent, or wait until the time shown.
No code arrives and the status in SMS Logs is LoggedThe test provider is active: nothing is sent.