Python & Django Guide

Send Transactional SMS in Python & Django

Integrate NepalOTP into your Python backend to programmatically dispatch system alerts, order updates, and critical notifications reliably using Celery and Requests.

Whether you are using a full-stack framework like Django, a micro-framework like FastAPI, or writing a simple background worker script, communicating with NepalOTP via Python is highly ergonomic. Our infrastructure utilizes standard REST architecture and accepts clean JSON payloads.

In this advanced guide, we will cover how to send transactional SMS alerts. Crucially, we will implement this using asynchronous task queues (Celery) so that network requests to the telecom API never block your main Django web thread.


1. Initial Setup

Python's industry-standard requests library is required to interact with the API. Install it via pip:

Terminal
$ pip install requests

Add your API credentials securely to your Django settings.py file, pulling from your environment variables.

core/settings.py
import os

# NepalOTP Configuration
NEPALOTP_API_KEY = os.getenv('NEPALOTP_API_KEY', '')
NEPALOTP_BASE_URL = 'https://api.nepalotp.com/v1'

2. The Core Utility Function

To send a transactional SMS through NepalOTP, you must use an approved template ID. We do not accept free-text promotional messages. This ensures high-priority routing for your alerts.

Let's create a core utility module that handles the HTTP request, sets timeouts, and logs errors properly.

utils/sms.py
import requests
import logging
from django.conf import settings

logger = logging.getLogger(__name__)

def dispatch_template_sms(phone_number: str, template_id: str, variables: dict) -> dict:
    """
    Dispatches a transactional SMS using the NepalOTP REST API.
    Raises requests.exceptions.RequestException on network or HTTP failure.
    """
    headers = {
        'Authorization': f'Bearer {settings.NEPALOTP_API_KEY}',
        'Content-Type': 'application/json'
    }
    
    payload = {
        'template_id': template_id,
        'phone': phone_number,
        'variables': variables
    }
    
    try:
        response = requests.post(
            f'{settings.NEPALOTP_BASE_URL}/sms/send', 
            json=payload, 
            headers=headers,
            timeout=5.0 # Always set a strict timeout for external API requests
        )
        response.raise_for_status() # Triggers exception for 4xx/5xx responses
        return response.json()
        
    except requests.exceptions.RequestException as e:
        logger.error(f"[NepalOTP] Dispatch failed for {phone_number}: {str(e)}")
        
        # Safely extract detailed API error message if returned by server
        if hasattr(e.response, 'json'):
            try:
                error_details = e.response.json().get('message', 'Unknown')
                logger.error(f"[NepalOTP] API Details: {error_details}")
            except ValueError:
                pass
                
        raise

3. Asynchronous Dispatch with Celery

A common anti-pattern is calling the SMS utility function directly inside a Django View. If the telecom network is slow and the request takes 3 seconds, your user is left staring at a loading spinner.

Instead, you should wrap the SMS dispatch in an asynchronous Celery task.

tasks.py
from celery import shared_task
from .utils.sms import dispatch_template_sms
import requests

# Retry up to 3 times automatically if a network error occurs
@shared_task(bind=True, max_retries=3, default_retry_delay=10)
def send_order_confirmation_task(self, phone_number, order_id, amount):
    
    variables = {
        'order_id': order_id,
        'amount': amount
    }
    
    try:
        # Use your pre-approved template ID from the dashboard
        result = dispatch_template_sms(phone_number, 'tpl_order_shipped', variables)
        return result['id']
        
    except requests.exceptions.RequestException as exc:
        # Log the failure to Sentry/Datadog and trigger a Celery retry
        raise self.retry(exc=exc)

4. Triggering the Task in Django Views

Now, inside your actual web view, you simply enqueue the task using .delay(). This returns a response to the user instantly, while Celery handles the API communication in the background.

views.py
from django.http import JsonResponse
from .tasks import send_order_confirmation_task

def checkout_complete_view(request):
    # ... business logic to process the order ...
    # order = Order.objects.create(...)
    
    # Dispatch the SMS notification asynchronously
    send_order_confirmation_task.delay(
        phone_number='+9779812345678', 
        order_id='ORD-8841', 
        amount='NPR 1500'
    )
        
    return JsonResponse({'status': 'Order confirmed securely'})

Start integrating today

Get your sandbox API keys instantly and test our endpoints safely via Python without spending credits.

Create Developer Account