# 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.