HH/docs/EXTERNAL_API_NOTIFICATION.md

14 KiB
Raw Permalink Blame History

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() and send_sms_via_api()
  • Generic Request Format: Simple JSON structure compatible with most APIs
  • Flexible Authentication: Supports both Bearer token and X-API-KEY header
  • 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:

  1. Initial attempt (retry 0)
  2. Wait 2 seconds (retry_delay)
  3. First retry (retry 1)
  4. Wait 4 seconds (retry_delay × 2^1)
  5. Second retry (retry 2)
  6. Wait 8 seconds (retry_delay × 2^2)
  7. 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

  1. API Keys: Never commit .env file to version control
  2. HTTPS: Always use HTTPS URLs for API endpoints
  3. Authentication: Use Bearer tokens or API keys, never basic auth
  4. Logging: API keys are not logged in full (metadata stores auth_method, not key)
  5. 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:

  1. Async Support: Integrate with Celery for background processing
  2. Webhooks: Support for delivery status callbacks
  3. Templates: Built-in email/SMS template support
  4. Rate Limiting: Implement API rate limiting
  5. Bulk Operations: Support for batch email/SMS sending
  6. Provider SDKs: Integrate provider-specific SDKs (SendGrid, Twilio, etc.)

Support

For issues or questions:

  1. Check logs: logs/px360.log and logs/integrations.log
  2. Verify .env configuration
  3. Check NotificationLog entries in Django admin
  4. Test API endpoint independently (curl, Postman, etc.)