# 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: ```bash # 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`: ```python 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**: ```json { "to": "recipient@example.com", "subject": "Email Subject", "message": "Plain text message", "html_message": "Optional HTML content" } ``` **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**: ```json { "to": "+966501234567", "message": "SMS message text" } ``` **Expected Response**: Any 2xx status code (200-299) ## Usage ### Basic Email via API ```python 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='

Welcome

Your account is ready!

' ) # 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 ```python 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 ```python 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: ```python 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 ```python 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 ```python 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 ```python 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 ```python 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` ```bash # 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 ```bash # 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 ```bash EMAIL_API_TIMEOUT=30 # Increase from 10 to 30 seconds ``` ### Frequent Failures **Problem**: API calls failing consistently **Solution**: Check logs for specific error messages ```bash # 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 ```python 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): ```python NotificationService.send_email( email='user@example.com', subject='Welcome', message='Hello!' ) ``` **After** (using external API): ```python 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: ```python 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.) ## Related Documentation - [Notification Service](../apps/notifications/README.md) - [Environment Configuration](../config/settings/DJANGO_ENVIRON_CONFIG.md) - [Logging Configuration](../config/settings/base.py#logging-configuration)