> For the complete documentation index, see [llms.txt](https://docs.digit.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.digit.org/complaints-management/cms-v2.11/deploy/configure/notifications.md).

# Notifications

## Overview

This enables SMS, WhatsApp and email notifications on a deployment created with the [deployment guide](/complaints-management/cms-v2.11/deploy/setup/production-setup/deploy.md). Run the repository commands below from the root of the cloned `Citizen-Complaint-Resolution-System` repository.

Each channel is independent — enable one, two or all three. Each has its own section below listing what it needs.

### Prerequisites

1. A deployment created by `./deploy.sh mycity`, or the same variables file before its first run. Adding these variables before the first deploy avoids a second run.
2. For WhatsApp, a Twilio account with a WhatsApp-enabled sender and approved message templates. Without approved templates, nothing is delivered.
3. For SMS, an SMSCountry account on the legacy bulk API, its panel login, and a registered sender ID. For Indian destinations, you also need DLT-registered templates — see [Registering templates.](#registering-templates-india-dlt)
4. For email, an SMTP account. On Gmail and Microsoft 365, that means an **app password**, not the account password — see [Enable Email](#id-5.-enable-email).

No channel depends on another: an SMS-only deployment needs no Twilio account, a WhatsApp-only one needs no SMSCountry account.

Nothing is dispatched until you name a channel in `novu_bridge_channels_enabled`. There is no default.

Each channel section has its own settings table. Collect the ones you need before the first deploy, and you can do this in a single run.

Last tested with `egovio/pgr-services:master-0938bdf` and `egovio/novu-bridge:master-0469335`.

## Steps

### 1. Configure Notification Variables

These apply to every channel. Add them to the same Ansible variables file used for the deployment: `local-setup/ansible/inventory/host_vars/mycity.yml`. Per-channel settings live in each channel's section.

<table><thead><tr><th width="189.32421875">Setting</th><th width="370.21484375">What it is</th><th>Example</th></tr></thead><tbody><tr><td><code>enable_novu</code></td><td>Starts Novu and the notification stack. Nothing below works without it.</td><td><code>true</code></td></tr><tr><td><code>pgr_notification_config_driven</code></td><td>Makes PGR read routing and templates from configuration instead of code.</td><td><code>true</code></td></tr><tr><td><code>seed_notifications</code></td><td>Seeds the three PGR notification MDMS masters on deploy. Idempotent.</td><td><code>true</code></td></tr><tr><td><code>novu_bridge_channels_enabled</code></td><td><strong>Required.</strong> Channels to dispatch — <code>SMS</code>, <code>WHATSAPP</code>, <code>EMAIL</code>, comma-separated. There is no default: leave it unset and nothing is sent. Name only channels you have onboarded a provider for.</td><td><code>"SMS,WHATSAPP"</code></td></tr><tr><td><code>novu_bridge_proxy_allowed_roles</code></td><td>Roles allowed to manage providers from Configurator.</td><td><code>"SUPERUSER,MDMS_ADMIN"</code></td></tr><tr><td><code>novu_admin_email</code></td><td>Novu admin account. Use an address you control.</td><td><code>notifications-admin@example.com</code></td></tr><tr><td><code>novu_admin_password</code></td><td>Novu admin password. Generate a unique, strong one.</td><td></td></tr><tr><td><code>novu_api_key</code></td><td>Leave unset. Ansible mints a key and wires it into <code>/opt/digit/.env</code>. Set it only if the deployment has a pinned key.</td><td></td></tr></tbody></table>

### 2. Start Deployment

```
cd local-setup/ansible
./deploy.sh mycity
```

Wait for `failed=0`. This also syncs the current `local-setup/kong/kong.yml` and reloads Kong, which the Configurator provider screens need. If you edit `kong.yml` without re-running the deployment, apply it with `sudo docker exec kong-gateway kong reload` — Kong is DB-less and does not re-read the file on its own.

#### Verify the Novu key

Do this before any provider work. A green play recap isn't enough: the provider bootstrap can fail without failing the deployment.

```
export NOVU_BASE_URL='http://127.0.0.1:14002'
export NOVU_API_KEY="$(sudo sed -n 's/^NOVU_API_KEY=//p' /opt/digit/.env | tail -1)"

test -n "$NOVU_API_KEY"
test "$NOVU_API_KEY" != changeme
curl -fsS -H "Authorization: ApiKey $NOVU_API_KEY" \
  "$NOVU_BASE_URL/v1/integrations" >/dev/null
```

Stop if any command fails. Configurator provider operations go through `novu-bridge`, and they cannot work while the bridge holds an empty, placeholder, or invalid Novu key. Keep this shell open — the sections below reuse `NOVU_BASE_URL` and `NOVU_API_KEY`.

Do not set `NOVU_BRIDGE_PROXY_AUTH_ENABLED=false` on a reachable deployment. The Kong routes delegate authentication to `novu-bridge`, so disabling it makes provider creation and test-send unauthenticated. We can track this in [issue #1942](https://github.com/egovernments/Citizen-Complaint-Resolution-System/issues/1942).

### 3. Enable WhatsApp

WhatsApp goes through Twilio. Set these and re-run `./deploy.sh mycity`:

| Setting                               | What it is                                                                                         | Example                 |
| ------------------------------------- | -------------------------------------------------------------------------------------------------- | ----------------------- |
| `twilio_account_sid`                  | From the Twilio Console.                                                                           |                         |
| `twilio_auth_token`                   | From the Twilio Console.                                                                           |                         |
| `twilio_whatsapp_from`                | Your WhatsApp sender, with the `whatsapp:` prefix. Defaults to Twilio's sandbox number if omitted. | `whatsapp:+14155238886` |
| `novu_bridge_workflow_id_whatsapp`    | Leave at the default unless you renamed the workflow.                                              | `"complaints-whatsapp"` |
| `novu_bridge_integration_id_whatsapp` | Leave blank. Only needed if a second Twilio integration exists alongside the WhatsApp one.         |                         |

The deploy registers the Twilio account with Novu and creates the message route WhatsApp uses. The rest of this section is verification and the extra steps real WhatsApp delivery needs.

#### Bootstrap the provider and workflow

If `twilio_account_sid` was set before the deployment, this is already done. The deploy runs the same script, and its defaults produce the same integration and the same workflows — only the workflow's display name differs, which nothing keys on. Skip to [Verify WhatsApp](#verify-whatsapp).

Run it by hand only when the Twilio credentials were added after the first deploy and you would rather not re-run `./deploy.sh`. It administers Novu: it does not run the deployment again and does not send a message.

```
# Copy these values from the Twilio Console.
export TWILIO_ACCOUNT_SID='<Twilio Account SID>'
export TWILIO_AUTH_TOKEN='<Twilio Auth Token>'
export TWILIO_WHATSAPP_FROM='whatsapp:+<Twilio WhatsApp sender>'

export NOVU_ENV_FILE=/dev/null
export NOVU_INTEGRATION_NAME=twilio-whatsapp
export NOVU_INTEGRATION_ID=twilio-whatsapp
export NOVU_WORKFLOW_ID=complaints-whatsapp
export NOVU_WORKFLOW_NAME=complaints-whatsapp
export NOVU_SMS_BODY='Complaint {{payload.complaintNo}} status is {{payload.status}}'

bash backend/novu-bridge/config/bootstrap-novu-whatsapp.sh

unset TWILIO_ACCOUNT_SID TWILIO_AUTH_TOKEN TWILIO_WHATSAPP_FROM NOVU_SMS_BODY
```

The script creates `twilio-whatsapp` when it is absent, but it does not update the credentials of an existing integration. On an existing installation, update or delete that integration in Novu before running the script.

#### Verify WhatsApp

```
curl -fsS -H "Authorisation: ApiKey $NOVU_API_KEY" \
  "$NOVU_BASE_URL/v1/integrations" \
| jq '[.data[] | select(.identifier == "twilio-whatsapp") |
       {identifier,providerId,channel,active,primary}]'

curl -fsS -H "Authorisation: ApiKey $NOVU_API_KEY" \
  "$NOVU_BASE_URL/v2/workflows?limit=100&page=0" \
| jq '[.data.workflows[] | select(.workflowId == "complaints-whatsapp") |
       .workflowId]'
```

Confirm that `twilio-whatsapp` is `active` and that `complaints-whatsapp` exists.

`twilio-whatsapp` being `primary` is expected and fine — it is the only Twilio integration this guide creates. SMS does not add another: SMSCountry is called directly and registers nothing in Novu.

#### Send a real WhatsApp message

Business-initiated WhatsApp messages require an approved Twilio Content SID. The bridge skips WhatsApp events without one.

First, confirm that notification configuration exists at the state root of the tenant where the complaint will be filed. A complaint in `pg.citya` resolves its configuration from `pg`, which on a stock dump-based quickstart may differ from the deployment's `state_root`.

If the roots differ, re-run the idempotent notification seed for the complaint root:

```
export COMPLAINT_TENANT='pg.citya'
export NOTIF_TENANT="${COMPLAINT_TENANT%%.*}"
export DIGIT_URL='http://127.0.0.1:18000'
export DIGIT_USERNAME='ADMIN'
export DIGIT_PASSWORD='<bootstrap_password from the Ansible variables>'
export DIGIT_LOGIN_TENANT="$NOTIF_TENANT"

cd /opt/digit/notification-seed
SCHEMA_FILE=/opt/digit/notification-seed/RAINMAKER-PGR.json \
DATA_DIR=/opt/digit/notification-seed \
python3 seed-notifications.py

unset DIGIT_PASSWORD
```

Wait for `DONE`, then log in to Configurator at the same `NOTIF_TENANT`. Configurator writes notification configuration at the tenant used for the session. If it writes to the deployment root instead, PGR finds no routing for the complaint tenant and emits no notification events. The automatic fix is tracked in [issue #1943](https://github.com/egovernments/Citizen-Complaint-Resolution-System/issues/1943).

Sync matches approved templates to PGR transitions by template name and saves that Twilio account's Content SID. Each account needs its own approved templates with the expected names and ordered variables; the example seed SIDs are not portable.

1. Open **Notifications -> Providers -> Sync WhatsApp templates**.
2. Review and persist the matched templates.
3. Open **Notifications -> Configure** and verify the routing and message templates for the required PGR transitions.
4. Trigger a real complaint transition.
5. Check **Notifications -> Logs**, `nb_dispatch_log`, Novu activity, the provider console, and finally the handset.

`SENT` in the bridge log means Novu accepted the trigger. It is not proof that the provider delivered the message.

### 4. Enable SMS

SMS goes through SMSCountry's **legacy bulk API** — the one eGov accounts are provisioned on. It authenticates with your SMSCountry panel login rather than an API key, and `novu-bridge` posts to it directly, so this needs no Novu integration and no Novu workflow.

If your SMSCountry panel shows an AuthKey/AuthToken pair, you are on their newer REST v0.1 API, which is not supported.

Set these and re-run `./deploy.sh mycity`:

| Setting                           | What it is                 | Example      |
| --------------------------------- | -------------------------- | ------------ |
| `novu_bridge_sms_provider`        | Selects the SMS gateway.   | `smscountry` |
| `novu_bridge_sms_sender_id`       | Your registered sender id. | `EGOVFS`     |
| `novu_bridge_smscountry_user`     | SMSCountry panel username. |              |
| `novu_bridge_smscountry_password` | SMSCountry panel password. |              |

#### Verify SMS

Trigger a complaint transition, then open the **delivery report in the SMSCountry panel** — not the bridge log.

SMSCountry returns a job ID even for messages it later drops, so the bridge can only tell you the message was accepted, never that it arrived. In the report, a delivered message is billed; a blocked one shows as rejected at zero cost.

#### Registering templates (India, DLT)

Skip this unless you send to Indian numbers. India's DLT regime requires every message to match a template registered against your sender ID. The gateway accepts unregistered messages and the operator drops them, so **a successful send response does not mean delivered** — a rejected message still gets a job ID. Judge only by the delivery report, where a blocked message shows as rejected and costs nothing.

Matching is on message content; no template ID is sent. Punctuation and blank-line differences are tolerated; a reworded sentence is not.

Register one template per row below, per language you send. Take the exact text from **Configurator -> Notifications -> Configure**, replacing each `{variable}` with DLT's `{#var#}` marker and leaving the rest character for character.

<table><thead><tr><th width="150.1328125">Audience</th><th width="181.40234375">Transition</th><th>Variables</th></tr></thead><tbody><tr><td>Citizen</td><td>complaint submitted</td><td>complaint type, id, date</td></tr><tr><td>Citizen</td><td>assigned</td><td>complaint type, id, date, employee name, designation, department</td></tr><tr><td>Citizen</td><td>re-assigned</td><td>complaint type, id, date, employee name, designation, department</td></tr><tr><td>Citizen</td><td>rejected</td><td>complaint type, id, date, rejection reason</td></tr><tr><td>Citizen</td><td>resolved</td><td>complaint type, id, date, employee name</td></tr><tr><td>Citizen</td><td>re-opened</td><td>complaint type, id, date</td></tr><tr><td>Employee</td><td>assigned to you</td><td>employee name, complaint type, id, designation, city</td></tr><tr><td>Employee</td><td>feedback received</td><td>employee name, complaint type, id, rating</td></tr></tbody></table>

Two things that bite:

* The rejection reason is free text an employee types, and DLT caps a variable at about 30 characters. Reword that template to drop the variable if your provider enforces the limit.
* Hindi and other non-Latin templates register separately from the English ones.

### 5. Enable Email

Email goes through Novu, unlike SMS. The deploy creates the `complaints-email` workflow; you add the SMTP provider yourself.

1. Open **Configurator -> Notifications -> Providers**, select **Add Provider**:

<table><thead><tr><th width="258.8984375">Field</th><th>Value</th></tr></thead><tbody><tr><td>Channel</td><td><code>EMAIL</code></td></tr><tr><td>Provider ID</td><td><code>nodemailer</code></td></tr><tr><td>Name</td><td>anything, e.g. <code>Org SMTP</code></td></tr><tr><td>Identifier</td><td>anything you will recognise, e.g. <code>org-smtp</code></td></tr><tr><td>SMTP Host</td><td><code>smtp.gmail.com</code>, <code>smtp.office365.com</code>, …</td></tr><tr><td>SMTP Port</td><td><code>587</code></td></tr><tr><td>SMTP User</td><td>the mailbox address</td></tr><tr><td>SMTP Password</td><td>an <strong>app password</strong> — see below</td></tr><tr><td>From</td><td>usually must equal the SMTP User</td></tr><tr><td>Use TLS (secure)</td><td><strong>unchecked</strong> for port 587 — see below</td></tr></tbody></table>

2. Add `EMAIL` to `novu_bridge_channels_enabled` and re-run `./deploy.sh mycity`.

Two things account for most failures:

* **Use an app password, not your account password.** Gmail and Microsoft 365 reject the account password for SMTP once 2FA is on, with `535-5.7.8 Username and Password not accepted` — which reads like a typo and sends people round in circles. Generate a 16-character app password instead.
* **"Use TLS (secure)" does not mean "use TLS".** It means TLS from the first byte, which is port **465**. Port **587** starts in plaintext and upgrades via STARTTLS, so it needs this **unchecked**. Checking it with 587 hangs or fails the handshake.

| Port | Use TLS (secure) |
| ---- | ---------------- |
| 587  | unchecked        |
| 465  | checked          |

#### Verify email

Trigger a complaint transition and check the mailbox. If nothing arrives, look at **Notifications -> Logs** first: it will tell you whether the message was handed to your SMTP server at all, which separates a configuration problem from a delivery one.

#### Test without mailing anyone

[Ethereal](https://ethereal.email/) issues throwaway SMTP credentials that accept and capture mail instead of delivering it. Enough to prove the wiring — the provider fields, the workflow, the bridge — without touching a real mailbox.

```
curl -sS -X POST https://api.nodemailer.com/user \
  -H 'Content-Type: application/json' \
  -d '{"requestor":"ccrs","version":"1.0.0"}'
```

That returns `host`, `port`, `secure`, `user` and `pass`. Put them in the provider form above, trigger a transition, then read the captured mail by signing in at [ethereal.email](https://ethereal.email/) with the same credentials, or over IMAP at `imap.ethereal.email:993`.

It proves configuration, not deliverability: SPF, DKIM and whether a real recipient's provider accepts the mail still need a live SMTP account.

### 6. Clean Up

Remove the setup values from the current shell:

```
unset NOVU_API_KEY NOVU_BASE_URL
unset NOVU_ENV_FILE NOVU_INTEGRATION_NAME NOVU_INTEGRATION_ID
unset NOVU_WORKFLOW_ID NOVU_WORKFLOW_NAME
```

## Configurator Scope

<table><thead><tr><th width="295.27734375">Configurator can</th><th>Configurator cannot</th></tr></thead><tbody><tr><td>Create provider integrations</td><td>Edit, rotate, or delete integrations</td></tr><tr><td>Manage notification configuration</td><td>Select the primary SMS integration</td></tr><tr><td>Sync WhatsApp templates</td><td>Validate provider credentials</td></tr><tr><td>Validate the configuration</td><td>Mint or wire the Novu API key</td></tr><tr><td>Display bridge dispatch logs</td><td>Start Novu, enable the Compose profile, set service environment flags, or create workflows</td></tr></tbody></table>

Everything in the right-hand column is a deployment or Novu administration operation.

## Code References

<table><thead><tr><th width="231.41796875">What</th><th>Where</th></tr></thead><tbody><tr><td>Ansible deployment</td><td><a href="https://github.com/egovernments/Citizen-Complaint-Resolution-System/blob/master/local-setup/ansible/playbook-deploy.yml"><code>local-setup/ansible/playbook-deploy.yml</code></a></td></tr><tr><td>Workflow/provider bootstrap</td><td><a href="https://github.com/egovernments/Citizen-Complaint-Resolution-System/blob/master/backend/novu-bridge/config/bootstrap-novu-whatsapp.sh"><code>backend/novu-bridge/config/bootstrap-novu-whatsapp.sh</code></a></td></tr><tr><td>Notification seed</td><td><a href="https://github.com/egovernments/Citizen-Complaint-Resolution-System/blob/master/local-setup/scripts/seed-notifications.py"><code>local-setup/scripts/seed-notifications.py</code></a></td></tr><tr><td>Configurator provider UI</td><td><a href="https://github.com/egovernments/Citizen-Complaint-Resolution-System/blob/master/configurator/src/resources/notification-providers/NotificationProviderList.tsx"><code>configurator/src/resources/notification-providers/NotificationProviderList.tsx</code></a></td></tr><tr><td>Provider administration API</td><td><a href="https://github.com/egovernments/Citizen-Complaint-Resolution-System/blob/master/backend/novu-bridge/src/main/java/org/egov/novubridge/web/controllers/ProviderController.java"><code>backend/novu-bridge/src/main/java/org/egov/novubridge/web/controllers/ProviderController.java</code></a></td></tr><tr><td>PGR routing/rendering</td><td><a href="https://github.com/egovernments/Citizen-Complaint-Resolution-System/blob/master/backend/pgr-services/src/main/java/org/egov/pgr/service/NotificationService.java"><code>backend/pgr-services/src/main/java/org/egov/pgr/service/NotificationService.java</code></a></td></tr><tr><td>Bridge dispatch</td><td><a href="https://github.com/egovernments/Citizen-Complaint-Resolution-System/blob/master/backend/novu-bridge/src/main/java/org/egov/novubridge/service/DispatchPipelineService.java"><code>backend/novu-bridge/src/main/java/org/egov/novubridge/service/DispatchPipelineService.java</code></a></td></tr></tbody></table>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.digit.org/complaints-management/cms-v2.11/deploy/configure/notifications.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
