first commit

This commit is contained in:
debu-2 2026-10-02 11:22:40 +05:30
commit efa5080897
127 changed files with 18610 additions and 0 deletions

387
GENERIC_TEMPLATE_SMS.md Normal file
View file

@ -0,0 +1,387 @@
# Ultronsms — generic DLT template SMS
Parameterized integration for sending **any DLT-approved SMS** via [Ultronsms](https://ultronsms.com) by supplying `peid`, `dlttemplateid`, sender ID, and filled template text at runtime.
Use this when you have multiple approved templates (OTP, password reset, alerts, etc.) and want one reusable client instead of hardcoding each flow.
---
## Overview
```
┌──────────────┐ peid + dlttemplateid ┌─────────────────┐ SendSMS ┌─────────────┐
│ Caller │ senderid + message │ Generic client │ ───────────► │ Ultronsms │
│ (your app) │ ───────────────────────► │ (this doc) │ │ gateway │
└──────────────┘ └─────────────────┘ └──────┬──────┘
│ │
│ GetDelivery (optional) │
◄──────────────────────────────┘
```
**Caller provides**
| Input | Description |
|-------|-------------|
| `mobile` | Recipient number (`+91…` or `91…`) |
| `sender_id` | DLT-approved header, e.g. `URMIAI`, `WEBDMG` |
| `pe_id` | Principal Entity ID linked to that sender |
| `dlt_template_id` | Approved content template ID |
| `message` | Final SMS text with all `{#var#}` / `{#num#}` placeholders replaced |
| `channel` | `Trans` (transactional) or `Promo` (promotional) — default `Trans` |
**Client returns**
- Send response with `JobId` and `MessageId`
- Optional delivery report via `GetDelivery`
---
## Environment configuration
Store credentials in environment variables (never commit secrets).
| Variable | Example | Description |
|----------|---------|-------------|
| `ULTRONSMS_BASE_URL` | `https://ultronsms.com` | Gateway base URL |
| `ULTRONSMS_USER` | `GENZENITH` | Account username |
| `ULTRONSMS_PASSWORD` | *(secret)* | Account password |
### Route mapping
| Channel | `channel` param | `route` param |
|---------|-----------------|---------------|
| Transactional | `Trans` | `2` |
| Promotional | `Promo` | `1` |
The UI may show internal route labels (e.g. `T_22`); the API expects numeric routes above.
---
## API endpoints
| Action | Method | URL |
|--------|--------|-----|
| Send SMS | `GET` | `{BASE_URL}/api/mt/SendSMS` |
| Delivery status | `GET` | `{BASE_URL}/api/mt/GetDelivery` |
### SendSMS — all parameters
| Parameter | Required | Notes |
|-----------|----------|-------|
| `user` | yes | From `ULTRONSMS_USER` |
| `password` | yes | From `ULTRONSMS_PASSWORD` |
| `senderid` | yes | DLT-approved header |
| `channel` | yes | `Trans` or `Promo` |
| `route` | yes | `2` (Trans) or `1` (Promo) |
| `number` | yes | Country code + number, no `+` |
| `text` | yes | Filled DLT template body |
| `peid` | yes | PE ID for this sender |
| `dlttemplateid` | yes | Lowercase parameter name |
| `DCS` | yes | `0` = English, `8` = Unicode |
| `flashsms` | yes | `0` = normal, `1` = flash |
### GetDelivery parameters
| Parameter | Required |
|-----------|----------|
| `user` | yes |
| `password` | yes |
| `jobid` | yes — from SendSMS `JobId` |
---
## DLT template rules
1. **Exact match** — Sent text must match the approved template except for variable substitutions.
2. **Replace all placeholders** — Common patterns: `{#var#}`, `{#num#}`. Fill every occurrence.
3. **Sender ↔ PE ID** — Each sender ID is bound to a PE ID in the DLT portal; they must match.
4. **Template ID** — Use the ID from the approved template row, not the PE ID.
### Example templates (verified in testing)
**URMI OTP** (transactional)
| Field | Value |
|-------|-------|
| Sender | `URMIAI` |
| PE ID | `1701178780498045011` |
| Template ID | `1777178797989033323` |
| Template | `Your URMI app verification code is {#num#}. It is valid for {#num#} minutes. Do not share it to anyone - URMI AI` |
| Example message | `Your URMI app verification code is 482916. It is valid for 10 minutes. Do not share it to anyone - URMI AI` |
**Password reset** (transactional)
| Field | Value |
|-------|-------|
| Sender | `WEBDMG` |
| PE ID | `1301159136621106678` |
| Template ID | `1307161537613263303` |
| Template | `Hello Customer, Your new password is '{#var#}'. Thank you. Regards, WebXion` |
| Example message | `Hello Customer, Your new password is 'Kx9#mP2q'. Thank you. Regards, WebXion` |
---
## Python implementation (generic)
```python
import os
import time
from dataclasses import dataclass
from enum import Enum
from typing import Any
from urllib.parse import urlencode
from urllib.request import urlopen
import json
class SmsChannel(str, Enum):
TRANSACTIONAL = "Trans"
PROMOTIONAL = "Promo"
@dataclass(frozen=True)
class DltTemplate:
"""Register each approved template in your app or database."""
sender_id: str
pe_id: str
dlt_template_id: str
channel: SmsChannel = SmsChannel.TRANSACTIONAL
@property
def route(self) -> str:
return "2" if self.channel == SmsChannel.TRANSACTIONAL else "1"
@dataclass
class SendResult:
success: bool
job_id: str | None
message_id: str | None
number: str | None
raw: dict[str, Any]
class UltronsmsClient:
def __init__(
self,
base_url: str | None = None,
user: str | None = None,
password: str | None = None,
) -> None:
self.base_url = (base_url or os.environ["ULTRONSMS_BASE_URL"]).rstrip("/")
self.user = user or os.environ["ULTRONSMS_USER"]
self.password = password or os.environ["ULTRONSMS_PASSWORD"]
@staticmethod
def normalize_phone(number: str) -> str:
digits = "".join(c for c in number if c.isdigit())
if len(digits) == 10:
return f"91{digits}"
return digits
def _get(self, path: str, params: dict[str, str]) -> dict[str, Any]:
url = f"{self.base_url}{path}?{urlencode(params)}"
with urlopen(url, timeout=30) as resp:
return json.loads(resp.read().decode())
def send_template_sms(
self,
mobile: str,
message: str,
template: DltTemplate,
*,
dcs: str = "0",
flash_sms: str = "0",
) -> SendResult:
"""
Send SMS using any DLT-approved template.
Args:
mobile: Recipient, e.g. +916296346452
message: Fully substituted DLT template text
template: sender_id, pe_id, dlt_template_id, channel
"""
number = self.normalize_phone(mobile)
params = {
"user": self.user,
"password": self.password,
"senderid": template.sender_id,
"channel": template.channel.value,
"route": template.route,
"DCS": dcs,
"flashsms": flash_sms,
"number": number,
"text": message,
"peid": template.pe_id,
"dlttemplateid": template.dlt_template_id,
}
raw = self._get("/api/mt/SendSMS", params)
ok = raw.get("ErrorCode") == "000"
message_data = (raw.get("MessageData") or [{}])[0]
return SendResult(
success=ok,
job_id=raw.get("JobId"),
message_id=message_data.get("MessageId"),
number=message_data.get("Number"),
raw=raw,
)
def get_delivery(self, job_id: str) -> dict[str, Any]:
return self._get(
"/api/mt/GetDelivery",
{"user": self.user, "password": self.password, "jobid": job_id},
)
def wait_for_delivery(
self,
job_id: str,
*,
timeout_seconds: int = 15,
poll_interval_seconds: float = 3.0,
) -> dict[str, Any]:
deadline = time.time() + timeout_seconds
last: dict[str, Any] = {}
while time.time() < deadline:
last = self.get_delivery(job_id)
reports = last.get("DeliveryReports") or []
if reports and reports[0].get("DeliveryStatus") == "Delivered":
return last
time.sleep(poll_interval_seconds)
return last
```
### Template registry example
```python
TEMPLATES = {
"urmi_otp": DltTemplate(
sender_id="URMIAI",
pe_id="1701178780498045011",
dlt_template_id="1777178797989033323",
channel=SmsChannel.TRANSACTIONAL,
),
"webxion_password": DltTemplate(
sender_id="WEBDMG",
pe_id="1301159136621106678",
dlt_template_id="1307161537613263303",
channel=SmsChannel.TRANSACTIONAL,
),
}
def build_message(template_key: str, **variables: str) -> str:
"""Fill placeholders manually per template — keep in sync with DLT approval."""
builders = {
"urmi_otp": lambda otp, minutes: (
f"Your URMI app verification code is {otp}. "
f"It is valid for {minutes} minutes. "
f"Do not share it to anyone - URMI AI"
),
"webxion_password": lambda password: (
f"Hello Customer, Your new password is '{password}'. "
f"Thank you. Regards, WebXion"
),
}
return builders[template_key](**variables)
```
### Usage
```python
client = UltronsmsClient()
# URMI OTP
result = client.send_template_sms(
mobile="+916296346452",
message=build_message("urmi_otp", otp="482916", minutes="10"),
template=TEMPLATES["urmi_otp"],
)
# Password reset
result = client.send_template_sms(
mobile="+919149154337",
message=build_message("webxion_password", password="Kx9#mP2q"),
template=TEMPLATES["webxion_password"],
)
if result.success:
delivery = client.wait_for_delivery(result.job_id)
print(delivery)
else:
print(result.raw) # handle ErrorCode / ErrorMessage
```
---
## cURL template
Replace `{...}` placeholders at call time.
```bash
curl -sS -L -G "https://ultronsms.com/api/mt/SendSMS" \
--data-urlencode "user=${ULTRONSMS_USER}" \
--data-urlencode "password=${ULTRONSMS_PASSWORD}" \
--data-urlencode "senderid={SENDER_ID}" \
--data-urlencode "channel=Trans" \
--data-urlencode "DCS=0" \
--data-urlencode "flashsms=0" \
--data-urlencode "number={MOBILE_91XXXXXXXXXX}" \
--data-urlencode "text={FILLED_DLT_MESSAGE}" \
--data-urlencode "route=2" \
--data-urlencode "peid={PE_ID}" \
--data-urlencode "dlttemplateid={DLT_TEMPLATE_ID}"
```
**Delivery check**
```bash
curl -sS -L -G "https://ultronsms.com/api/mt/GetDelivery" \
--data-urlencode "user=${ULTRONSMS_USER}" \
--data-urlencode "password=${ULTRONSMS_PASSWORD}" \
--data-urlencode "jobid={JOB_ID}"
```
---
## Response handling
### Success criteria
| API | Success signal |
|-----|----------------|
| SendSMS | `ErrorCode == "000"`, non-empty `JobId` |
| GetDelivery | `DeliveryReports[0].DeliveryStatus == "Delivered"`, `ErrorCode == "000"` |
### Common errors
| ErrorCode | Meaning | Action |
|-----------|---------|--------|
| `7` | Invalid username/password | Check env credentials |
| `25` | Server NullReferenceException | Usually invalid `route` or malformed request |
| Non-`000` on delivery | Operator / DLT rejection | Verify template text, PE ID, sender binding |
---
## Adding a new template
1. Register header and content on the DLT portal; wait for **Approved** status.
2. Note **Sender ID**, **PE ID**, and **Template ID** from the Ultronsms dashboard.
3. Add a `DltTemplate` entry to your registry.
4. Implement a message builder that produces text identical to the approved template (variables filled only).
5. Send one test SMS to a real number; confirm delivery via `GetDelivery`.
6. Only then enable in production.
---
## Security notes
- Load `ULTRONSMS_PASSWORD` from secrets manager or `.env` (gitignored).
- Never expose gateway credentials to frontend or mobile apps.
- Log `JobId` and `MessageId` for support; avoid logging full message content if it contains OTPs or passwords.
- Use the demo account (`GENZENITHDemo`) for staging only.
---
## Related document
- [OTP_SMS_IMPLEMENTATION.md](./OTP_SMS_IMPLEMENTATION.md) — URMI OTP flow with all values hardcoded for quick drop-in use.