medusa-msg91
v0.0.8
Published
MSG91 SMS notification provider for Medusa v2 (Flow API).
Maintainers
Readme
medusa-msg91
Medusa v2 Notification Provider for MSG91 transactional SMS via the Flow API.
Features
- Plugs into
@medusajs/medusa/notificationviaAbstractNotificationProviderService - Flow API (
POST /api/v5/flow/) for template-based transactional SMS (DLT-safe — no raw message strings) - Process-owned Flow IDs via
notification.template(e.g. customer-registrationnotification_template) - Optional legacy
options.templatesmap for key → Flow ID remapping - Strict E.164 phone validation before any MSG91 call
- Per-process sliding-window rate limiting
- HTTP timeout (default 10s) with no automatic in-provider retry
- Typed errors: client (4xx) vs server (5xx/timeout) vs rate limit
- Structured success/failure/latency logs
Known limitations (read before production)
Fire-and-forget: v1 returns the MSG91 request id when the API accepts the send. There is no DLR/delivery webhook. “Sent” does not mean “delivered to handset.” Retain externalId from logs for support. Delivery confirmation is Phase 2.
Per-process rate limiter: The sliding window is per Node process, not cluster-wide. With N Medusa replicas/pods the effective ceiling is approximately rateLimitPerMinute × N. Do not discover this after a bad autoscale event.
Correlation key ≠ guaranteed dedupe: A correlation key (Medusa notification.id or UUID) is always logged. MSG91 Flow v5 docs do not document a client reference field that MSG91 dedupes on. A retried send() can still bill/send twice. Callers must not blindly retry without their own dedupe.
No circuit breaker: During an extended MSG91 outage each call waits the full requestTimeoutMs (default 10s). Fail-fast circuit breaking is Phase 2.
No free-form content.text: Unlike Twilio, MSG91 Flow/DLT cannot send rendered SMS bodies. Processes must set notification_template to a Flow ID (or use the optional options.templates map).
Installation
npm install medusa-msg91
# or
yarn add medusa-msg91Registration (medusa-config.ts)
Provider options are credentials only. Flow IDs live on the process (customer-registration / order-management):
import { defineConfig } from "@medusajs/framework/utils"
export default defineConfig({
plugins: [
{
resolve: "customer-registration",
options: {
phone_auth: {
channel: "sms",
// MSG91 Flow ID — sent as notification.template
notification_template: process.env.MSG91_FLOW_OTP_SEND,
template: "templates/customer-otp-sms.txt",
resendThrottleSeconds: 90,
},
},
},
],
modules: [
{
resolve: "@medusajs/medusa/notification",
options: {
providers: [
{
resolve: "medusa-msg91/providers/msg91",
id: "msg91",
options: {
channels: ["sms"],
authkey: process.env.MSG91_AUTHKEY,
senderId: process.env.MSG91_SENDER_ID,
// Confirm real account throughput with MSG91 support/dashboard before production.
rateLimitPerMinute: Number(process.env.MSG91_RATE_LIMIT_PER_MINUTE),
// Flow variable for notification.data.otp. Blank defaults to "num" (##num##).
otpVar: process.env.MSG91_OTP_VAR,
requestTimeoutMs: 10_000,
},
},
],
},
},
],
})Rate-limit confirmation checklist
- Open MSG91 account dashboard / contact MSG91 support
- Confirm the actual per-account throughput (requests/minute)
- Set
MSG91_RATE_LIMIT_PER_MINUTEto that confirmed number (or lower) - If you run multiple Medusa replicas, remember the limiter is per process
Sending notifications
Prefer process-owned template_obj (customer-registration / order-management):
await notificationModule.createNotifications({
to: "+919876543210", // strict E.164 required
channel: "sms",
template: process.env.MSG91_FLOW_OTP_SEND, // Flow ID
data: {
otp: "123456",
customer_name: "Ada",
template_obj: {
templateId: process.env.MSG91_FLOW_OTP_SEND,
varMap: { otp: "VAR1", customer_name: "VAR2" },
},
},
})Without varMap, stringable data fields are passed through as MSG91 variables (keys must match the Flow).
For OTP, set provider otpVar (env MSG91_OTP_VAR) to the Flow placeholder name. When present, data.otp is sent as that variable (e.g. num → ##num##). There is no built-in default — omit otpVar only if you already map otp via varMap. Blank MSG91_OTP_VAR is treated as unset (the provider still boots). If the phone gets the template text with an empty OTP slot, the Flow ##placeholder## does not match otpVar / varMap.
Options
| Option | Required | Description |
|---|---|---|
| authkey | Yes | MSG91 auth key (sent as authkey header, not query param) |
| senderId | Yes | Approved sender ID for Flow API |
| rateLimitPerMinute | Yes | Max outbound calls/minute in this process |
| otpVar | No* | MSG91 Flow variable for data.otp (e.g. num). From MSG91_OTP_VAR. *Required for OTP when varMap does not map otp. |
| templates | No | Optional legacy map of logical keys → { templateId, varMap? }. Prefer process notification_template = Flow ID. |
| requestTimeoutMs | No | HTTP timeout (default 10000) |
When options.templates is omitted (recommended), notification.template is the MSG91 Flow ID (flow_id / template_id in the API payload).
Error taxonomy
| Error class | Typical cause | Guidance |
|---|---|---|
| Msg91ClientError → MedusaError.INVALID_DATA | Bad phone, missing template, MSG91 4xx | Do not retry; fix input/config |
| Msg91ServerError → MedusaError.UNEXPECTED_STATE | Timeout, network, MSG91 5xx | Retry later with care (no blind loops) |
| Msg91RateLimitError → MedusaError.UNEXPECTED_STATE | Local limiter or MSG91 429 | Back off |
Structured log fields
Success (MSG91 notification sent): template, to (masked), externalId, correlationKey, latencyMs, statusCode
Failure (MSG91 notification failed): same + errorType (client | server | rate_limit), statusCode, message
Caller guidance
- Do not blindly retry
send()/createNotificationswithout your own dedupe — MSG91 may send twice - Prefer a stable Medusa notification id so logs correlate retries
- Treat accepted API response as “queued/accepted by MSG91”, not “delivered”
- Set process
notification_templateto the Flow ID; do not send Twilio-stylecontent.textalone
Phase 2 backlog
- DLR webhooks + delivery status
- Persistent idempotency ledger / cluster-safe dedupe
- Cluster-wide rate limiting (Redis or equivalent)
- Circuit breaker (fail fast after N consecutive MSG91 failures)
- Vendor-level MSG91 credentials
- OTP channel via MSG91 OTP API (out of scope for v1)
- WhatsApp / Email via MSG91 Hello / Campaign products
Development
cd medusa-msg91
npm install
npm test
npm run buildMedusa version
Peer dependencies target @medusajs/* 2.18.0.
