14 KiB
External API Notification Service
Overview
The PX360 notification system has been extended to support sending emails and SMS via external API endpoints. This allows integration with third-party notification services while maintaining backward compatibility with existing Django SMTP functionality.
Features
- ✅ Separate API Methods:
send_email_via_api()andsend_sms_via_api() - ✅ Generic Request Format: Simple JSON structure compatible with most APIs
- ✅ Flexible Authentication: Supports both
Bearertoken andX-API-KEYheader - ✅ Fire-and-Forget: Accepts any 2xx response (API processes in background)
- ✅ Retry Logic: Exponential backoff with configurable max retries
- ✅ Comprehensive Logging: All attempts logged to Django logger and NotificationLog
- ✅ Error Handling: Handles timeouts, connection errors, and HTTP errors
- ✅ Configurable: All settings via environment variables
- ✅ Non-Breaking: Existing code continues to work unchanged
Configuration
Environment Variables
Add these to your .env file:
# External Email API
EMAIL_API_ENABLED=false # Enable/disable email API
EMAIL_API_URL=https://api.example.com/send-email
EMAIL_API_KEY=your-api-key-here
EMAIL_API_AUTH_METHOD=bearer # Options: bearer, api_key
EMAIL_API_METHOD=POST
EMAIL_API_TIMEOUT=10 # Request timeout in seconds
EMAIL_API_MAX_RETRIES=3 # Maximum retry attempts
EMAIL_API_RETRY_DELAY=2 # Initial delay in seconds
# External SMS API
SMS_API_ENABLED=false # Enable/disable SMS API
SMS_API_URL=https://api.example.com/send-sms
SMS_API_KEY=your-api-key-here
SMS_API_AUTH_METHOD=bearer # Options: bearer, api_key
SMS_API_METHOD=POST
SMS_API_TIMEOUT=10 # Request timeout in seconds
SMS_API_MAX_RETRIES=3 # Maximum retry attempts
SMS_API_RETRY_DELAY=2 # Initial delay in seconds
Settings Structure
The configuration is automatically loaded into settings.EXTERNAL_NOTIFICATION_API:
EXTERNAL_NOTIFICATION_API = {
'email': {
'enabled': env.bool('EMAIL_API_ENABLED', default=False),
'url': env('EMAIL_API_URL', default=''),
'api_key': env('EMAIL_API_KEY', default=''),
'auth_method': env('EMAIL_API_AUTH_METHOD', default='bearer'),
'method': env('EMAIL_API_METHOD', default='POST'),
'timeout': env.int('EMAIL_API_TIMEOUT', default=10),
'max_retries': env.int('EMAIL_API_MAX_RETRIES', default=3),
'retry_delay': env.int('EMAIL_API_RETRY_DELAY', default=2),
},
'sms': {
'enabled': env.bool('SMS_API_ENABLED', default=False),
'url': env('SMS_API_URL', default=''),
'api_key': env('SMS_API_KEY', default=''),
'auth_method': env('SMS_API_AUTH_METHOD', default='bearer'),
'method': env('SMS_API_METHOD', default='POST'),
'timeout': env.int('SMS_API_TIMEOUT', default=10),
'max_retries': env.int('SMS_API_MAX_RETRIES', default=3),
'retry_delay': env.int('SMS_API_RETRY_DELAY', default=2),
},
}
API Request Format
Email API Request
Endpoint: POST to EMAIL_API_URL
Headers:
Content-Type: application/json
Authorization: Bearer {EMAIL_API_KEY} # or X-API-KEY: {EMAIL_API_KEY}
Request Body:
{
"to": "recipient@example.com",
"subject": "Email Subject",
"message": "Plain text message",
"html_message": "<html>Optional HTML content</html>"
}
Expected Response: Any 2xx status code (200-299)
SMS API Request
Endpoint: POST to SMS_API_URL
Headers:
Content-Type: application/json
Authorization: Bearer {SMS_API_KEY} # or X-API-KEY: {SMS_API_KEY}
Request Body:
{
"to": "+966501234567",
"message": "SMS message text"
}
Expected Response: Any 2xx status code (200-299)
Usage
Basic Email via API
from apps.notifications.services import NotificationService
# Simple email
log = NotificationService.send_email_via_api(
message='Your account has been created',
email='user@example.com',
subject='Welcome to PX360'
)
# Email with HTML
log = NotificationService.send_email_via_api(
message='Plain text version',
email='user@example.com',
subject='Welcome',
html_message='<h1>Welcome</h1><p>Your account is ready!</p>'
)
# Email with tracking
complaint = Complaint.objects.get(id=123)
log = NotificationService.send_email_via_api(
message='You have a new complaint...',
email='staff@example.com',
subject='New Complaint',
related_object=complaint,
metadata={'complaint_id': str(complaint.id)}
)
Basic SMS via API
from apps.notifications.services import NotificationService
# Simple SMS
log = NotificationService.send_sms_via_api(
message='Your verification code is 123456',
phone='+966501234567'
)
# SMS with tracking
survey_instance = SurveyInstance.objects.get(id=123)
log = NotificationService.send_sms_via_api(
message='Please complete your survey: https://...',
phone='+966501234567',
related_object=survey_instance,
metadata={'survey_id': str(survey_instance.id)}
)
Comparison with Existing Methods
from apps.notifications.services import NotificationService
# Existing method - uses Django SMTP
NotificationService.send_email(
email='user@example.com',
subject='Welcome',
message='Hello!'
)
# New method - uses external API
NotificationService.send_email_via_api(
message='Hello!',
email='user@example.com',
subject='Welcome'
)
Retry Logic
Both API methods implement exponential backoff retry:
- Initial attempt (retry 0)
- Wait 2 seconds (retry_delay)
- First retry (retry 1)
- Wait 4 seconds (retry_delay × 2^1)
- Second retry (retry 2)
- Wait 8 seconds (retry_delay × 2^2)
- Final retry (retry 3)
Configurable via:
EMAIL_API_MAX_RETRIES/SMS_API_MAX_RETRIES(default: 3)EMAIL_API_RETRY_DELAY/SMS_API_RETRY_DELAY(default: 2 seconds)
Error Handling
All errors are handled gracefully:
Connection Errors
- Logged as "Connection error"
- Retry attempted if retries available
Timeout Errors
- Logged as "Request timeout"
- Retry attempted if retries available
HTTP Errors
- Non-2xx status codes logged with status number
- Retry attempted if retries available
Unexpected Errors
- Full error message logged
- Retry attempted if retries available
Final State
- After all retries exhausted, NotificationLog marked as
failed - Error details stored in log's error_message field
Database Tracking
All API notifications are tracked in the NotificationLog model:
log = NotificationService.send_email_via_api(
message='Hello',
email='user@example.com',
subject='Test'
)
# Check status
print(log.status) # 'sent' or 'failed'
# Check metadata
print(log.metadata)
# {
# 'api_url': 'https://api.example.com/send-email',
# 'auth_method': 'bearer',
# ...
# }
Logging
All API calls are logged to Django logger at different levels:
INFO Level
- Successful API calls
- API request initiation
WARNING Level
- Non-2xx HTTP responses
- Connection errors (during retries)
- Timeout errors (during retries)
ERROR Level
- Unexpected exceptions
- Final failure after all retries
Logs are written to:
- Console (in development)
logs/px360.log(file)logs/integrations.log(integration-specific)
Examples
Example 1: Send Welcome Email via API
def send_welcome_email(user):
from apps.notifications.services import NotificationService
log = NotificationService.send_email_via_api(
message=f'Welcome {user.get_full_name()}!',
email=user.email,
subject='Welcome to PX360'
)
if log and log.status == 'sent':
print("Email sent successfully via API")
else:
print("Failed to send email")
Example 2: Send SMS Verification Code
def send_verification_code(phone, code):
from apps.notifications.services import NotificationService
log = NotificationService.send_sms_via_api(
message=f'Your verification code is: {code}',
phone=phone
)
return log.status == 'sent'
Example 3: Send Complaint Notification via API
def notify_complaint_department(complaint):
from apps.notifications.services import NotificationService
department = complaint.department
log = NotificationService.send_email_via_api(
message=f'New complaint received from {complaint.patient.get_full_name()}',
email=department.contact_email,
subject=f'New Complaint: {complaint.ticket_number}',
related_object=complaint,
metadata={
'complaint_id': str(complaint.id),
'ticket_number': complaint.ticket_number
}
)
return log
Example 4: Send Survey Invitation via API
def send_survey_invitation_via_api(survey_instance):
from apps.notifications.services import NotificationService
patient = survey_instance.patient
survey_url = survey_instance.get_survey_url()
if survey_instance.delivery_channel == 'email':
log = NotificationService.send_email_via_api(
message=f'Please complete your survey: {survey_url}',
email=patient.email,
subject='Experience Survey',
related_object=survey_instance
)
elif survey_instance.delivery_channel == 'sms':
log = NotificationService.send_sms_via_api(
message=f'Complete your survey: {survey_url}',
phone=patient.phone,
related_object=survey_instance
)
return log
Troubleshooting
API Disabled
Problem: Emails/SMS not being sent via API
Solution: Check that EMAIL_API_ENABLED or SMS_API_ENABLED is set to true in .env
# Check environment variable
echo $EMAIL_API_ENABLED
# Enable if disabled
EMAIL_API_ENABLED=true
Authentication Failures
Problem: API returning 401 or 403 errors
Solution: Verify API key and auth method
# Check auth method
EMAIL_API_AUTH_METHOD=bearer # or api_key
# Verify API key is correct
EMAIL_API_KEY=your-actual-api-key
Connection Timeout
Problem: Requests timing out
Solution: Increase timeout value
EMAIL_API_TIMEOUT=30 # Increase from 10 to 30 seconds
Frequent Failures
Problem: API calls failing consistently
Solution: Check logs for specific error messages
# View logs
tail -f logs/px360.log | grep "API"
# Check NotificationLog in Django admin
# Navigate to /admin/notifications/notificationlog/
No API Calls Being Made
Problem: Methods returning None
Solution: Verify configuration is loaded
from django.conf import settings
# Check configuration
print(settings.EXTERNAL_NOTIFICATION_API)
# Should show 'email' and 'sms' keys with configuration
Migration Guide
Migrating from Django SMTP to API
Before (using Django SMTP):
NotificationService.send_email(
email='user@example.com',
subject='Welcome',
message='Hello!'
)
After (using external API):
NotificationService.send_email_via_api(
message='Hello!',
email='user@example.com',
subject='Welcome'
)
Note: The parameter order is slightly different - message comes before email for API methods.
Gradual Migration Strategy
You can use both methods in parallel:
def send_email(email, subject, message):
from apps.notifications.services import NotificationService
from django.conf import settings
# Use API if enabled
if settings.EXTERNAL_NOTIFICATION_API['email']['enabled']:
return NotificationService.send_email_via_api(
message=message,
email=email,
subject=subject
)
# Fall back to Django SMTP
else:
return NotificationService.send_email(
email=email,
subject=subject,
message=message
)
Security Considerations
- API Keys: Never commit
.envfile to version control - HTTPS: Always use HTTPS URLs for API endpoints
- Authentication: Use Bearer tokens or API keys, never basic auth
- Logging: API keys are not logged in full (metadata stores auth_method, not key)
- Timeouts: Set reasonable timeouts to prevent hanging requests
Performance Impact
- Network Latency: Each API call adds network round-trip time
- Retries: Failed requests are retried with exponential backoff
- Logging: Minimal overhead from logging to database
- Async Consideration: For high-volume scenarios, consider using Celery tasks
Future Enhancements
Potential improvements for the API notification service:
- Async Support: Integrate with Celery for background processing
- Webhooks: Support for delivery status callbacks
- Templates: Built-in email/SMS template support
- Rate Limiting: Implement API rate limiting
- Bulk Operations: Support for batch email/SMS sending
- Provider SDKs: Integrate provider-specific SDKs (SendGrid, Twilio, etc.)
Support
For issues or questions:
- Check logs:
logs/px360.logandlogs/integrations.log - Verify
.envconfiguration - Check
NotificationLogentries in Django admin - Test API endpoint independently (curl, Postman, etc.)