12 KiB
12 KiB
Ultronsms — generic DLT template SMS
Parameterized integration for sending any DLT-approved SMS via Ultronsms 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
JobIdandMessageId - 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
- Exact match — Sent text must match the approved template except for variable substitutions.
- Replace all placeholders — Common patterns:
{#var#},{#num#}. Fill every occurrence. - Sender ↔ PE ID — Each sender ID is bound to a PE ID in the DLT portal; they must match.
- 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)
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
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
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.
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
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
- Register header and content on the DLT portal; wait for Approved status.
- Note Sender ID, PE ID, and Template ID from the Ultronsms dashboard.
- Add a
DltTemplateentry to your registry. - Implement a message builder that produces text identical to the approved template (variables filled only).
- Send one test SMS to a real number; confirm delivery via
GetDelivery. - Only then enable in production.
Security notes
- Load
ULTRONSMS_PASSWORDfrom secrets manager or.env(gitignored). - Never expose gateway credentials to frontend or mobile apps.
- Log
JobIdandMessageIdfor 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 — URMI OTP flow with all values hardcoded for quick drop-in use.