{"id":2611,"date":"2026-08-07T12:26:23","date_gmt":"2026-08-07T06:56:23","guid":{"rendered":"https:\/\/www.smsgatewaycenter.com\/blog\/?p=2611"},"modified":"2026-08-07T12:26:25","modified_gmt":"2026-08-07T06:56:25","slug":"bulk-sms-pricing-in-india-what-actually-drives-cost","status":"publish","type":"post","link":"https:\/\/www.smsgatewaycenter.com\/blog\/bulk-sms-pricing-in-india-what-actually-drives-cost\/","title":{"rendered":"Bulk SMS Pricing in India: What Actually Drives Cost"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">The per-SMS rate on a pricing page is only one of six variables in your actual bill. This guide breaks down the real cost mechanics of bulk SMS in India: credit counting under GSM-7 and UCS-2, route class, DLT overhead, submission-time billing, retry amplification and DND scrubbing, with working code to price a message before you send it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Table of Contents<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><a href=\"#the-short-answer\">The Short Answer<\/a><\/li>\n\n\n\n<li><a href=\"#tldr\">TL;DR<\/a><\/li>\n\n\n\n<li><a href=\"#the-six-variables-that-decide-your-bill\" data-type=\"internal\" data-id=\"#the-six-variables-that-decide-your-bill\">The Six Variables That Decide Your Bill<\/a><\/li>\n\n\n\n<li><a href=\"#variable-one-the-credit-not-the-message\" data-type=\"internal\" data-id=\"#variable-one-the-credit-not-the-message\">Variable One: The Credit, Not the Message<\/a><\/li>\n\n\n\n<li><a href=\"#the-encoding-cliff-one-character-can-triple-your-bill\" data-type=\"internal\" data-id=\"#the-encoding-cliff-one-character-can-triple-your-bill\">The Encoding Cliff: One Character Can Triple Your Bill<\/a><\/li>\n\n\n\n<li><a href=\"#counting-segments-correctly-in-code\" data-type=\"internal\" data-id=\"#counting-segments-correctly-in-code\">Counting Segments Correctly in Code<\/a><\/li>\n\n\n\n<li><a href=\"#pricing-a-message-before-you-send-it\" data-type=\"internal\" data-id=\"#pricing-a-message-before-you-send-it\">Pricing a Message Before You Send It<\/a><\/li>\n\n\n\n<li><a href=\"#variable-two-route-class\" data-type=\"internal\" data-id=\"#variable-two-route-class\">Variable Two: Route Class<\/a><\/li>\n\n\n\n<li><a href=\"#what-the-published-slabs-actually-say\" data-type=\"internal\" data-id=\"#what-the-published-slabs-actually-say\">What the Published Slabs Actually Say<\/a><\/li>\n\n\n\n<li><a href=\"#variable-three-volume-slabs-and-the-commitment-trap\" data-type=\"internal\" data-id=\"#variable-three-volume-slabs-and-the-commitment-trap\">Variable Three: Volume Slabs and the Commitment Trap<\/a><\/li>\n\n\n\n<li><a href=\"#where-the-deduction-actually-happens\" data-type=\"internal\" data-id=\"#where-the-deduction-actually-happens\">Variable Four: Where the Deduction Actually Happens<\/a><\/li>\n\n\n\n<li><a href=\"#variable-five-retry-amplification\" data-type=\"internal\" data-id=\"#variable-five-retry-amplification\">Variable Five: Retry Amplification<\/a><\/li>\n\n\n\n<li><a href=\"#variable-six-dlt-and-the-costs-that-are-not-per-sms\" data-type=\"internal\" data-id=\"#variable-six-dlt-and-the-costs-that-are-not-per-sms\">Variable Six: DLT and the Costs That Are Not Per-SMS<\/a><\/li>\n\n\n\n<li><a href=\"#dnd-scrubbing-and-who-pays-for-a-blocked-message\" data-type=\"internal\" data-id=\"#dnd-scrubbing-and-who-pays-for-a-blocked-message\">DND Scrubbing and Who Pays for a Blocked Message<\/a><\/li>\n\n\n\n<li><a href=\"#international-sms-a-different-cost-model-entirely\" data-type=\"internal\" data-id=\"#international-sms-a-different-cost-model-entirely\">International SMS: A Different Cost Model Entirely<\/a><\/li>\n\n\n\n<li><a href=\"#reconciling-your-bill-against-the-dlr-api\" data-type=\"internal\" data-id=\"#reconciling-your-bill-against-the-dlr-api\">Reconciling Your Bill Against the DLR API<\/a><\/li>\n\n\n\n<li><a href=\"#how-to-model-your-monthly-bulk-sms-cost\" data-type=\"internal\" data-id=\"#how-to-model-your-monthly-bulk-sms-cost\">How to Model Your Monthly Bulk SMS Cost<\/a><\/li>\n\n\n\n<li><a href=\"#a-worked-example\" data-type=\"internal\" data-id=\"#a-worked-example\">A Worked Example<\/a><\/li>\n\n\n\n<li><a href=\"#cost-reduction-checklist\" data-type=\"internal\" data-id=\"#cost-reduction-checklist\">Cost Reduction Checklist<\/a><\/li>\n\n\n\n<li><a href=\"#channel-cost-decision-matrix\" data-type=\"internal\" data-id=\"#channel-cost-decision-matrix\">Channel Cost Decision Matrix<\/a><\/li>\n\n\n\n<li><a href=\"#ten-costly-mistakes\" data-type=\"internal\" data-id=\"#ten-costly-mistakes\">Ten Costly Mistakes<\/a><\/li>\n\n\n\n<li><a href=\"#faq\" data-type=\"internal\" data-id=\"#faq\">Frequently Asked Questions<\/a><\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"the-short-answer\" class=\"wp-block-heading\">The Short Answer<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Your <strong><a href=\"https:\/\/www.smsgatewaycenter.com\/bulk-sms\/\">bulk SMS<\/a><\/strong> bill in India is the per-credit rate multiplied by the number of credits consumed, and almost every unpleasant surprise comes from the second term, not the first. A message is billed in credits, a credit covers 160 characters of GSM-7 text or only 70 characters of Unicode, and a single character outside the GSM-7 set flips the whole message to Unicode and can turn a one-credit send into three. On top of that, credits are deducted when a message is successfully submitted to the operator rather than when it is delivered, route class changes the rate by a third or more, DLT registration adds fixed costs outside the per-SMS rate, and a naive retry loop multiplies everything. Comparing providers on the advertised paise figure alone tells you almost nothing about what you will actually spend.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/08\/bulk-sms-pricing-india-cost-drivers.webp\"><img loading=\"lazy\" decoding=\"async\" width=\"1000\" height=\"570\" src=\"https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/08\/bulk-sms-pricing-india-cost-drivers.webp\" alt=\"Diagram showing a single SMS of 160 characters splitting into three 153-character billed segments alongside a depleting credit meter, illustrating bulk SMS cost mechanics in India.\" class=\"wp-image-2612\" srcset=\"https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/08\/bulk-sms-pricing-india-cost-drivers.webp 1000w, https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/08\/bulk-sms-pricing-india-cost-drivers-300x171.webp 300w, https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/08\/bulk-sms-pricing-india-cost-drivers-768x438.webp 768w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/a><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"tldr\" class=\"wp-block-heading\">TL;DR<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>You are billed per credit, not per message.<\/strong> One credit equals 160 GSM-7 characters or 70 Unicode characters. <a href=\"https:\/\/www.3gpp.org\/DynaReport\/23040.htm\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">Concatenated messages drop to 153 and 67<\/a> per segment because the User Data Header eats space.<\/li>\n\n\n\n<li><strong>One non-GSM-7 character re-encodes the entire message.<\/strong> A curly apostrophe, an em dash, an emoji or a rupee sign pushes a 155-character message from one credit to three.<\/li>\n\n\n\n<li><strong>Route class matters more than negotiation.<\/strong> On the published SMSGatewayCenter slabs, transactional at 50,000 units is INR 0.15 per SMS while promotional at the same volume is INR 0.13, and the routes have different delivery windows, sender ID types and DND behaviour.<\/li>\n\n\n\n<li><strong>Deduction happens at successful submission to the operator, not at delivery.<\/strong> The published terms state credits are non-refundable once the SMS is successfully submitted. Undelivered does not mean unbilled.<\/li>\n\n\n\n<li><strong>Retries are the most common source of unexplained overspend.<\/strong> Retrying a permanently failed message three times triples the cost of that message and buys nothing.<\/li>\n\n\n\n<li><strong>DLT is a fixed overhead layer<\/strong>, not a per-SMS charge, but template rejections and mismatches cause failed sends that still consume operational time and can cause resend cost.<\/li>\n\n\n\n<li><strong>Price the message before you send it.<\/strong> <code>POST https:\/\/unify.smsgateway.center\/SMSApi\/info\/msg<\/code> returns encoding, length, remaining characters and credit count for any message string.<\/li>\n\n\n\n<li><strong>Reconcile against delivery reports.<\/strong> The DLR API returns per-message <code>cost<\/code> and <code>length<\/code> fields, which is the only way to audit a bill against reality.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"the-six-variables-that-decide-your-bill\" class=\"wp-block-heading\">The Six Variables That Decide Your Bill<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Most bulk SMS pricing articles are a table of provider names and paise figures. That is a comparison of list prices, not a cost model. Here is the actual structure of the bill:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>#<\/th><th>Variable<\/th><th>What it controls<\/th><th>Typical magnitude of effect<\/th><\/tr><\/thead><tbody><tr><td>1<\/td><td><strong>Credits per message<\/strong><\/td><td>How many billable units one logical message consumes<\/td><td>1x to 3x or worse, entirely under your control<\/td><\/tr><tr><td>2<\/td><td><strong>Route class<\/strong><\/td><td>The per-credit rate and the delivery rules attached to it<\/td><td>Roughly 15 to 30 percent between promotional and transactional at the same volume<\/td><\/tr><tr><td>3<\/td><td><strong>Volume slab<\/strong><\/td><td>The per-credit rate you qualify for<\/td><td>Roughly 30 percent from the entry slab to the top published slab<\/td><\/tr><tr><td>4<\/td><td><strong>Billing trigger point<\/strong><\/td><td>Whether failures cost you money<\/td><td>Up to your full failure rate, often 2 to 8 percent<\/td><\/tr><tr><td>5<\/td><td><strong>Retry policy<\/strong><\/td><td>How many times you pay for the same logical message<\/td><td>1x to 4x on the retried subset<\/td><\/tr><tr><td>6<\/td><td><strong>Fixed compliance overhead<\/strong><\/td><td>DLT registration, sender ID and template costs, amortised<\/td><td>Fixed, matters enormously at low volume and rounds to nothing at high volume<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Variables 1, 4 and 5 are engineering decisions. Variables 2 and 3 are commercial decisions. Variable 6 is a regulatory floor. Teams almost always negotiate hard on 2 and 3 and completely ignore 1, 4 and 5, which is backwards, because the engineering variables have a larger multiplier and cost nothing to fix.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"variable-one-the-credit-not-the-message\" class=\"wp-block-heading\">Variable One: The Credit, Not the Message<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A GSM SMS carries a 140-octet payload. With 7-bit GSM-7 packing that is 160 characters. With 16-bit UCS-2, needed for any character outside the <a href=\"https:\/\/www.3gpp.org\/DynaReport\/23038.htm\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">GSM-7 alphabet<\/a>, it is 70 characters.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When a message exceeds one segment, the sender splits it and prefixes each part with a User Data Header so the handset can reassemble it in order. That header consumes payload. The result is the well-known asymmetry: the first segment of a concatenated message holds 153 GSM-7 characters, not 160, and 67 Unicode characters, not 70.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">SMSGatewayCenter publishes this mapping directly on its <a href=\"https:\/\/www.smsgatewaycenter.com\/developer-api\/text-messages-chart\/\">Text Messages Chart<\/a>, and the numbers line up exactly with the GSM specification:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Credits billed<\/th><th>GSM-7 text characters<\/th><th>Unicode characters<\/th><\/tr><\/thead><tbody><tr><td>1<\/td><td>160<\/td><td>70<\/td><\/tr><tr><td>2<\/td><td>306<\/td><td>134<\/td><\/tr><tr><td>3<\/td><td>459<\/td><td>201<\/td><\/tr><tr><td>4<\/td><td>612<\/td><td>268<\/td><\/tr><tr><td>5<\/td><td>765<\/td><td>335<\/td><\/tr><tr><td>6<\/td><td>918<\/td><td>402<\/td><\/tr><tr><td>7<\/td><td>1071<\/td><td>469<\/td><\/tr><tr><td>8<\/td><td>1224<\/td><td>536<\/td><\/tr><tr><td>9<\/td><td>1377<\/td><td>603<\/td><\/tr><tr><td>10<\/td><td>1530<\/td><td>670<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Read the second row carefully. Two credits buy 306 characters, not 320. Three buy 459, not 480. Every segment after the first costs you seven characters of GSM-7 capacity or three characters of Unicode capacity to the header.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The practical consequence: a message of exactly 161 GSM-7 characters costs the same as a message of 306 characters. If your template lands at 161 to 170 characters, you are paying for a second segment and using almost none of it. Trimming eight characters halves that message&#8217;s cost. Across a million-message campaign that is not a rounding error.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The 160 \/ 161 boundary in plain terms<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Message length (GSM-7)<\/th><th>Segments<\/th><th>Credits<\/th><th>Wasted capacity<\/th><\/tr><\/thead><tbody><tr><td>158<\/td><td>1<\/td><td>1<\/td><td>2 characters<\/td><\/tr><tr><td>160<\/td><td>1<\/td><td>1<\/td><td>0<\/td><\/tr><tr><td>161<\/td><td>2<\/td><td>2<\/td><td>145 characters<\/td><\/tr><tr><td>306<\/td><td>2<\/td><td>2<\/td><td>0<\/td><\/tr><tr><td>307<\/td><td>3<\/td><td>3<\/td><td>152 characters<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The 161-character message is the single most expensive length in bulk SMS. It costs double and delivers one extra character of value.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"the-encoding-cliff-one-character-can-triple-your-bill\" class=\"wp-block-heading\">The Encoding Cliff: One Character Can Triple Your Bill<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This is the failure mode that produces the &#8220;why did our SMS bill jump 40 percent last month&#8221; conversation, and it is almost always caused by a copy-paste from a word processor.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">GSM-7 has a fixed alphabet. Anything outside it forces the entire message, not just the offending character, into UCS-2. The capacity per credit drops from 160 to 70, a 56 percent reduction.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Characters that look harmless and are <strong>not<\/strong> in GSM-7:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Character<\/th><th>Name<\/th><th>Where it comes from<\/th><\/tr><\/thead><tbody><tr><td><code>'<\/code> U+2019<\/td><td>Right single quotation mark<\/td><td>Microsoft Word and Google Docs autocorrect on apostrophes<\/td><\/tr><tr><td><code>\"<\/code> <code>\"<\/code> U+201C\/D<\/td><td>Curly double quotes<\/td><td>Same autocorrect<\/td><\/tr><tr><td><code>\u2013<\/code> U+2013<\/td><td>En dash<\/td><td>Word autocorrects a hyphen surrounded by spaces<\/td><\/tr><tr><td><code>\u2026<\/code> U+2026<\/td><td>Horizontal ellipsis<\/td><td>Word autocorrects three dots<\/td><\/tr><tr><td><code>\u20b9<\/code> U+20B9<\/td><td>Indian rupee sign<\/td><td>Typed directly instead of using &#8220;Rs.&#8221; or &#8220;INR&#8221;<\/td><\/tr><tr><td><code>\u2022<\/code> U+2022<\/td><td>Bullet<\/td><td>Pasted from a bulleted list<\/td><\/tr><tr><td>Any emoji<\/td><td><\/td><td>Marketing copy<\/td><\/tr><tr><td>Any Devanagari, Tamil, Telugu, Bengali, Gujarati, Kannada, Malayalam or Odia character<\/td><td><\/td><td>Regional language campaigns, which is intentional and unavoidable<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">A 155-character promotional message written in plain ASCII costs one credit. The same message where an autocorrect turned two apostrophes into U+2019 becomes Unicode, needs 155 characters of UCS-2 capacity, and at 67 characters per concatenated segment that is three segments. <strong>One credit becomes three.<\/strong> The message is visually identical to the person who wrote it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Worse, the two characters that caused it will not show up in a visual review. They render as apostrophes.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Characters that are in GSM-7 but cost two units<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">There is a second, subtler trap. Ten characters live in the GSM-7 extension table and are encoded as an escape sequence followed by the character, consuming <strong>two<\/strong> of your 160 units each:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>{<\/code> <code>}<\/code> <code>[<\/code> <code>]<\/code> <code>~<\/code> <code>^<\/code> <code>\\<\/code> <code>|<\/code> and the euro sign <code>\u20ac<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A URL containing square brackets, or a message using a tilde, silently costs more than its character count suggests. A message that a naive <code>strlen()<\/code> reports as 158 characters can be 164 GSM-7 units and therefore two segments.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is why counting characters with <code>strlen()<\/code>, <code>.length<\/code> or <code>len()<\/code> and dividing by 160 gives the wrong answer in two separate directions.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/08\/encoding-cliff.webp\"><img loading=\"lazy\" decoding=\"async\" width=\"1000\" height=\"570\" src=\"https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/08\/encoding-cliff.webp\" alt=\"Diagram about The Encoding Cliff: One Character Can Triple Your Bill\" class=\"wp-image-2613\" srcset=\"https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/08\/encoding-cliff.webp 1000w, https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/08\/encoding-cliff-300x171.webp 300w, https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/08\/encoding-cliff-768x438.webp 768w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/a><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Regional language campaigns are not a mistake, they are a budget line<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If you are sending in Hindi, Tamil or Marathi, Unicode is correct and unavoidable. The point is not to avoid it, it is to <strong>budget for it separately<\/strong>. A Unicode campaign at the same message length costs roughly 2.3 times what the English version costs. Model the two message classes separately in your forecast rather than applying a single blended rate to your whole volume. Transactional and OTP routes on SMSGatewayCenter both list Unicode and regional language support, so the capability is there; the cost model just needs to reflect it.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"counting-segments-correctly-in-code\" class=\"wp-block-heading\">Counting Segments Correctly in Code<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A correct counter must do three things: detect whether every character is in the GSM-7 alphabet, count extension-table characters as two units, and apply the 160\/153 and 70\/67 rules. It must also iterate by Unicode code point rather than by UTF-16 code unit, or emoji outside the Basic Multilingual Plane will be counted twice.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">PHP<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\n\nfinal class SmsSegmentCounter\n{\n    \/** Base GSM 03.38 alphabet, one unit each. *\/\n    private const GSM_BASIC =\n        \"@\u00a3$\u00a5\u00e8\u00e9\u00f9\u00ec\u00f2\u00c7\\n\u00d8\u00f8\\r\u00c5\u00e5\u0394_\u03a6\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u039e\u00c6\u00e6\u00df\u00c9 !\\\"#\u00a4%&amp;'()*+,-.\/0123456789:;&lt;=&gt;?\"\n      . \"\u00a1ABCDEFGHIJKLMNOPQRSTUVWXYZ\u00c4\u00d6\u00d1\u00dc\u00a7\u00bfabcdefghijklmnopqrstuvwxyz\u00e4\u00f6\u00f1\u00fc\u00e0\";\n\n    \/** GSM 03.38 extension table, two units each (escape + char). *\/\n    private const GSM_EXTENDED = \"^{}\\\\&#91;~]|\u20ac\";\n\n    \/**\n     * @return array{encoding:string,units:int,segments:int,perSegment:int}\n     *\/\n    public static function count(string $message): array\n    {\n        $chars = preg_split('\/\/u', $message, -1, PREG_SPLIT_NO_EMPTY);\n        $units = 0;\n        $isGsm = true;\n\n        foreach ($chars as $char) {\n            if (mb_strpos(self::GSM_BASIC, $char, 0, 'UTF-8') !== false) {\n                $units += 1;\n            } elseif (mb_strpos(self::GSM_EXTENDED, $char, 0, 'UTF-8') !== false) {\n                $units += 2;\n            } else {\n                $isGsm = false;\n                break;\n            }\n        }\n\n        if (!$isGsm) {\n            \/\/ UCS-2: count UTF-16 code units, so non-BMP characters count as 2.\n            $units = mb_strlen(mb_convert_encoding($message, 'UTF-16BE', 'UTF-8'), '8bit') \/ 2;\n            $single = 70;\n            $multi  = 67;\n            $encoding = 'UNICODE';\n        } else {\n            $single = 160;\n            $multi  = 153;\n            $encoding = 'TEXT';\n        }\n\n        $segments = $units &lt;= $single\n            ? 1\n            : (int) ceil($units \/ $multi);\n\n        return &#91;\n            'encoding'   =&gt; $encoding,\n            'units'      =&gt; (int) $units,\n            'segments'   =&gt; max(1, $segments),\n            'perSegment' =&gt; $units &lt;= $single ? $single : $multi,\n        ];\n    }\n}\n\n\/\/ A message that looks like one segment but is not.\n$a = \"Your order has been shipped and will arrive by Friday. Track it at \"\n   . \"https:\/\/ex.co\/t\/9F3K2 or call 1800-000-000 for help. Thank you for shopping with us!\";\nvar_dump(SmsSegmentCounter::count($a));\n\/\/ encoding TEXT, units 151, segments 1\n\n\/\/ The same copy after a word processor touched the apostrophe.\n$b = str_replace(\"'\", \"\\u{2019}\", $a . \" Don't reply.\");\nvar_dump(SmsSegmentCounter::count($b));\n\/\/ encoding UNICODE, segments 3\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is the same counter used in the <a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/sms-api-php-integration-tutorial\/\">SMS API in PHP integration tutorial<\/a>, where it sits in the message-building layer rather than being bolted on at send time.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Node.js<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>const GSM_BASIC = new Set(\n  \"@\u00a3$\u00a5\u00e8\u00e9\u00f9\u00ec\u00f2\u00c7\\n\u00d8\u00f8\\r\u00c5\u00e5\u0394_\u03a6\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u039e\u00c6\u00e6\u00df\u00c9 !\\\"#\u00a4%&amp;'()*+,-.\/0123456789:;&lt;=&gt;?\" +\n  \"\u00a1ABCDEFGHIJKLMNOPQRSTUVWXYZ\u00c4\u00d6\u00d1\u00dc\u00a7\u00bfabcdefghijklmnopqrstuvwxyz\u00e4\u00f6\u00f1\u00fc\u00e0\"\n);\nconst GSM_EXTENDED = new Set(\"^{}\\\\&#91;~]|\u20ac\");\n\nexport function countSegments(message) {\n  let units = 0;\n  let isGsm = true;\n\n  \/\/ Spread iterates by code point, not UTF-16 code unit.\n  for (const char of message) {\n    if (GSM_BASIC.has(char)) units += 1;\n    else if (GSM_EXTENDED.has(char)) units += 2;\n    else { isGsm = false; break; }\n  }\n\n  if (!isGsm) {\n    units = message.length; \/\/ .length IS UTF-16 code units, which is what UCS-2 bills.\n  }\n\n  const single = isGsm ? 160 : 70;\n  const multi = isGsm ? 153 : 67;\n  const segments = units &lt;= single ? 1 : Math.ceil(units \/ multi);\n\n  return {\n    encoding: isGsm ? \"TEXT\" : \"UNICODE\",\n    units,\n    segments: Math.max(1, segments),\n    perSegment: units &lt;= single ? single : multi,\n  };\n}\n\nconsole.log(countSegments(\"Hello world\"));\n\/\/ { encoding: 'TEXT', units: 11, segments: 1, perSegment: 160 }\n\nconsole.log(countSegments(\"\u0928\u092e\u0938\u094d\u0924\u0947, \u0906\u092a\u0915\u093e \u0911\u0930\u094d\u0921\u0930 \u092d\u0947\u091c \u0926\u093f\u092f\u093e \u0917\u092f\u093e \u0939\u0948\u0964\"));\n\/\/ { encoding: 'UNICODE', ... }\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Note the deliberate asymmetry: in the GSM branch we iterate code points, and in the Unicode branch we use <code>.length<\/code> precisely because UCS-2 billing counts 16-bit units, so an astral-plane emoji correctly counts as two. The full version, with the surrounding client and queue, is in the <a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/sms-api-nodejs-integration-tutorial\/\">SMS API in Node.js integration tutorial<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Python<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>from math import ceil\n\nGSM_BASIC = set(\n    \"@\u00a3$\u00a5\u00e8\u00e9\u00f9\u00ec\u00f2\u00c7\\n\u00d8\u00f8\\r\u00c5\u00e5\u0394_\u03a6\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u039e\u00c6\u00e6\u00df\u00c9 !\\\"#\u00a4%&amp;'()*+,-.\/0123456789:;&lt;=&gt;?\"\n    \"\u00a1ABCDEFGHIJKLMNOPQRSTUVWXYZ\u00c4\u00d6\u00d1\u00dc\u00a7\u00bfabcdefghijklmnopqrstuvwxyz\u00e4\u00f6\u00f1\u00fc\u00e0\"\n)\nGSM_EXTENDED = set(\"^{}\\\\&#91;~]|\u20ac\")\n\n\ndef count_segments(message: str) -&gt; dict:\n    units = 0\n    is_gsm = True\n\n    for char in message:\n        if char in GSM_BASIC:\n            units += 1\n        elif char in GSM_EXTENDED:\n            units += 2\n        else:\n            is_gsm = False\n            break\n\n    if not is_gsm:\n        # UCS-2 bills 16-bit units, so encode and halve the byte count.\n        units = len(message.encode(\"utf-16-be\")) \/\/ 2\n\n    single, multi = (160, 153) if is_gsm else (70, 67)\n    segments = 1 if units &lt;= single else ceil(units \/ multi)\n\n    return {\n        \"encoding\": \"TEXT\" if is_gsm else \"UNICODE\",\n        \"units\": units,\n        \"segments\": max(1, segments),\n        \"per_segment\": single if units &lt;= single else multi,\n    }\n\n\nif __name__ == \"__main__\":\n    print(count_segments(\"Pay Rs. 1,499 by 5 PM to avoid late fees.\"))\n    # {'encoding': 'TEXT', 'units': 40, 'segments': 1, 'per_segment': 160}\n\n    print(count_segments(\"Pay \u20b91,499 by 5 PM to avoid late fees.\"))\n    # {'encoding': 'UNICODE', ...}  &lt;- the rupee sign alone did this\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The last two lines of that example are the entire article in miniature. Writing <code>Rs.<\/code> instead of <code>\u20b9<\/code> keeps the message in GSM-7. It is a two-character copy change with a permanent effect on unit economics.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Java<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>import java.nio.charset.StandardCharsets;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\npublic final class SmsSegmentCounter {\n\n    private static final String BASIC =\n        \"@\u00a3$\u00a5\u00e8\u00e9\u00f9\u00ec\u00f2\u00c7\\n\u00d8\u00f8\\r\u00c5\u00e5\u0394_\u03a6\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u039e\u00c6\u00e6\u00df\u00c9 !\\\"#\u00a4%&amp;'()*+,-.\/0123456789:;&lt;=&gt;?\"\n      + \"\u00a1ABCDEFGHIJKLMNOPQRSTUVWXYZ\u00c4\u00d6\u00d1\u00dc\u00a7\u00bfabcdefghijklmnopqrstuvwxyz\u00e4\u00f6\u00f1\u00fc\u00e0\";\n\n    private static final Set&lt;Integer&gt; GSM_BASIC =\n        BASIC.codePoints().boxed().collect(Collectors.toSet());\n\n    private static final Set&lt;Integer&gt; GSM_EXTENDED =\n        \"^{}\\\\&#91;~]|\u20ac\".codePoints().boxed().collect(Collectors.toSet());\n\n    public record Result(String encoding, int units, int segments, int perSegment) {}\n\n    public static Result count(String message) {\n        int units = 0;\n        boolean isGsm = true;\n\n        for (int cp : message.codePoints().toArray()) {\n            if (GSM_BASIC.contains(cp)) units += 1;\n            else if (GSM_EXTENDED.contains(cp)) units += 2;\n            else { isGsm = false; break; }\n        }\n\n        if (!isGsm) {\n            units = message.getBytes(StandardCharsets.UTF_16BE).length \/ 2;\n        }\n\n        int single = isGsm ? 160 : 70;\n        int multi = isGsm ? 153 : 67;\n        int segments = units &lt;= single ? 1 : (int) Math.ceil((double) units \/ multi);\n\n        return new Result(isGsm ? \"TEXT\" : \"UNICODE\",\n                          units, Math.max(1, segments),\n                          units &lt;= single ? single : multi);\n    }\n}\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"pricing-a-message-before-you-send-it\" class=\"wp-block-heading\">Pricing a Message Before You Send It<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">You do not have to trust your own counter. SMSGatewayCenter exposes a server-side length and credit calculator that returns the platform&#8217;s own verdict on any message string, which is the number that will actually be billed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Endpoint:<\/strong> <code>POST https:\/\/unify.smsgateway.center\/SMSApi\/info\/msg<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Parameters:<\/strong> <code>userid<\/code> and <code>password<\/code>, or an <code>apiKey<\/code> HTTP header, plus <code>msg<\/code> (the message text) and <code>output<\/code> (<code>json<\/code> or <code>plain<\/code>, default <code>plain<\/code>).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">cURL<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>curl --request POST \\\n  --url https:\/\/unify.smsgateway.center\/SMSApi\/info\/msg \\\n  --header 'content-type: application\/x-www-form-urlencoded' \\\n  --data-urlencode 'userid=YourUsername' \\\n  --data-urlencode 'password=YourPassword' \\\n  --data-urlencode 'msg=Hello World' \\\n  --data-urlencode 'output=json'\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Response<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"response\": {\n    \"api\": \"info\",\n    \"action\": \"msg\",\n    \"status\": \"success\",\n    \"msg\": \"success\",\n    \"code\": \"200\",\n    \"count\": 1,\n    \"msgList\": {\n      \"encoding\": \"TEXT\",\n      \"length\": 11,\n      \"remaining\": 149,\n      \"credit\": 1\n    }\n  }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Four fields, and every one of them is operationally useful:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Field<\/th><th>Meaning<\/th><th>What to do with it<\/th><\/tr><\/thead><tbody><tr><td><code>encoding<\/code><\/td><td><code>TEXT<\/code> for GSM-7, otherwise Unicode<\/td><td>Alert if a template you expect to be English comes back as Unicode. This is your autocorrect detector.<\/td><\/tr><tr><td><code>length<\/code><\/td><td>Units consumed by the message<\/td><td>Compare against your local counter in CI; a mismatch means your counter has a bug<\/td><\/tr><tr><td><code>remaining<\/code><\/td><td>Characters left in the current segment<\/td><td>Surface this in your template editor so copywriters see the cliff approaching<\/td><\/tr><tr><td><code>credit<\/code><\/td><td>Credits that will be billed<\/td><td>The number to multiply by your slab rate<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The endpoint is documented at <a href=\"https:\/\/www.smsgatewaycenter.com\/developer-api\/get-sms-length-cost\/\">Get SMS Length and Cost<\/a> and it is a POST-only HTTP API.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The one place every team should call this<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Not at send time. At <strong>template save time<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Calling it per outbound message adds a network round trip to your hot path for no benefit, because your local counter should already be correct. Call it once when a template is created or edited, store the returned <code>encoding<\/code> and <code>credit<\/code>, and block the save if a template that is flagged English comes back as Unicode. That single guard catches the autocorrect problem permanently, at the only moment when a human is present to fix it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Wire it into CI as well, alongside the pre-send template validation via <code>SMSApi\/validateTemplate<\/code> described in the <a href=\"https:\/\/www.smsgatewaycenter.com\/dlt-sms\/\">DLT registration guide<\/a>. One check confirms the message is compliant, the other confirms it is affordable.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"variable-two-route-class\" class=\"wp-block-heading\">Variable Two: Route Class<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">India does not have one bulk SMS product, it has several regulatory categories with different rules and different rates. Choosing the wrong one costs money in both directions: overpaying for a transactional route to send marketing, or having marketing on a promotional route blocked because it went to a DND number at 10 PM.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Route class<\/th><th>What it carries<\/th><th>Delivery window<\/th><th>DND handling<\/th><th>Sender ID<\/th><\/tr><\/thead><tbody><tr><td><strong>Transactional<\/strong><\/td><td>Order confirmations, bank alerts, ticket details, service updates tied to an existing relationship<\/td><td>24&#215;7<\/td><td>Delivered to DND numbers<\/td><td>Static alphanumeric<\/td><\/tr><tr><td><strong>Promotional<\/strong><\/td><td>Offers, discounts, campaign blasts to prospects<\/td><td>10 AM to 9 PM<\/td><td>Filtered against DND<\/td><td>Numeric<\/td><\/tr><tr><td><strong>OTP<\/strong><\/td><td>One-time passwords and verification codes only<\/td><td>24&#215;7<\/td><td>Delivered to DND numbers<\/td><td>Alphanumeric<\/td><\/tr><tr><td><strong>Service Implicit<\/strong><\/td><td>Messages arising from an existing transaction or relationship where consent is implied<\/td><td>Per TRAI rules<\/td><td>Per registered consent<\/td><td>Registered header<\/td><\/tr><tr><td><strong>Service Explicit<\/strong><\/td><td>Messages requiring recorded explicit consent from the subscriber<\/td><td>Per <a href=\"https:\/\/www.trai.gov.in\/\" data-type=\"link\" data-id=\"https:\/\/www.trai.gov.in\/\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">TRAI<\/a> rules<\/td><td>Requires explicit consent record<\/td><td>Registered header<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The implicit and explicit distinction is the one that trips up most teams, because it is a <strong>consent<\/strong> classification rather than a content classification, and misclassifying it is a compliance problem before it is a cost problem. SMSGatewayCenter maintains dedicated explainers for <a href=\"https:\/\/www.smsgatewaycenter.com\/service-implicit-sms\/\">service implicit SMS<\/a> and <a href=\"https:\/\/www.smsgatewaycenter.com\/service-explicit-sms\/\">service explicit SMS<\/a>, and a broader overview at <a href=\"https:\/\/www.smsgatewaycenter.com\/sms-types\/\">SMS types<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The cost angle: routing a message on the wrong class does not usually save money, it causes it to be scrubbed or blocked, at which point you have paid for a message nobody received. That is variable four in action, and it is why classification is a cost control, not just a compliance control.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"what-the-published-slabs-actually-say\" class=\"wp-block-heading\">What the Published Slabs Actually Say<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">These are the rates published on the SMSGatewayCenter pricing pages at the time of writing. All are for India, all carry one year validity, and all are quoted plus GST at the prevailing rate. Check the live pages before budgeting, since published rates change.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Transactional SMS<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Source: <a href=\"https:\/\/www.smsgatewaycenter.com\/transactional-sms-pricing\/\">Transactional SMS Pricing<\/a>. 24&#215;7 delivery, DND numbers delivered, static alphanumeric sender ID.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>SMS units<\/th><th>Rate per SMS<\/th><th>Plan price<\/th><\/tr><\/thead><tbody><tr><td>10,000<\/td><td>INR 0.17<\/td><td>INR 1,650 + GST<\/td><\/tr><tr><td>50,000<\/td><td>INR 0.15<\/td><td>INR 7,500 + GST<\/td><\/tr><tr><td>1,00,000<\/td><td>INR 0.14<\/td><td>INR 14,000 + GST<\/td><\/tr><tr><td>5,00,000<\/td><td>INR 0.12<\/td><td>INR 60,000 + GST<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Promotional SMS<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Source: <a href=\"https:\/\/www.smsgatewaycenter.com\/promotional-sms-pricing\/\">Promotional SMS Pricing<\/a>. 10 AM to 9 PM delivery, DND numbers filtered, numeric sender ID.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>SMS units<\/th><th>Rate per SMS<\/th><th>Plan price<\/th><\/tr><\/thead><tbody><tr><td>50,000<\/td><td>INR 0.13<\/td><td>INR 6,500 + GST<\/td><\/tr><tr><td>1,00,000<\/td><td>INR 0.12<\/td><td>INR 12,000 + GST<\/td><\/tr><tr><td>5,00,000<\/td><td>INR 0.11<\/td><td>INR 55,000 + GST<\/td><\/tr><tr><td>10,00,000<\/td><td>INR 0.10<\/td><td>INR 1,00,000 + GST<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">OTP SMS<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Source: <a href=\"https:\/\/www.smsgatewaycenter.com\/otp-sms-pricing\/\">OTP SMS Pricing<\/a>. 24&#215;7 delivery.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>SMS units<\/th><th>Rate per SMS<\/th><th>Plan price<\/th><\/tr><\/thead><tbody><tr><td>10,000<\/td><td>INR 0.17<\/td><td>INR 1,650 + GST<\/td><\/tr><tr><td>50,000<\/td><td>INR 0.15<\/td><td>INR 7,500 + GST<\/td><\/tr><tr><td>1,00,000<\/td><td>INR 0.14<\/td><td>INR 14,000 + GST<\/td><\/tr><tr><td>5,00,000<\/td><td>INR 0.13<\/td><td>INR 65,000 + GST<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">What these tables tell you that a price comparison does not<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Three things are worth extracting.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>First, the transactional-to-promotional spread narrows as volume rises.<\/strong> At 50,000 units the gap is INR 0.15 against INR 0.13, about 15 percent. At 5,00,000 it is INR 0.12 against INR 0.11, about 9 percent. If you are large, splitting traffic across two route classes to chase the promotional rate buys you less than the operational complexity costs. If you are small, the entry barrier matters more: the transactional route starts at a 10,000-unit slab while promotional starts at 50,000.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Second, OTP diverges from transactional only at the top slab.<\/strong> The two are identical at 10,000, 50,000 and 1,00,000 units. At 5,00,000 units OTP is INR 0.13 against transactional INR 0.12. Below half a million messages, OTP and transactional pricing are the same product economically, so route the traffic by what it is rather than by what you think it costs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Third, the entry slab has a rounding artefact worth noticing.<\/strong> INR 1,650 divided by 10,000 is INR 0.165, advertised as INR 0.17. That is in your favour, marginally, and it is a good reminder to divide plan price by units yourself rather than reading the headline rate.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Credits do not expire in twelve months, but they do expire<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">All three routes carry one year validity, and the pricing pages note that units carry forward if you renew within the due date. That structure has a cost implication most teams miss: buying a large slab to get the low rate and then under-sending means the effective rate is plan price divided by messages <strong>actually sent<\/strong>, not by units purchased.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Slab bought<\/th><th>List rate<\/th><th>Messages actually sent in the year<\/th><th>Effective rate<\/th><\/tr><\/thead><tbody><tr><td>5,00,000 transactional at INR 60,000<\/td><td>INR 0.12<\/td><td>5,00,000<\/td><td>INR 0.12<\/td><\/tr><tr><td>5,00,000 transactional at INR 60,000<\/td><td>INR 0.12<\/td><td>3,00,000<\/td><td>INR 0.20<\/td><\/tr><tr><td>1,00,000 transactional at INR 14,000<\/td><td>INR 0.14<\/td><td>1,00,000<\/td><td>INR 0.14<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Under-utilising a 5,00,000 slab by 40 percent produces a worse effective rate than buying the 1,00,000 slab at list. Buy the slab that matches your forecast, not the slab with the best headline number, and renew within the due date so unused units carry forward rather than lapsing.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"variable-three-volume-slabs-and-the-commitment-trap\" class=\"wp-block-heading\">Variable Three: Volume Slabs and the Commitment Trap<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The slab structure rewards volume, which is normal and rational. The trap is treating a slab as a discount rather than as a commitment.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Two questions decide whether a bigger slab is worth it:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>What is your forecast confidence?<\/strong> If your annual volume estimate has a plus or minus 30 percent error bar, and the slab above requires you to be at the top of that range to break even, buy the smaller one. The maths above shows that under-utilisation destroys the discount faster than the discount creates value.<\/li>\n\n\n\n<li><strong>Is your volume seasonal or steady?<\/strong> One year validity plus carry-forward on renewal makes seasonality survivable, but only if you actually renew on time. A festive-season retailer sending 70 percent of annual volume in one quarter should size the slab to the annual total and diarise the renewal date, not size it to peak-quarter run rate.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">There is also a floor effect nobody mentions. Below roughly 50,000 messages a year, the per-SMS rate barely matters. At the 10,000-unit transactional slab, the entire annual spend is INR 1,650 plus GST. The DLT registration overhead and the engineering hours spent on integration dominate that figure by an order of magnitude. Small senders should optimise for integration simplicity and support quality, not for two paise.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"where-the-deduction-actually-happens\" class=\"wp-block-heading\">Variable Four: Where the Deduction Actually Happens<\/h2>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/08\/where-deduction-actually-happens.webp\"><img loading=\"lazy\" decoding=\"async\" width=\"1000\" height=\"570\" src=\"https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/08\/where-deduction-actually-happens.webp\" alt=\"Diagram about Where the Deduction Actually Happens\" class=\"wp-image-2614\" srcset=\"https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/08\/where-deduction-actually-happens.webp 1000w, https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/08\/where-deduction-actually-happens-300x171.webp 300w, https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/08\/where-deduction-actually-happens-768x438.webp 768w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/a><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">This is the variable that produces the largest gap between expected and actual spend, and it is stated plainly in the published pricing terms:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">&#8220;The applicable per-SMS rate will be deducted from your wallet while sending SMS.&#8221;<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">and<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">&#8220;Credits are non-refundable once SMS is successfully submitted to the operator.&#8221;<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Read that as a state machine. Here is the lifecycle of a message and where money leaves:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Stage<\/th><th>What it means<\/th><th>Billed?<\/th><\/tr><\/thead><tbody><tr><td>1. API request received<\/td><td>Your call reached <code>SMSApi\/send<\/code><\/td><td>No<\/td><\/tr><tr><td>2. Validation<\/td><td>Auth, sender ID, DLT template match, mobile number format<\/td><td>No, a rejection here is free<\/td><\/tr><tr><td>3. <strong>Submission to operator accepted<\/strong><\/td><td>The operator has taken the message<\/td><td><strong>Yes, deduction happens here<\/strong><\/td><\/tr><tr><td>4. Operator attempts delivery<\/td><td>Handset reachable? Subscriber active?<\/td><td>Already billed<\/td><\/tr><tr><td>5. DLR returned<\/td><td><code>DELIVERED<\/code>, <code>UNDELIV<\/code>, <code>EXPIRED<\/code>, <code>ABSENT_SUB<\/code> and so on<\/td><td>Already billed<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Everything from stage 3 onward is sunk cost. A message that reaches an <a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/kb\/what-does-absent_sub-absent-subscriber-unreachable-mean\/\">absent subscriber<\/a>, or is undelivered because the <a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/kb\/what-does-operator_down-operator-unreachable-mean\/\">operator is unreachable<\/a>, has already cost you a credit.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What this means practically<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Your effective per-delivered-message rate is your slab rate divided by your delivery rate.<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Slab rate<\/th><th>Delivery rate<\/th><th>Effective cost per delivered message<\/th><\/tr><\/thead><tbody><tr><td>INR 0.14<\/td><td>98%<\/td><td>INR 0.143<\/td><\/tr><tr><td>INR 0.14<\/td><td>94%<\/td><td>INR 0.149<\/td><\/tr><tr><td>INR 0.14<\/td><td>88%<\/td><td>INR 0.159<\/td><\/tr><tr><td>INR 0.14<\/td><td>80%<\/td><td>INR 0.175<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">A provider at INR 0.12 with an 85 percent delivery rate costs INR 0.141 per delivered message. A provider at INR 0.14 with a 98 percent delivery rate costs INR 0.143. Those are functionally the same price, and the second one is a far better product because the messages arrive. This is precisely why comparing providers on the advertised rate is close to meaningless, and why the recommendation to compare actual delivery reports across providers, made in the guide on <a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/best-bulk-sms-providers-in-india\/\">choosing bulk SMS providers in India<\/a>, is the correct methodology.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Failures that are free<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Not everything costs money. Rejections at stage 2 do not reach the operator, so they do not consume a credit. That includes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Authentication failures<\/li>\n\n\n\n<li>Invalid mobile number format, reported in the <code>invalidMobile<\/code> field of the send response<\/li>\n\n\n\n<li><a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/kb\/what-does-template_check_failed-template-mismatch-mean\/\">Template mismatch<\/a>, which returns status code 188 from <code>SMSApi\/validateTemplate<\/code> and is rejected before submission<\/li>\n\n\n\n<li><a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/kb\/what-does-blocked_senderid-mean\/\">Blocked sender ID<\/a> and <a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/kb\/what-does-invalid_senderid-invalid-senderid-mean\/\">invalid sender ID<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/kb\/what-does-duplicate_fail-duplicate-fail-mean\/\">Duplicate rejection<\/a> when <code>duplicatecheck<\/code> is enabled<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The last one is a genuine cost control. Enabling <code>duplicatecheck<\/code> on the send call stops a re-fired job from paying twice for the same message. It is not a substitute for idempotency in your own code, but it is a useful second line of defence, and it is free to turn on.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The engineering implication is direct: <strong>push every failure you can into stage 2.<\/strong> Validate mobile numbers with a proper library before submitting, validate templates against <code>validateTemplate<\/code> in CI, and check sender ID and encoding at template save time. Every failure you move from stage 4 to stage 2 converts a billed loss into a free rejection.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"variable-five-retry-amplification\" class=\"wp-block-heading\">Variable Five: Retry Amplification<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This is the cheapest problem to fix and the most expensive one to ignore.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A retry loop that does not distinguish between permanent and transient failures pays full price for every attempt. Consider a modest campaign:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Scenario<\/th><th>Messages<\/th><th>Failures<\/th><th>Retry policy<\/th><th>Total credits billed<\/th><\/tr><\/thead><tbody><tr><td>No retries<\/td><td>1,00,000<\/td><td>6,000<\/td><td>none<\/td><td>1,00,000<\/td><\/tr><tr><td>Blind retry, 3 attempts<\/td><td>1,00,000<\/td><td>6,000<\/td><td>retry all failures 3x<\/td><td>1,18,000<\/td><\/tr><tr><td>Classified retry<\/td><td>1,00,000<\/td><td>6,000 (4,000 permanent, 2,000 transient)<\/td><td>retry transient only, 2x<\/td><td>1,04,000<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Blind retrying costs 18 percent more than not retrying at all, and the extra 18 percent buys you recovery only on the 2,000 genuinely transient failures. The classified policy recovers the same messages for 4 percent.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The classification rule is simple and the delivery error codes make it mechanical:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Class<\/th><th>Examples<\/th><th>Retry?<\/th><th>Reason<\/th><\/tr><\/thead><tbody><tr><td><strong>Permanent<\/strong><\/td><td>Invalid number, <code>ABSENT_SUB<\/code> on a dead number, blocked sender ID, template mismatch, DND block on promotional<\/td><td><strong>Never<\/strong><\/td><td>The next attempt will fail identically and cost the same<\/td><\/tr><tr><td><strong>Transient<\/strong><\/td><td><code>OPERATOR_DOWN<\/code>, <code>SYSTEM_FAIL<\/code>, network timeout, HTTP 5xx<\/td><td><strong>Yes, with backoff<\/strong><\/td><td>The condition is time-limited<\/td><\/tr><tr><td><strong>Ambiguous<\/strong><\/td><td>Request timed out with no response<\/td><td><strong>Yes, but only with an idempotency key<\/strong><\/td><td>You do not know whether the platform accepted it, and retrying without a key risks paying twice for a delivered message<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">That third row is the expensive one. A client-side timeout does not mean the message was not submitted. If it was submitted, you were already billed; retrying bills you again and the recipient gets the message twice. The fix is a business-level idempotency key plus <code>duplicatecheck<\/code>, covered in depth in the <a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/sms-api-retry-strategy-handling-failed-messages\/\">SMS API retry strategy guide<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can fetch the authoritative code lists programmatically rather than hardcoding them, via <code>POST https:\/\/unify.smsgateway.center\/SMSApi\/info\/deliverycodes<\/code> for <a href=\"https:\/\/www.smsgatewaycenter.com\/developer-api\/get-delivery-error-code-list\/\">delivery error codes<\/a> and <code>POST https:\/\/unify.smsgateway.center\/SMSApi\/info\/responsecodes<\/code> for <a href=\"https:\/\/www.smsgatewaycenter.com\/developer-api\/get-api-response-error-code-list\/\">API response codes<\/a>. Build your permanent-versus-transient map from those lists and keep it in version control.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Suppression lists are a cost control<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If a number returns a permanent failure three campaigns running, stop sending to it. A stale list of 40,000 dead numbers on a monthly campaign is 4,80,000 wasted credits a year, which at INR 0.12 is INR 57,600 spent on messages that cannot arrive. Suppression is boring, unglamorous and one of the highest-return things you can build.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"variable-six-dlt-and-the-costs-that-are-not-per-sms\" class=\"wp-block-heading\">Variable Six: DLT and the Costs That Are Not Per-SMS<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">DLT registration is mandatory for A2P messaging in India under TRAI&#8217;s <a href=\"https:\/\/www.trai.gov.in\/\">Telecom Commercial Communications Customer Preference Regulations<\/a>, and the SMSGatewayCenter pricing terms state it directly: DLT registration is required to send transactional SMS, and the sender ID and template must be pre-approved for delivery to happen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The costs here sit outside the per-SMS rate:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Cost item<\/th><th>Nature<\/th><th>Notes<\/th><\/tr><\/thead><tbody><tr><td>Principal Entity registration on an operator DLT portal<\/td><td>One-time<\/td><td>Charged by the DLT portal operator, not by the messaging provider<\/td><\/tr><tr><td>Sender ID (header) registration<\/td><td>One-time per header<\/td><td>Alphanumeric, up to six characters for India<\/td><\/tr><tr><td>Template registration<\/td><td>One-time per template<\/td><td>Every content variant needs its own approved template<\/td><\/tr><tr><td>Template maintenance<\/td><td>Ongoing<\/td><td>Copy changes require re-approval, which means lead time<\/td><\/tr><tr><td>PE-TM binding<\/td><td>Operational<\/td><td>Binding your Principal Entity to a Telemarketer, which is what breaks when you switch providers<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The per-SMS impact is indirect but real. <strong>Template rigidity forces message design decisions that affect segment count.<\/strong> If your approved template is 168 characters and you cannot change it without re-approval, you are paying two credits per message until the next approval cycle. Getting the template under 160 GSM-7 units <strong>before<\/strong> submitting it for approval is a one-time act with a permanent recurring saving.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The other DLT cost is switching friction. PE-TM binding means your registrations are tied to your current telemarketer. Moving providers is not free, which weakens your negotiating position at renewal. Budget for the transition if you plan to move; the <a href=\"https:\/\/www.smsgatewaycenter.com\/dlt-sms\/\">DLT SMS hub<\/a> and the <a href=\"https:\/\/www.smsgatewaycenter.com\/pe-tm-binding-chain-process-in-dlt\/\">PE-TM binding chain explainer<\/a> cover the mechanics.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We are deliberately not publishing DLT portal registration fees here. They are set by the individual operator portals, they differ between them, and they change. Check the portal you are registering with.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"dnd-scrubbing-and-who-pays-for-a-blocked-message\" class=\"wp-block-heading\">DND Scrubbing and Who Pays for a Blocked Message<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">DND is a route-class question, and the two behaviours are explicitly different in the published plan features.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Route<\/th><th>DND behaviour per published plan features<\/th><\/tr><\/thead><tbody><tr><td>Transactional<\/td><td>&#8220;DND Numbers Delivered&#8221;<\/td><\/tr><tr><td>OTP<\/td><td>24&#215;7 delivery<\/td><\/tr><tr><td>Promotional<\/td><td>&#8220;DND Numbers Filtered&#8221;<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">For promotional traffic, scrubbing happens against the DND registry before or at submission. A number filtered out at that point has not been submitted to the operator, so it does not consume a credit under the &#8220;billed on successful submission&#8221; rule.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The cost implication is not the scrubbing itself, it is what it does to your <strong>campaign economics<\/strong>. If 25 percent of your promotional list is DND-registered, a campaign you budgeted as 1,00,000 messages reaches 75,000 people. You have not overpaid, but your cost per reached contact is a third higher than your model assumed, and your cost per conversion is higher still.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Scrub your list before you plan the campaign, not after. Then size the slab against the reachable list, not the raw list.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"international-sms-a-different-cost-model-entirely\" class=\"wp-block-heading\">International SMS: A Different Cost Model Entirely<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Everything above describes India-domestic pricing. The published terms are explicit that the domestic rates apply for India only.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">International SMS runs on a different economic model:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Per-destination pricing.<\/strong> There is no single international rate. Each destination country and often each operator within it has its own termination cost, and the spread between the cheapest and most expensive destinations is very large.<\/li>\n\n\n\n<li><strong>Different sender ID rules.<\/strong> Some countries mandate alphanumeric sender IDs, some mandate numeric, some require pre-registration comparable to DLT, and some forbid sender ID overrides entirely.<\/li>\n\n\n\n<li><strong>Different regulatory overhead.<\/strong> There is no global equivalent of DLT, so compliance work is per-country.<\/li>\n\n\n\n<li><strong>Higher absolute rates.<\/strong> SMSGatewayCenter&#8217;s <a href=\"https:\/\/www.smsgatewaycenter.com\/international-sms-pricing\/\">international SMS pricing<\/a> plans start at INR 20,000 for high-volume campaigns.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">If you are budgeting a mixed domestic and international programme, model them as two separate cost lines with separate rates and separate delivery-rate assumptions. Blending them into one average rate produces a forecast that is wrong in both directions.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"reconciling-your-bill-against-the-dlr-api\" class=\"wp-block-heading\">Reconciling Your Bill Against the DLR API<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Everything above is a model. This section is how you check the model against reality, and it is the part almost nobody does.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The <a href=\"https:\/\/www.smsgatewaycenter.com\/developer-api\/sms-delivery-report\/\">delivery report API<\/a> returns per-message records that include both a <code>cost<\/code> field and a <code>length<\/code> field. That is enough to audit your spend message by message.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Endpoint:<\/strong> <code>GET<\/code> or <code>POST https:\/\/unify.smsgateway.center\/SMSApi\/reports\/status<\/code> with <code>method=getDlr<\/code>, <code>fromdate<\/code>, <code>todate<\/code>, <code>pageLimit<\/code>, and optionally <code>mobileNo<\/code>, <code>uuId<\/code> or <code>startCursor<\/code>, plus <code>output=json<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Each record in <code>reports_dlrList<\/code> carries: <code>country<\/code>, <code>amount<\/code>, <code>msgType<\/code>, <code>cost<\/code>, <code>deliveryTime<\/code>, <code>length<\/code>, <code>channel<\/code>, <code>msgId<\/code>, <code>cause<\/code>, <code>mobileNo<\/code>, <code>uuId<\/code>, <code>dltTemplateId<\/code>, <code>globalErrorCode<\/code>, <code>cursorId<\/code>, <code>network<\/code>, <code>senderName<\/code>, <code>flashMsg<\/code>, <code>submitTime<\/code>, <code>text<\/code>, <code>status<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">A reconciliation script<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>import os\nfrom collections import defaultdict\nfrom decimal import Decimal\n\nimport httpx\n\nBASE = \"https:\/\/unify.smsgateway.center\/SMSApi\/reports\/status\"\n\n\ndef fetch_dlr(from_date: str, to_date: str, page_limit: int = 500):\n    \"\"\"Yield DLR records, following the cursor until exhausted.\"\"\"\n    cursor = None\n    with httpx.Client(timeout=httpx.Timeout(30.0, connect=10.0)) as client:\n        while True:\n            data = {\n                \"method\": \"getDlr\",\n                \"userid\": os.environ&#91;\"SGC_USERID\"],\n                \"password\": os.environ&#91;\"SGC_PASSWORD\"],\n                \"fromdate\": from_date,\n                \"todate\": to_date,\n                \"pageLimit\": page_limit,\n                \"output\": \"json\",\n            }\n            if cursor:\n                data&#91;\"startCursor\"] = cursor\n\n            resp = client.post(BASE, data=data)\n            resp.raise_for_status()\n            payload = resp.json()\n\n            records = payload.get(\"reports_dlrList\") or &#91;]\n            if not records:\n                return\n\n            for record in records:\n                yield record\n\n            cursor = records&#91;-1].get(\"cursorId\")\n            if not cursor:\n                return\n\n\ndef reconcile(from_date: str, to_date: str) -&gt; None:\n    spend_by_status = defaultdict(Decimal)\n    spend_by_encoding = defaultdict(Decimal)\n    count_by_status = defaultdict(int)\n    wasted = Decimal(\"0\")\n    total = Decimal(\"0\")\n\n    for rec in fetch_dlr(from_date, to_date):\n        cost = Decimal(str(rec.get(\"cost\") or \"0\"))\n        status = (rec.get(\"status\") or \"UNKNOWN\").upper()\n        encoding = (rec.get(\"msgType\") or \"unknown\").lower()\n\n        total += cost\n        spend_by_status&#91;status] += cost\n        spend_by_encoding&#91;encoding] += cost\n        count_by_status&#91;status] += 1\n\n        # Anything that was billed but not delivered is pure loss.\n        if status not in (\"DELIVERED\", \"DELIVRD\"):\n            wasted += cost\n\n    print(f\"Total billed        : {total:.4f}\")\n    print(f\"Billed, not delivered: {wasted:.4f}  \"\n          f\"({(wasted \/ total * 100) if total else 0:.2f}% of spend)\")\n    print()\n    print(\"Spend by delivery status\")\n    for status, amount in sorted(spend_by_status.items(),\n                                 key=lambda kv: kv&#91;1], reverse=True):\n        print(f\"  {status:&lt;16} {count_by_status&#91;status]:&gt;8,}  {amount:&gt;12.4f}\")\n    print()\n    print(\"Spend by encoding\")\n    for encoding, amount in sorted(spend_by_encoding.items(),\n                                   key=lambda kv: kv&#91;1], reverse=True):\n        print(f\"  {encoding:&lt;16} {amount:&gt;12.4f}\")\n\n\nif __name__ == \"__main__\":\n    reconcile(\"2026-07-01\", \"2026-07-31\")\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Run this monthly. Three numbers come out of it and each one drives a decision:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Percentage of spend on undelivered messages.<\/strong> If this is above five percent, your list hygiene or your route selection is the problem, not your rate.<\/li>\n\n\n\n<li><strong>Spend by encoding.<\/strong> If <code>unicode<\/code> spend is materially higher than your intended regional-language volume, you have an encoding leak somewhere in a template.<\/li>\n\n\n\n<li><strong>Average <code>length<\/code> per message.<\/strong> If it sits just above a segment boundary, there is copy to trim.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>text<\/code> field in the DLR record also lets you find the specific offending templates. Group by <code>dltTemplateId<\/code>, sum <code>cost<\/code>, sort descending, and you have a ranked list of exactly which templates are consuming your budget. Fix the top three and you have usually found the whole problem. For background on interpreting the status values, see the knowledge base entry on <a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/kb\/understanding-delivery-reports-dlr\/\">understanding delivery reports<\/a>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"how-to-model-your-monthly-bulk-sms-cost\" class=\"wp-block-heading\">How to Model Your Monthly Bulk SMS Cost<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Six steps. Do them in this order.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 1: Enumerate your message classes.<\/strong> Not campaigns, classes. An OTP is a class. An order confirmation is a class. A festive offer is a class. Each has a distinct template, route and volume pattern.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 2: For each class, get the true credit count.<\/strong> Run the template through <code>SMSApi\/info\/msg<\/code> and record the <code>credit<\/code> value. Do not estimate from character count.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 3: Multiply volume by credits, not by messages.<\/strong> This is the step teams skip. A class sending 50,000 messages at 2 credits each consumes 1,00,000 units.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 4: Assign each class to a route and apply the correct slab rate.<\/strong> Sum the total units across all classes first to determine which slab you qualify for, then apply that rate.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 5: Divide by your expected delivery rate.<\/strong> Use the actual rate from your DLR reconciliation, not an aspirational one. This converts cost-per-submitted into cost-per-delivered.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 6: Add the retry factor and the fixed compliance overhead.<\/strong> Retry factor is 1.0 if you retry nothing and rises with your transient-failure rate times your attempt count. Compliance overhead is amortised annually.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The formula:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Monthly cost = \u03a3 (volume_class \u00d7 credits_class) \u00d7 slab_rate\n             \u00f7 delivery_rate\n             \u00d7 retry_factor\n             + (annual_compliance_cost \u00f7 12)\n             + GST at prevailing rate\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"a-worked-example\" class=\"wp-block-heading\">A Worked Example<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A mid-size Indian e-commerce company, using the published transactional and promotional slabs above.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Message classes<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Class<\/th><th>Monthly volume<\/th><th>Template length<\/th><th>Encoding<\/th><th>Credits each<\/th><th>Units consumed<\/th><th>Route<\/th><\/tr><\/thead><tbody><tr><td>OTP for login<\/td><td>120,000<\/td><td>92 chars<\/td><td>GSM-7<\/td><td>1<\/td><td>120,000<\/td><td>OTP<\/td><\/tr><tr><td>Order confirmation<\/td><td>65,000<\/td><td>148 chars<\/td><td>GSM-7<\/td><td>1<\/td><td>65,000<\/td><td>Transactional<\/td><\/tr><tr><td>Shipping update<\/td><td>65,000<\/td><td>171 chars<\/td><td>GSM-7<\/td><td>2<\/td><td>130,000<\/td><td>Transactional<\/td><\/tr><tr><td>Delivery confirmation<\/td><td>60,000<\/td><td>118 chars<\/td><td>GSM-7<\/td><td>1<\/td><td>60,000<\/td><td>Transactional<\/td><\/tr><tr><td>Festive offer (English)<\/td><td>200,000<\/td><td>139 chars<\/td><td>GSM-7<\/td><td>1<\/td><td>200,000<\/td><td>Promotional<\/td><\/tr><tr><td>Festive offer (Hindi)<\/td><td>80,000<\/td><td>96 chars<\/td><td>Unicode<\/td><td>2<\/td><td>160,000<\/td><td>Promotional<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Naive model, the one most teams build<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">510,000 messages per month. Apply a blended INR 0.13. Result: <strong>INR 66,300 per month.<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Actual model<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Transactional and OTP units: 120,000 + 65,000 + 130,000 + 60,000 = <strong>375,000 units.<\/strong> At an annualised 4.5 million units, this clears the 5,00,000 slab comfortably, so apply INR 0.12 for transactional and INR 0.13 for OTP at that tier.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>OTP: 120,000 \u00d7 INR 0.13 = INR 15,600<\/li>\n\n\n\n<li>Transactional: 255,000 \u00d7 INR 0.12 = INR 30,600<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Promotional units: 200,000 + 160,000 = <strong>360,000 units.<\/strong> Annualised well past 10,00,000, so INR 0.10.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Promotional: 360,000 \u00d7 INR 0.10 = INR 36,000<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Subtotal: <strong>INR 82,200.<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now apply delivery rate. Assume 96 percent on transactional and OTP, 89 percent on promotional after DND filtering and handset unreachability.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Transactional and OTP effective: INR 46,200 \u00f7 0.96 = INR 48,125 worth of value delivered per INR 46,200 spent, so the true cost per delivered message is 4.2 percent higher than list.<\/li>\n\n\n\n<li>Promotional at 89 percent is 12.4 percent higher than list.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Add a retry factor of 1.03 (a well-classified retry policy on a 4 percent transient failure rate).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Total: roughly INR 84,700 per month plus GST<\/strong>, against a naive estimate of INR 66,300. The naive model is <strong>28 percent low<\/strong>, and every rupee of the gap comes from the two rows highlighted below.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The two lines worth fixing<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Shipping update at 171 characters.<\/strong> Eleven characters over the boundary, costing a second credit on 65,000 messages a month. Trimming it to 160 characters or fewer saves 65,000 units a month, which at INR 0.12 is <strong>INR 7,800 a month, INR 93,600 a year<\/strong>. The change is deleting eleven characters of copy and getting the template re-approved once.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Hindi festive offer at 96 Unicode characters.<\/strong> Unavoidable encoding, but 96 characters needs two segments at 67 each, using 134 of a possible 134 units. Trimming to 70 characters would make it one credit and save 80,000 units a month, <strong>INR 8,000 a month at the promotional rate<\/strong>. Whether 70 Unicode characters is enough to carry the offer is a marketing judgement, but it should be a conscious one, made with the number in front of you.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Those two changes together are roughly <strong>INR 190,000 a year<\/strong> and require zero engineering work. That is the entire argument for treating segment counting as a first-class concern.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Want to know what your current templates actually cost? Sign in and run each one through the SMS Length and Cost API, or <a href=\"https:\/\/www.smsgatewaycenter.com\/contact\/\">talk to our team<\/a> and we will audit your message mix against your delivery reports.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"cost-reduction-checklist\" class=\"wp-block-heading\">Cost Reduction Checklist<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ordered by return on effort, highest first.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>#<\/th><th>Action<\/th><th>Effort<\/th><th>Typical saving<\/th><\/tr><\/thead><tbody><tr><td>1<\/td><td>Audit every template&#8217;s credit count via <code>SMSApi\/info\/msg<\/code> and trim anything one to fifteen characters over a boundary<\/td><td>Hours<\/td><td>5 to 20 percent<\/td><\/tr><tr><td>2<\/td><td>Block Unicode on templates intended to be English at save time<\/td><td>Hours<\/td><td>Highly variable, occasionally enormous<\/td><\/tr><tr><td>3<\/td><td>Classify failures and stop retrying permanent ones<\/td><td>Days<\/td><td>3 to 15 percent<\/td><\/tr><tr><td>4<\/td><td>Build a suppression list from repeated permanent failures<\/td><td>Days<\/td><td>2 to 8 percent<\/td><\/tr><tr><td>5<\/td><td>Enable <code>duplicatecheck<\/code> on the send call<\/td><td>Minutes<\/td><td>Insurance against re-fired jobs<\/td><\/tr><tr><td>6<\/td><td>Move number validation client-side, before submission<\/td><td>Days<\/td><td>Converts billed failures into free rejections<\/td><\/tr><tr><td>7<\/td><td>Scrub promotional lists against DND before sizing the campaign<\/td><td>Hours<\/td><td>Improves cost-per-reach, not raw cost<\/td><\/tr><tr><td>8<\/td><td>Size your slab against a realistic annual forecast, not peak run rate<\/td><td>Hours<\/td><td>Up to 40 percent effective rate<\/td><\/tr><tr><td>9<\/td><td>Reconcile monthly against DLR <code>cost<\/code> and <code>length<\/code><\/td><td>Hours per month<\/td><td>Finds everything above<\/td><\/tr><tr><td>10<\/td><td>Replace <code>\u20b9<\/code> with <code>Rs.<\/code>, straight quotes for curly, hyphens for en dashes in every template<\/td><td>Minutes<\/td><td>Prevents the single worst failure mode<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Item 10 takes ten minutes and is the highest-leverage line in the table. Do it today.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"channel-cost-decision-matrix\" class=\"wp-block-heading\">Channel Cost Decision Matrix<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">SMS is not always the cheapest channel for a given message, and it is not always the right one. Cost per message is the wrong comparison; cost per successful outcome is the right one.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Message type<\/th><th>SMS<\/th><th>WhatsApp Business API<\/th><th>RCS<\/th><th>Voice<\/th><th>Recommendation<\/th><\/tr><\/thead><tbody><tr><td>OTP for login<\/td><td>Reliable, universal, low unit cost<\/td><td>Fails silently when the user has no WhatsApp or no data, no timeout signal<\/td><td>Requires RCS-capable handset<\/td><td>Expensive, slow<\/td><td><strong>SMS<\/strong>, always as primary<\/td><\/tr><tr><td>Order confirmation<\/td><td>Cheap, universal<\/td><td>Richer, needs an active session or a template<\/td><td>Rich, patchy reach<\/td><td>Inappropriate<\/td><td><strong>SMS<\/strong>, with WhatsApp as an enhancement<\/td><\/tr><tr><td>Shipping tracking with a map<\/td><td>Two credits and a bare URL<\/td><td>Native rich card, better experience<\/td><td>Native rich card<\/td><td>No<\/td><td><strong>WhatsApp or RCS<\/strong> if reach allows<\/td><\/tr><tr><td>Festive promotional blast<\/td><td>Lowest unit cost, DND-filtered<\/td><td>Per-conversation pricing, template approval, opt-in required<\/td><td>Rich but limited reach<\/td><td>No<\/td><td><strong>SMS<\/strong> for reach and cost<\/td><\/tr><tr><td>Regional-language campaign<\/td><td>2 to 3x cost from Unicode<\/td><td>Same content, different pricing model<\/td><td>Rich<\/td><td>No<\/td><td>Model both, the crossover point moves with volume<\/td><\/tr><tr><td>Payment reminder needing acknowledgement<\/td><td>One-way, no read receipt<\/td><td>Two-way, read receipts, buttons<\/td><td>Two-way<\/td><td>High cost, high attention<\/td><td><strong>WhatsApp<\/strong> for engagement, SMS fallback<\/td><\/tr><tr><td>Critical alert to an unknown-capability device<\/td><td>Universal<\/td><td>Uncertain<\/td><td>Uncertain<\/td><td>Universal<\/td><td><strong>SMS<\/strong>, with voice escalation<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The asymmetry worth internalising: SMS fails observably and late, WhatsApp fails silently with no timeout signal. That difference is why WhatsApp should not be the primary OTP channel regardless of what the per-message arithmetic says. The full engineering comparison is in the <a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/whatsapp-business-api-vs-sms-api-2025\/\">SMS API versus WhatsApp Business API guide<\/a>, and if you want to run channels in sequence rather than choosing one, <a href=\"https:\/\/www.smsgatewaycenter.com\/multi-channel-fallback\/\">multi-channel fallback<\/a> lets you specify which delivery error codes trigger a step to the next channel rather than falling through on any failure.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"ten-costly-mistakes\" class=\"wp-block-heading\">Ten Costly Mistakes<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>1. Counting characters with <code>strlen()<\/code> and dividing by 160.<\/strong> Wrong twice: it misses the extension-table characters that cost two units, and it misses the 153-character concatenation rule.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>2. Letting copywriters draft templates in a word processor.<\/strong> Autocorrect converts apostrophes and dashes to non-GSM-7 characters that render identically and triple the cost. Draft in a plain-text field with a live credit counter.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>3. Comparing providers on advertised rate alone.<\/strong> A rate is meaningless without a delivery rate. Ask for delivery reports, run a paid pilot, and compute cost per delivered message.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>4. Retrying every failure the same way.<\/strong> Permanent failures never succeed and always bill. Classify before you retry.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>5. Assuming undelivered means unbilled.<\/strong> Deduction happens at successful submission to the operator. Your bill and your delivery report are different documents.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>6. Buying the largest slab for the headline rate.<\/strong> Under-utilisation produces a worse effective rate than a smaller slab at list price. Size to forecast.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>7. Submitting a DLT template without checking its credit count first.<\/strong> Once approved, you are stuck with the length until you re-approve. Optimise before submission, not after.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>8. Applying one blended rate across English and regional-language traffic.<\/strong> They differ by more than 2x. Model them separately.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>9. Never reconciling.<\/strong> If you have never run the DLR <code>cost<\/code> and <code>length<\/code> fields into a report, you do not know what you are actually spending money on. Almost every team that does this for the first time finds a surprise.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>10. Optimising the rate before optimising the credits.<\/strong> Negotiating two paise off INR 0.14 is a 14 percent saving on a variable you barely control. Fixing a template that is 11 characters over the boundary is a 50 percent saving on that class, available immediately, with no negotiation.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"faq\" class=\"wp-block-heading\">Frequently Asked Questions<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>What is the actual per-SMS cost of bulk SMS in India?<\/strong> It depends on route class and volume. On SMSGatewayCenter&#8217;s published slabs, transactional SMS ranges from INR 0.17 per SMS at 10,000 units down to INR 0.12 at 5,00,000 units, promotional from INR 0.13 at 50,000 units down to INR 0.10 at 10,00,000 units, and OTP from INR 0.17 at 10,000 units down to INR 0.13 at 5,00,000 units, all plus GST with one year validity. Your effective cost is higher than any of these figures because messages over 160 GSM-7 characters consume multiple credits and because credits are consumed on submission rather than delivery.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Why did my SMS bill increase without my volume changing?<\/strong> The most common cause by far is an encoding change. A template edit that introduced a curly apostrophe, an en dash, an ellipsis or a rupee sign converts the message from GSM-7 to Unicode, cutting per-credit capacity from 160 characters to 70 and often tripling the cost of that message class. Run each template through <code>POST https:\/\/unify.smsgateway.center\/SMSApi\/info\/msg<\/code> and check the <code>encoding<\/code> field. The second most common cause is a template edit that pushed length past 160 characters, doubling the credit count.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Is one SMS always one credit?<\/strong> No. One credit covers 160 characters in GSM-7 encoding or 70 characters in Unicode. Beyond that, messages are split into concatenated segments of 153 GSM-7 characters or 67 Unicode characters each, because the User Data Header consumes payload space. A 300-character English message is two credits, a 300-character Hindi message is five.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Do I pay for SMS that are not delivered?<\/strong> Yes, in most cases. SMSGatewayCenter&#8217;s published pricing terms state that credits are non-refundable once the SMS is successfully submitted to the operator. A message that fails after submission, for example because the handset is switched off or the subscriber is unreachable, has already consumed a credit. Failures caught before submission, such as invalid number format, template mismatch or an invalid sender ID, do not consume a credit.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Does a promotional message to a DND number cost me money?<\/strong> On a promotional route, DND numbers are filtered before submission to the operator, so a filtered number does not consume a credit. What it does affect is your reachable audience size, so scrub your list before sizing a campaign rather than after.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Why does sending in Hindi cost more than sending in English?<\/strong> Devanagari characters are outside the GSM-7 alphabet, so the message must be encoded in UCS-2, which fits 70 characters per credit instead of 160, or 67 per segment in a concatenated message. That is roughly 2.3 times the cost for the same character count. This is an inherent property of the GSM standard, not a provider charge.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>How can I check what an SMS will cost before I send it?<\/strong> Call <code>POST https:\/\/unify.smsgateway.center\/SMSApi\/info\/msg<\/code> with your credentials and the <code>msg<\/code> parameter. The response returns <code>encoding<\/code>, <code>length<\/code>, <code>remaining<\/code> and <code>credit<\/code>, where <code>credit<\/code> is the number of billable units. Documented at the <a href=\"https:\/\/www.smsgatewaycenter.com\/developer-api\/get-sms-length-cost\/\">Get SMS Length and Cost<\/a> page. Call it when a template is saved rather than on every send.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Which characters silently increase SMS cost?<\/strong> Two groups. First, characters outside GSM-7 that force the whole message to Unicode: curly quotes, en and em dashes, the ellipsis character, the rupee symbol, bullets, emoji and all Indic scripts. Second, ten characters that are in GSM-7 but consume two units each via the extension table: <code>{<\/code>, <code>}<\/code>, <code>[<\/code>, <code>]<\/code>, <code>~<\/code>, <code>^<\/code>, <code>\\<\/code>, <code>|<\/code> and the euro sign.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Is transactional or promotional SMS cheaper?<\/strong> Promotional is cheaper at every comparable volume, but the routes are not interchangeable. Promotional delivers only between 10 AM and 9 PM, filters DND numbers and uses a numeric sender ID. Transactional delivers 24&#215;7 to DND numbers with a static alphanumeric sender ID. Sending transactional content on a promotional route is a compliance failure, and the saving is not worth it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Does GST apply to bulk SMS in India?<\/strong> The published plan prices are quoted plus GST at the prevailing rate. Budget the tax on top of the plan price, and check the current rate with your finance team rather than assuming.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>What happens to unused SMS credits?<\/strong> Published plans carry one year validity, and the pricing pages note that units are carried forward if you renew within the due date. Credits that lapse are lost, which is why buying a slab significantly larger than your forecast is a false economy: your effective rate is plan price divided by messages actually sent.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Does DLT registration cost extra per message?<\/strong> No. DLT costs are fixed and one-time: Principal Entity registration, sender ID registration and template registration, all charged by the operator DLT portals rather than as a per-SMS surcharge. The indirect per-message cost comes from template rigidity, since an approved template that is 11 characters over the 160-character boundary costs a second credit on every message until you re-approve a shorter version.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>How do I audit what I actually spent on SMS?<\/strong> Call the delivery report API at <code>https:\/\/unify.smsgateway.center\/SMSApi\/reports\/status<\/code> with <code>method=getDlr<\/code>. Each record in <code>reports_dlrList<\/code> includes <code>cost<\/code>, <code>length<\/code>, <code>msgType<\/code>, <code>status<\/code> and <code>dltTemplateId<\/code>. Group by status to find spend on undelivered messages, group by <code>msgType<\/code> to find unexpected Unicode spend, and group by <code>dltTemplateId<\/code> to rank templates by total cost. That last grouping usually identifies the entire problem in three rows.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Is SMS cheaper than WhatsApp Business API?<\/strong> They use different pricing models, SMS per message segment and WhatsApp per conversation window, so a direct unit comparison is misleading. The right comparison is cost per successful outcome, which depends on your reach: WhatsApp reaches only users who have the app and data connectivity, and it fails silently when they do not, whereas SMS reaches any active handset and reports its failures. For OTP and critical alerts, SMS wins on reliability regardless of unit price. For rich, engagement-driven messaging to an opted-in audience, WhatsApp often wins on outcome.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Ready to size a plan against a real forecast rather than a headline rate? Review the current <a href=\"https:\/\/www.smsgatewaycenter.com\/transactional-sms-pricing\/\">transactional<\/a>, <a href=\"https:\/\/www.smsgatewaycenter.com\/promotional-sms-pricing\/\">promotional<\/a> and <a href=\"https:\/\/www.smsgatewaycenter.com\/otp-sms-pricing\/\">OTP<\/a> slabs, or <a href=\"https:\/\/www.smsgatewaycenter.com\/contact\/\">get in touch<\/a> for a quote built from your actual message classes. Check out our <a href=\"https:\/\/www.smsgatewaycenter.com\/fair-pricing-policy\/\">fair pricing policy<\/a>.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n","protected":false},"excerpt":{"rendered":"<p>The per-SMS rate on a pricing page is only one of six variables in your actual bill. This guide breaks down the real cost mechanics of bulk SMS in India: credit counting under GSM-7 and UCS-2, route class, DLT overhead, submission-time billing, retry amplification and DND scrubbing, with working code to price a message before you send it.<\/p>\n","protected":false},"author":118,"featured_media":2612,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2067],"tags":[86,2072,1277,2071,2070,2075,481,2077,2069,2068,27,2076,445,2074,2073],"class_list":["post-2611","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-guides","tag-bulk-sms","tag-bulk-sms-pricing-india","tag-dlt-compliance","tag-gsm-7-encoding","tag-otp-sms-pricing","tag-promotional-sms-pricing","tag-sms-api","tag-sms-billing","tag-sms-cost-calculation","tag-sms-credits","tag-sms-gateway-center","tag-sms-segment-counting","tag-smsgatewaycenter","tag-transactional-sms-pricing","tag-ucs-2-unicode-sms"],"_links":{"self":[{"href":"https:\/\/www.smsgatewaycenter.com\/blog\/wp-json\/wp\/v2\/posts\/2611","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.smsgatewaycenter.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.smsgatewaycenter.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.smsgatewaycenter.com\/blog\/wp-json\/wp\/v2\/users\/118"}],"replies":[{"embeddable":true,"href":"https:\/\/www.smsgatewaycenter.com\/blog\/wp-json\/wp\/v2\/comments?post=2611"}],"version-history":[{"count":0,"href":"https:\/\/www.smsgatewaycenter.com\/blog\/wp-json\/wp\/v2\/posts\/2611\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.smsgatewaycenter.com\/blog\/wp-json\/wp\/v2\/media\/2612"}],"wp:attachment":[{"href":"https:\/\/www.smsgatewaycenter.com\/blog\/wp-json\/wp\/v2\/media?parent=2611"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.smsgatewaycenter.com\/blog\/wp-json\/wp\/v2\/categories?post=2611"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.smsgatewaycenter.com\/blog\/wp-json\/wp\/v2\/tags?post=2611"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}