whitebox-pro-sms-mobica
v0.2.0
Published
Mobica (gate.mobica.bg) provider for whitebox-pro-server-plugin-sms — HTTP send with a short client-defined id, DLR delivery-status parsing. Send-only (no inbound). No SDK.
Downloads
273
Readme
whitebox-pro-sms-mobica
Mobica provider for whitebox-pro-server-plugin-sms. Lives in its own repo; the SMS plugin stays provider-agnostic and composes this in like any other integration. Mobica is a plain HTTP SMS gateway popular for Bulgaria (+359) — typically used as a per-prefix route alongside an international default like Twilio.
import { sms } from 'whitebox-pro-server-plugin-sms'
import { twilio } from 'whitebox-pro-sms-twilio'
import { mobica } from 'whitebox-pro-sms-mobica'
sms({
provider: twilio({ /* … international default … */ }),
routes: {
'+359': mobica({
user: process.env.WB_MOBICA_USER,
pass: process.env.WB_MOBICA_PASS,
from: 'WhiteBox', // alphanumeric sender id
// instanceId: process.env.WB_INSTANCE_ID, // per-instance idd prefix; required for DLR fan-out (see below)
// gate: 'https://gate.mobica.bg/send.php', // override the gateway URL
// iddLength: 10, // message-id length (Mobica's `idd` limit)
// dlrSecret: process.env.WB_MOBICA_DLR_SECRET, // require ?secret= on the DLR callback
}),
},
defaultCountry: 'BG',
auth: { secret: process.env.WB_SMS_TOKEN },
})Send-only (no inbound)
Mobica is a one-way gateway: it sends and reports delivery, but does not deliver inbound replies (MO). So this provider implements send + parseStatus but not parseInbound — the plugin's inbound webhook returns 501 for mobica. There is consequently no inbound STOP/START path through Mobica; manage Bulgarian opt-outs via the suppression API or route a reply-capable number through another provider.
Client-defined message id
Mobica's idd field (the id the DLR references) is short and won't hold a UUID. This provider generates a compact 10-char base62 id per send, passes it as idd, and returns it as messageId — so the plugin's outbox matches DLRs on that id. Tune the length with iddLength if your Mobica account differs.
Multi-instance DLR fan-out (instanceId)
Mobica allows one DLR callback URL per account, set via Mobica support — there's no per-message callback parameter (confirmed against the v2 API docs) and no subaccounts. If several WhiteBox instances (dev/staging/prod, or multiple tenants) share one Mobica account, that single URL has to reach the instance that actually sent each message.
Since the idd is the only handle a DLR carries back, the approach is: make the idd globally unique by namespacing it per instance, then fan the one callback URL out to every instance (e.g. with nginx). Each instance matches the DLR against its own outbox — the owner advances the row, everyone else silently no-ops.
1. Give each instance a distinct instanceId (a short base62 prefix). The id becomes instanceId + random, capped at iddLength:
mobica({ user, pass, from: 'WhiteBox', instanceId: process.env.WB_INSTANCE_ID }) // e.g. 'a1' → idd "a1Xk9..." (10 chars total)A 2-char prefix supports 3,844 instances and still leaves 62⁸ ≈ 2×10¹⁴ ids each. With no instanceId (single-instance), ids are pure random — today's behavior.
2. Fan the single Mobica URL out to all instances. Point Mobica's callback at a front door that broadcasts the DLR (query string intact, incl. any ?secret=) to each instance's /sms/webhooks/mobica/status:
# The one URL configured with Mobica support → mirror to every instance.
location = /sms/webhooks/mobica/status {
mirror /_dlr_b; # instance B (and add more mirror dirs per instance)
proxy_pass http://instance_a/sms/webhooks/mobica/status$is_args$args; # instance A handles the response to Mobica
}
location = /_dlr_b { internal; proxy_pass http://instance_b/sms/webhooks/mobica/status$is_args$args; }Each instance returns 200 OK even for an idd it didn't mint, so Mobica always sees success. Only the instance whose instanceId prefixed the idd finds a matching outbox row — it advances the status, notifies, and (on undelivered/failed/blacklisted) blocklists. The others match nothing and do nothing, so a failure for one instance's recipient never suppresses another's.
What it implements
| method | Mobica specifics |
|---|---|
| send({ to, from, body }) → { messageId } | POST to the gate with user/pass/from/phone (E.164 minus +)/message/idd; returns the generated idd |
| verifySignature(req, kind) | checks ?secret= (or x-dlr-secret) against dlrSecret; if no secret is configured, doesn't verify (secure the callback by IP allowlist instead) |
| parseStatus(req) | maps Mobica's numeric DLR code → canonical status; flags blacklisted |
| classifyError(err) | 4xx / invalid / blacklisted / barred ⇒ blocklist instead of retry |
DLR setup
Point your Mobica account's delivery-report callback at:
GET https://YOUR_HOST/sms/webhooks/mobica/status?status=<code>&id=<idd>&phone=<e164-no-+>(The SMS plugin accepts the status webhook over GET specifically for Mobica's DLR style.) Code mapping:
| Mobica DLR code | canonical | effect in WhiteBox |
|---|---|---|
| 1000, 1001 | delivered | outbox → delivered |
| 1003, 1203 | sent | → sent (accepted / pending) |
| 1204, 1205 | undelivered | → undelivered (+ invalid list) |
| 1004, 1206–1207, 1005–1117 | failed | → failed (+ invalid list) |
| 1209 | failed | → failed + blacklisted |
If you set dlrSecret, configure the same ?secret= value in the Mobica panel so unsigned callbacks are rejected.
Credentials
user + pass from your Mobica account. Keep them in the environment (WB_MOBICA_USER, WB_MOBICA_PASS) — never commit them.
