You build the campaign. You wire up the template. You send to ten thousand opted-in users. Twenty-four hours later your number quality rating is red, delivery rates have tanked, and support messages from paying customers are bouncing. This is not a hypothetical — it is a pattern that repeats every time a team treats WhatsApp like email and skips the reliability layer.
This article is for developers who own the sending stack. We will walk through the parts that most teams underestimate: opt-out handling at the right scope, suppression lists that do not over-block, number quality signals and what feeds them, template discipline, and throttled sending. These are unglamorous engineering concerns. They are also the difference between a channel that delivers revenue and one that gets shut down.
Why WhatsApp Campaigns Go Wrong in India
Email deliverability has a thirty-year-old ecosystem of standards and tooling. WhatsApp does not. Most developers entering the space copy the email mental model: maintain a list, fire API calls, watch opens. The problem is that WhatsApp's quality enforcement is harder and faster. A surge of low-quality sends can degrade your sending number's rating within hours, and a degraded number restricts how many users you can message per day. A fully disabled number has no recovery path beyond raising a support ticket that may take days.
The three common failure modes:
Unthrottled blasting. Sending thousands of messages in a burst triggers WhatsApp's anomaly detection. Even if every recipient consented, the sending pattern alone can flag the number.
Ignoring opt-outs. A user texts "STOP." The team logs it somewhere. No one wires up that log to the actual send path. The user gets messaged again next week. They report the message as spam. That report feeds directly into your number quality rating.
Over-suppressing transactional messages. A user opts out of marketing. The team suppresses all messages. Three days later the user raises a support ticket. Your bot cannot reach them because the suppression list has no scope — it blocked everything.
Each failure mode has a concrete engineering fix.
Opt-Out Handling Done Right
The naive implementation stores opt-outs in a column in the same table as contacts and filters at query time. This breaks down in several ways: the query may miss recent opt-outs if there is any replication lag, the filter lives in application code that other send paths may not call, and there is no concept of scope.
A more durable approach centers on a preference check that runs as a gate before any message leaves the system, regardless of which internal service or campaign job is initiating the send. The gate is not optional. It is not a soft check. The send does not proceed until the gate returns a clear signal.
Scope is the critical design decision. A user texting "STOP" in the context of a promotional campaign should not suppress a support reply when they raise a ticket. WhatsApp's own terms distinguish between marketing, utility, and authentication message categories. Your suppression logic should mirror this distinction.
A useful mental model: maintain a preference record per phone number that tracks suppression at the category level, not just a binary opted-out flag. Promotional suppression covers marketing messages, re-engagement nudges, lead-qualification flows, and sales outreach. It does not cover transactional confirmations, genuine support replies, OTPs, or security alerts. When the gate runs, it checks the category of the outgoing message against the preferences record and blocks or permits accordingly.
Illustrative logic — not the live Boni API, and not production-ready pseudocode, but a shape to reason about:
# ILLUSTRATIVE ONLY — not Boni's real API or schema
def can_send(phone: str, message_category: str) -> bool:
prefs = preference_store.get(phone)
if prefs is None:
return True # no known opt-out
suppressed_categories = prefs.get("suppressed_categories", [])
return message_category not in suppressed_categories
# Example categories: "marketing", "re_engagement", "support", "transactional", "authentication"
The moment a user sends "STOP" (or any recognized opt-out signal in your inbound handler), you write the suppression with category="marketing" and related promotional types. You do not write category="support". This is what makes the channel usable for genuine customer service even after a promotional opt-out.
Inbound handler latency matters. The opt-out must propagate to the preference store before the next outbound send attempt. If your campaign dispatcher batches sends for the next hour, and the preference write takes thirty seconds, you have a safe window. If your dispatcher fires immediately, you need the preference store write to be synchronous with the gate. Design around this ordering guarantee explicitly, not by assumption.
Suppression Lists Across Campaign Types
Suppression lists tend to proliferate. The marketing team has one. The product team running re-engagement has another. Support has a blocklist from angry users. None of them are the same list, and none of them are consistently checked before sends.
The fix is a single preference service as the authoritative source, with all send paths calling it. Not a shared database table — a service with a clear contract, so that the check can be audited, monitored for latency, and updated without touching every calling service.
A few properties worth building in:
- Write-through on inbound. Any inbound message handler that detects an opt-out keyword must write to the preference service synchronously before returning a 200 to WhatsApp's webhook.
- Hard failure on unavailability. If the preference service is down, the correct behavior is to not send — not to default to permitted. Fail closed.
- Category-scoped reads. The caller specifies the outgoing message category. The service returns permitted or suppressed for that category, not a global flag.
- Audit log. Every suppression write and every gate read that returns suppressed should be logged with a timestamp and a source. This is the record you produce when a user disputes receiving a message they opted out of.
Number Quality and Template Rules
WhatsApp assigns your sending number a quality rating based on signals from user behavior: blocks, reports, low engagement, and delivery failures. The rating affects your message sending limits. A degraded rating restricts reach. This is not negotiable and is not reversible by technical means — it recovers (or does not) based on sustained improved behavior over time.
What feeds bad quality signals:
- Messages reported as spam by recipients
- Messages blocked by recipients
- Very low read rates on promotional templates (users see the notification, ignore it, and eventually block)
- Template rejections due to policy violations
Template quality is a separate but related concern. WhatsApp reviews templates before approval. Templates that use vague or misleading button labels, that look like phishing attempts, or that promise things the message body does not deliver get rejected. Templates that get approved but generate high block rates from recipients can be paused by WhatsApp even after approval.
Developer disciplines that help:
- One template per genuine use case. Do not stuff multiple calls to action into a single template to avoid the review queue. Reviewers notice.
- Match the template header to the body. If the header says "Your order update," the body should be about an order. Marketing content in a utility template is a policy violation and will eventually surface.
- Test with a small cohort before full send. Send to a few hundred users first. Check delivery rates and any block signals before widening the audience.
- Retire templates that perform poorly. A template with a high block rate is actively damaging your number quality. Remove it even if it still has technical approval.
Pacing and Throttling Sends
Sending ten thousand messages in ten seconds is a bad idea regardless of template quality or opt-out discipline. The sending pattern itself is a signal, and it also strains your own infrastructure, making it harder to handle failed deliveries and retries gracefully.
Throttling means spreading sends over time. The implementation can be as simple as a rate limiter at the send layer or as structured as a queue with controlled consumption rate. A few patterns:
Token bucket at the send layer. The dispatcher acquires a token before each send. Tokens replenish at a fixed rate. Excess sends wait or are rescheduled. This is the simplest shape and is well-understood.
# ILLUSTRATIVE ONLY — not Boni's real API
import time
class TokenBucket:
def __init__(self, rate_per_second: float):
self.rate = rate_per_second
self.tokens = rate_per_second
self.last_check = time.monotonic()
def acquire(self) -> bool:
now = time.monotonic()
elapsed = now - self.last_check
self.tokens = min(self.rate, self.tokens + elapsed * self.rate)
self.last_check = now
if self.tokens >= 1:
self.tokens -= 1
return True
return False
Queue-based dispatch with a controlled worker pool. Campaign jobs write phone numbers and template parameters to a queue. A fixed number of worker processes consume from the queue at a bounded rate. This decouples campaign creation from sending speed and makes it easy to pause, reprioritize, or drain a campaign mid-flight.
Spread large campaigns over hours, not seconds. For a campaign targeting tens of thousands of users, a reasonable approach is to spread the full send over several hours, with the preference gate running at dispatch time (not at enqueue time) so that opt-outs that arrive during the send window are honored.
One frequently missed detail: retry logic needs backoff and category awareness. If a send fails with a transient error, the retry should use exponential backoff and should re-check the preference gate before retrying. An opt-out can arrive between the first attempt and the retry.
How Boni CPaaS Handles This Layer
Boni's CPaaS and campaign tooling is in pilot and rolling out to early-access partners. The design principles it implements are the ones described in this article: a central preference gate that runs before every send, category-scoped suppression so marketing opt-outs do not block support messages, paced dispatch rather than unthrottled blasting, and inbound opt-out handlers that write to the preference store synchronously.
The goal is that developers integrating Boni's sending APIs do not need to build and maintain this compliance layer themselves. The gate, the suppression store, the throttle, and the template-quality guardrails are part of the platform, not responsibilities pushed to the integration layer.
If you are building WhatsApp campaign infrastructure in India and want to work with a team that is thinking carefully about this layer, the API Hub is the starting point.
Disclosure: This article is published by Boni One.
Originally published on the Boni blog.
Top comments (0)