SMS
SMS templates via channel.sms, transactional fx.send, and provider-managed OTP with fx.sendOtp / fx.verifyOtp.
SMS (channel.sms) delivers short text and provider-managed one-time codes. Pin an SMS driver
(no default in any env), declare templates for app-owned messages, or call fx.sendOtp when the
vendor owns the code.
For developers verifying phones — choose raw Channel OTP vs the otp plugin.
The one rule
SMS is opt-in: set drivers.channel.sms (e.g. "taqnyat") or boot has no SMS transport. Provider
OTP needs a Verify-capable driver (taqnyat); app-owned codes use templates + fx.deliverOtp /
the OTP plugin.
Smallest Example
Pin an SMS driver
import { defineConfig } from "okengine/config";
export default defineConfig({
drivers: {
channel: {
sms: { prod: "taqnyat" },
},
},
});Set TAQNYAT_BEARER_TOKEN (or TAQNYAT_TOKEN) and TAQNYAT_SENDER.
Declare a template and send
import { channel } from "okengine";
import { z } from "zod";
const sms = channel.sms({ sender: "ACME" });
export const orderShippedSms = sms.template("order.shipped", {
locales: ["en"],
schema: z.object({ tracking: z.string() }),
});await fx.send(orderShippedSms, {
to: "+15551234567",
data: { tracking: "1Z999" },
});Add a catalog body (text: "Shipped — track {{tracking}}") or accept the JSON fallback.
Or send a provider OTP
await fx.sendOtp({
to: "+15551234567",
requestId: fx.id(),
lang: "en",
});
await fx.verifyOtp({
to: "+15551234567",
requestId, // same id
code: userEnteredCode,
});Effects record sends: ["sms-otp"]. Prefer otp({ mode: "provider" }) for
/auth/otp/* routes.
Progressive Patterns
Transactional SMS through the same fx.send path as email:
const sms = channel.sms({ from: "ACME" });
export const alertSms = sms.template("ops.alert", {
schema: z.object({ message: z.string() }),
});
await fx.send(alertSms, { to: phone, data: { message: "Disk 90%" } });Drivers
| Driver id | OTP Verify | Env |
|---|---|---|
taqnyat | yes | TAQNYAT_BEARER_TOKEN / TAQNYAT_TOKEN + TAQNYAT_SENDER |
msegat | no | MSEGAT_USERNAME / MSEGAT_API_KEY / MSEGAT_SENDER |
unifonic | no | UNIFONIC_APPSID |
console | — | Config id that opens no SMS driver |
Boot errors (verbatim):
oke boot: taqnyat channel needs TAQNYAT_BEARER_TOKEN
oke boot: taqnyat channel needs TAQNYAT_SENDER
oke boot: msegat channel needs MSEGAT_USERNAME / MSEGAT_API_KEY / MSEGAT_SENDER
oke boot: unifonic channel needs UNIFONIC_APPSIDProvider OTP without a Verify driver:
channel: SMS driver "…" does not support provider-managed OTP; use exposeDevOtp locally or set drivers.channel.sms to "taqnyat"No SMS bound:
channel: no SMS driver bound — bind drivers.channel.sms (e.g. taqnyat) to send provider OTPOptions Reference
channel.sms(options?)
Same medium options as email: from / sender.
.template(name, options?)
description · locales · schema — bodies in the catalog (text is typical for SMS).
Troubleshooting
No Channel runtime, or SMS not configured. Declare templates/drivers and set
drivers.channel.sms.
Config has no SMS id for this env (CHANNEL_SMS_DEFAULTS is empty). Pin taqnyat / msegat /
unifonic.
Only Taqnyat exposes sendOtp / verifyOtp. Switch driver or use app-mode OTP
(fx.deliverOtp / otp({ mode: "app" })).
fx.deliverOtp had no matching address for a declared medium (need phone for SMS / WhatsApp,
email for email).
Every medium in the failover list failed. Check driver credentials, suppression, and template catalog entries.
Learn more
- OTP plugin —
/auth/otp/* - WhatsApp — medium for app-mode OTP failover
- Email — email leg of
deliverOtp - Channel overview — effects and drivers
- Environment variables — SMS boot binder