Skip to content
All articles
9 min read

Jasmin SMS Gateway in Production: Billing, DLR, DLT

MD Rakibul Islam RakibMD Rakibul Islam RakibFull-stack developer, DevOps & Linux engineer
Jasmin SMS Gateway in Production: Billing, DLR, DLT

In production, Jasmin charges a user's balance when it routes a message, not when the phone gets it. Delivery reports and DLT IDs need separate setup.

I set up and fix Jasmin gateways for SMS resellers, including one for an Indian aggregator that had to pass DLT template checks. Installing Jasmin is the easy part, and I covered it in my Jasmin install guide. The money is lost later: customers charged for messages that never left, delivery reports nobody receives, and DLT rejections nobody can explain. For this post I ran Jasmin in Docker on 10 October 2026 and tested the billing rules for real. The outputs below are copied from that run.

Key takeaways

  • "Success" means queued. Jasmin returned Success and charged the balance even with the upstream connector stopped. Four messages sat in the RabbitMQ queue, already paid for.
  • Billing is per route and per part. The route's rate is charged for every SMS part, so a 200-character message costs two units.
  • early_percent splits the charge: part when the message is queued, the rest only when the upstream SMSC accepts it.
  • Delivery reports are opt-in per message with dlr=yes, a level and a URL. No URL, no report for your customer.
  • India DLT IDs travel as vendor TLVs, and the tag numbers differ between providers. Ask yours; don't copy them from a blog.

How a message moves through Jasmin

Knowing the path makes every later problem easier to place:

  1. Your customer's app calls the HTTP API (port 1401) or binds to Jasmin's SMPP server (port 2775).
  2. Jasmin authenticates the user, checks their filters and quotas, and the MT router picks a route. This is where the user is charged.
  3. The message goes into a RabbitMQ queue named after the connector, for example submit.sm.upstream.
  4. The SMPP client connector sends it to your upstream provider (the SMSC), which answers with a message ID.
  5. Later, the SMSC sends a delivery receipt. Jasmin matches it by that ID and forwards it to the customer's dlr-url or SMPP bind.
App HTTP/SMPP Router route + bill Queue RabbitMQ SMPP connector SMSC provider delivery receipt → your dlr-url $ charged here, before sending if the connector is down, paid messages wait in the queue
Jasmin charges at the router, before the message reaches the provider. The delivery receipt arrives later, on its own path, and only if the message asked for one.

Billing: what I saw when I tested it

I created a user with a balance of 10 and a cap of 100 messages, and a default route with a rate of 2.5 to a connector I deliberately left stopped:

jcli : user -a
> username acme
> password acme123
> gid resellers
> uid acme
> mt_messaging_cred quota balance 10
> mt_messaging_cred quota sms_count 100
> ok
jcli : mtrouter -a
> type DefaultRoute
> connector smppc(upstream)
> rate 2.5
> ok
jcli : persist

Then I sent five messages through the HTTP API:

balance: {"balance": 10.0, "sms_count": 100}
rate: {"unit_rate": 2.5, "submit_sm_count": 1}
send 1: Success "b70a2612-449f-40f5-bb00-ac874fca9982"
send 2: Success "f50798dd-f58d-4515-99aa-d93adea4441f"
send 3: Success "c657f407-631d-4f07-aa58-caaf295be4d1"
send 4: Success "0c1bb65d-4cac-4b27-a842-b12788406f5b"
send 5: Error "Cannot charge submit_sm, check RouterPB log file for details"
balance: {"balance": 0.0, "sms_count": 96}

Three lessons in eight lines:

  • The balance went to zero for four messages that never left the server. RabbitMQ showed submit.sm.upstream 4. If the connector stays down until the messages expire, your customer paid for nothing, and Jasmin has no automatic refund.
  • Running out of balance gives a clear error, so your panel can show "top up" instead of a generic failure.
  • Parts count. A 200-character message rated as "submit_sm_count": 2, so it costs two units. Show customers the part count before they send a campaign.

Charge less upfront with early_percent

Jasmin's router code bills a route in one of two ways. Without early_percent, the full rate is taken when the message is routed. With it, only that percentage is taken then, and the rest when the upstream SMSC accepts the message (the submit_sm_resp). I set it to 20 on the same user and sent three more messages to the stopped connector:

jcli : user -u acme
> mt_messaging_cred quota balance 10
> mt_messaging_cred quota early_percent 20
> ok

Success "14aa8087-dd0b-4627-b1f2-79f3e6c310da"
Success "dfaad83f-43ca-4cb5-a9fb-fa61421ccea8"
Success "a93d7520-066e-4331-928d-ff0297f051ed"
balance: {"balance": 8.5, "sms_count": 93}

Each message took 0.5 (20% of 2.5) while it waited. For a reseller this is the fairer default: customers pay in full only for messages your provider actually accepted. It still isn't billing on delivery; for that you need the delivery reports below and your own accounting.

Delivery reports your customers can trust

Jasmin forwards delivery receipts only when the message asks for them. On the HTTP API, add:

  • dlr=yes
  • dlr-level: 1 for SMSC level (the provider accepted it), 2 for terminal level (the handset got it), 3 for both.
  • dlr-url and dlr-method (GET or POST) where Jasmin should send them.

Things that break delivery reports in practice:

  • The provider doesn't send terminal receipts on that route, or sends them only for some networks. Test every route you sell and tell customers what "delivered" means on each.
  • The callback URL is slow or down. Make your receiver answer quickly and process the report afterwards, the same rule as for payment webhooks, and store each report keyed by message ID so a repeat doesn't count twice.
  • Redis lost the mapping. Jasmin keeps the link between its message ID and the provider's in Redis to match receipts. Restarting or flushing Redis without persistence orphans in-flight receipts.

India DLT: PE ID and template ID as TLVs

In India, commercial SMS must match a template registered on the telecom operators' DLT platform, under a registered principal entity (PE) and sender ID (header). Since 1 October 2024, links in messages must also be whitelisted, and since 11 December 2024 traffic without a declared PE-to-telemarketer chain is rejected.

Many Indian providers ask you to send the PE ID and template ID with each message as vendor-specific SMPP TLVs. Jasmin's HTTP API accepts a custom_tlvs parameter for this:

curl -X POST "http://127.0.0.1:1401/send" \
  --data "username=client1" --data "password=..." \
  --data "to=91XXXXXXXXXX" --data "from=HEADER" \
  --data "content=Your exact registered template text" \
  --data "dlr=yes" --data "dlr-level=3" \
  --data-urlencode 'custom_tlvs={"0x1400":"<PE ID>","0x1401":"<template ID>"}'

What I learned on that project:

  • Tag numbers are not standard. My client's provider wanted the PE ID in 0x1400 and the template ID in 0x1401. Jasmin's own TLV documentation uses an example where those two are the other way round. Get the mapping in writing from your provider.
  • The value type matters. Our first attempts failed because the values went out in a binary format the provider didn't expect; sending them as strings fixed it. Recent work on Jasmin's main branch (April 2026) lets the connector declare each tag's type and length.
  • The content must match the template exactly. One extra space, a different apostrophe or a missing full stop is a rejection. Variables must fill only the {#var#} slots.
  • Many "Jasmin errors" are DLT portal errors. A provider-specific code (4155, at that provider) turned out to mean the PE, header and template weren't linked to each other on the DLT portal. No change in Jasmin fixes that.
  • An MT interceptor (a small Python script Jasmin runs on every message) can force the sender ID and TON/NPI values per customer when their apps send the wrong ones.

Monitoring a reseller gateway

These are the numbers I put on a dashboard or an alert for every Jasmin I look after:

  • Queue depth per connector (rabbitmqctl list_queues name messages). A growing submit.sm.<cid> queue means a connector is down or throttled, and customers are being charged meanwhile.
  • Connector status in jCli (smppccm -l): bound or not, and since when.
  • Delivery rate per route and per hour. A sudden drop on one route is usually the provider, not you.
  • Customer balances near zero, so you can warn them before their campaign fails halfway.
  • Disk and log growth. Jasmin's message logs grow fast on busy gateways; rotate them, or you will meet my "No space left on device" guide at the worst moment.

Keep jCli (8990), the HTTP API and RabbitMQ off the public internet, or restricted to known IPs. An open jCli is full control of the gateway, including every customer's balance.

Frequently asked questions

When does Jasmin deduct a user's balance?

When the router accepts the message, before it is sent upstream. With early_percent set, it deducts that share at routing time and the rest when the provider accepts the message. It doesn't wait for a delivery report.

Does Jasmin refund failed messages?

Not automatically. If messages expire in a queue or the provider rejects them, the charge stays. Resellers usually reconcile against delivery reports in their own panel and credit customers there.

Why does Jasmin say Success when nothing was delivered?

Success with a message ID means Jasmin accepted and queued the message. Delivery is reported separately through delivery receipts, so ask for them with dlr=yes and a dlr-url.

How do I send India DLT template IDs through Jasmin?

Pass them as the TLVs your provider specifies, for example through the HTTP API's custom_tlvs parameter, and make sure the content matches the registered template exactly. Confirm the tag numbers and value format with your provider first.

Can Jasmin bill different customers different prices?

Yes. Rates live on routes, and routes can be filtered by user or group, so each customer group can have its own rate per destination. Balances and message caps are set per user.

Running an SMS business on Jasmin?

I set up, fix and monitor Jasmin gateways: routing, billing, delivery reports, DLT compliance and customer panels. See my DevOps services, read how the full bulk SMS stack fits together, or tell me what your gateway is doing wrong.

MD Rakibul Islam Rakib

Written by

MD Rakibul Islam Rakib

Full-stack developer, DevOps engineer and Linux system administrator with 5+ years of production experience. I deploy, harden and fix servers and web apps for clients worldwide, and everything in this article runs on real servers I manage, including this site.

  • Jasmin SMS gateway billing
  • Jasmin delivery receipts
  • India DLT SMPP TLV
  • Jasmin SMPP routing
  • SMS reseller platform
  • Jasmin custom_tlvs
  • SMPP gateway monitoring