--- name: mailjet-email-service description: Mailjet Email Service Implementation Skill context: fork --- # Mailjet Email Service Implementation Skill ## Purpose This skill provides a complete, reusable implementation for setting up transactional email notifications using Mailjet REST API v3 in a Django application. It includes: - Core email service class with retry logic - Pre-built email templates for common workflows (purchase confirmation, provider activation, booking notifications, etc.) - Integration patterns for sending emails from views and business logic - Error handling and logging strategies - HTML + plain text email fallbacks **User Outcome**: After following this skill, users will have: - A production-grade email service with automatic retries - Consistent, branded HTML email templates - Easy integration points in Django views - Comprehensive error handling and logging - Support for multiple email types (purchase, activation, notifications, reports) ## When to Use This Skill Use this skill when: - You need transactional emails in a Django REST API application - You want to use Mailjet for email delivery - You need professionally designed, branded HTML email templates - You want automatic retry logic for failed email sends - You need to send different types of emails (confirmations, notifications, alerts) - You want proper fallback plain text versions ## Prerequisites Checklist Before starting, verify your project has: - [ ] Django REST Framework configured - [ ] Mailjet account with API credentials (API_KEY, SECRET_KEY) - [ ] Django settings file configured for environment variables (using `decouple` or similar) - [ ] Logging configured in Django settings - [ ] Requests library installed (`pip install requests`) - [ ] Email templates directory structure (optional, for template rendering) - [ ] Models for entities you want to email about (PassPurchase, User, Booking, etc.) - [ ] Basic understanding of Django views and models **Time Estimate**: 2-3 hours total ## Architecture Overview ### Email Service Flow ``` Trigger event (purchase, activation, booking) ↓ Call MailjetEmailService method ↓ Build email context/content ↓ Render HTML template (if using templates) ↓ Generate plain text fallback ↓ Call _send_email() with retry logic ↓ POST to Mailjet API v3 with Basic Auth ↓ Handle response (success/retry/failure) ↓ Log results with MessageID ↓ Return success status to caller ``` ### Core Components - **MailjetEmailService**: Main class with methods for each email type - **\_send_email()**: Low-level method with retry logic (3 attempts) - **\_render_template()**: Renders Django templates for HTML emails - **\_build_context()**: Builds template context from model instances - **Public methods**: send_purchase_confirmation(), send_provider_activation_email(), etc. ## Phase 1: Django Configuration ### Step 1: Add Mailjet to Requirements ```bash pip install requests # If not already installed ``` (Note: requests is likely already a dependency of Django or other packages) Update `requirements.txt`: ``` requests>=2.10.0 ``` ### Step 2: Configure Django Settings **File**: `django-backend/config/settings.py` (or your settings file) Add Mailjet configuration (after other service configurations like Stripe, Twilio): ```python # Mailjet Configuration MAILJET_API_KEY = config('MAILJET_API_KEY', default='') MAILJET_SECRET_KEY = config('MAILJET_SECRET_KEY', default='') DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', default='noreply@yourapp.com') DEFAULT_FROM_NAME = config('DEFAULT_FROM_NAME', default='Your App Name') ``` ### Step 3: Add Environment Credentials **File**: `django-backend/.env` ```bash # Mailjet Configuration MAILJET_API_KEY=your_mailjet_api_key_here MAILJET_SECRET_KEY=your_mailjet_secret_key_here DEFAULT_FROM_EMAIL=noreply@yourapp.com DEFAULT_FROM_NAME=Your App Name ``` Get credentials from [Mailjet Dashboard](https://app.mailjet.com/account): 1. Go to Account Settings 2. Click "API Key Management" 3. Copy API Key and Secret Key ### Step 4: Configure Logging (Optional but Recommended) Add to LOGGING configuration in settings.py to capture email service logs: ```python LOGGING = { # ... existing config ... 'loggers': { # ... existing loggers ... 'payments.services.mailjet_service': { 'handlers': ['console', 'file'], 'level': 'INFO', 'propagate': False, }, }, } ``` ## Phase 2: Create Email Service ### Step 1: Create Service File **File**: `django-backend/payments/services/mailjet_service.py` (NEW FILE) ```python """ Mailjet Email Service for transactional emails. Handles confirmation emails, notifications, and alerts via Mailjet REST API v3. """ from django.conf import settings import requests import logging logger = logging.getLogger(__name__) class MailjetEmailService: """ Email service for Mailjet REST API v3 transactional emails. Handles various notification types with retry logic and template rendering. Documentation: https://dev.mailjet.com/email/guides/send-api-v3/ """ def __init__(self): """Initialize Mailjet email service with API credentials""" self.api_key = settings.MAILJET_API_KEY self.api_secret = settings.MAILJET_SECRET_KEY self.api_url = 'https://api.mailjet.com/v3/send' self.from_email = settings.DEFAULT_FROM_EMAIL self.from_name = getattr(settings, 'DEFAULT_FROM_NAME', 'App Name') self.logger = logging.getLogger(__name__) def send_email(self, subject: str, to_email: str, to_name: str, html_content: str, text_content: str) -> bool: """ Send email via Mailjet REST API v3 with retry logic. Args: subject: Email subject to_email: Recipient email address to_name: Recipient name html_content: HTML email content text_content: Plain text email content Returns: bool: True if sent successfully, False otherwise """ max_retries = 3 retry_count = 0 while retry_count < max_retries: try: # Prepare API payload according to Mailjet v3 documentation payload = { 'FromEmail': self.from_email, 'FromName': self.from_name, 'Subject': subject, 'Text-part': text_content, 'Recipients': [ { 'Email': to_email, 'Name': to_name } ] } # Add HTML part if available if html_content: payload['Html-part'] = html_content # Make API request with Basic Auth response = requests.post( self.api_url, auth=(self.api_key, self.api_secret), json=payload, timeout=10 ) # Check response status - Mailjet returns 200 or 201 for success if response.status_code in [200, 201]: # Email was sent successfully by Mailjet try: response_data = response.json() sent_info = response_data.get('Sent', []) if sent_info: message_id = sent_info[0].get('MessageID') message_uuid = sent_info[0].get('MessageUUID') self.logger.info( f"Email sent successfully via Mailjet API (attempt {retry_count + 1}/{max_retries}). " f"MessageID: {message_id}, MessageUUID: {message_uuid}" ) return True else: # Status 200/201 means email was sent, even if no Sent info self.logger.warning( f"Mailjet API returned {response.status_code} (success) but no Sent info in response. " f"Email was likely sent to {to_email}" ) return True except ValueError as json_error: # JSON parsing failed but status was 200/201, so email was sent self.logger.warning( f"Email sent successfully (status {response.status_code}) but failed to parse response JSON: {str(json_error)}. " f"Email was delivered to {to_email}" ) return True # Handle rate limiting gracefully elif response.status_code == 429: self.logger.warning( f"Mailjet rate limit reached for {to_email} (attempt {retry_count + 1}/{max_retries}). " f"Failing gracefully." ) # Don't retry on rate limit, fail gracefully return False else: error_message = response.text self.logger.error( f"Mailjet API error (Status {response.status_code}, attempt {retry_count + 1}/{max_retries}): {error_message}" ) retry_count += 1 continue except requests.exceptions.Timeout: retry_count += 1 self.logger.error(f"Mailjet API request timeout for {to_email} (attempt {retry_count}/{max_retries})") if retry_count >= max_retries: return False continue except requests.exceptions.RequestException as e: retry_count += 1 self.logger.error( f"Mailjet API request failed for {to_email} (attempt {retry_count}/{max_retries}): {str(e)}" ) if retry_count >= max_retries: return False continue except Exception as e: retry_count += 1 self.logger.error( f"Failed to send email to {to_email} (attempt {retry_count}/{max_retries}): {str(e)}" ) if retry_count >= max_retries: return False continue # All retries exhausted self.logger.error(f"Failed to send email to {to_email} after {max_retries} attempts") return False def _render_template(self, template_name: str, context: dict) -> tuple: """ Render HTML and text email templates. Args: template_name: Path to HTML template (e.g., 'emails/purchase_confirmation.html') context: Template context dictionary Returns: tuple: (html_content, text_content) """ from django.template.loader import render_to_string # Try to render HTML template try: html_content = render_to_string(template_name, context) except Exception as e: self.logger.warning(f"Failed to render HTML template: {str(e)}") html_content = None # Generate plain text fallback text_content = f""" Thank you for using our service! If you have any questions, contact our support team. --- © 2025 All rights reserved """ return html_content, text_content ``` ### Step 2: Create Service Module Init File **File**: `django-backend/payments/services/__init__.py` ```python from .mailjet_service import MailjetEmailService __all__ = ['MailjetEmailService'] ``` ## Phase 3: Add Email Methods Add these methods to the `MailjetEmailService` class in `mailjet_service.py`. Each method handles a specific email type: ### Purchase Confirmation Email ```python def send_purchase_confirmation(self, user_email: str, user_name: str, plan_name: str, amount: str, pass_code: str) -> bool: """ Send purchase confirmation email. Args: user_email: Customer email user_name: Customer name plan_name: Name of pass/plan purchased amount: Amount paid pass_code: Unique pass code for customer Returns: bool: True if sent successfully """ try: subject = f"Purchase Confirmation - {plan_name}" html_content = f"""

Purchase Confirmation

Thank you, {user_name}!

Your purchase has been confirmed. Here are your details:

Plan: {plan_name}

Amount Paid: ${amount}

Pass Code: {pass_code}

You can now start using your {plan_name}. Keep your pass code safe!

Need help?
Contact our support team

""" text_content = f""" Thank you for purchasing {plan_name}! Plan: {plan_name} Amount Paid: ${amount} Pass Code: {pass_code} You can now start using your {plan_name}. Keep your pass code safe! If you have any questions, contact our support team. --- © 2025 All rights reserved """ return self.send_email( subject=subject, to_email=user_email, to_name=user_name, html_content=html_content, text_content=text_content ) except Exception as e: self.logger.error(f"Failed to send purchase confirmation email: {str(e)}") return False ``` ### Provider Activation Email ```python def send_provider_activation_email(self, user_email: str, user_name: str, activation_link: str) -> bool: """ Send provider account activation email with password setup link. Args: user_email: Provider email address user_name: Provider business name activation_link: URL to set up password and activate account Returns: bool: True if sent successfully """ try: subject = "Welcome - Activate Your Provider Account" html_content = f"""

Provider Portal

Account Created

Welcome, {user_name}!

Your provider account has been created successfully. You're one step away from accessing the Provider Portal.

To activate your account:

  1. Click the button below to set up your password
  2. Create a secure password for your account
  3. Log in to the Provider Portal
Activate Account & Set Password

If the button doesn't work, copy and paste this link into your browser:
{activation_link}

This activation link is valid for 3 days. If you need assistance, contact us.

""" text_content = f""" Welcome to the Provider Portal, {user_name}! Your provider account has been created successfully. To activate your account and set up your password, please visit: {activation_link} Once activated, you'll be able to: - Access the Provider Portal - Manage bookings and appointments - Track your earnings This activation link is valid for 3 days. If you need assistance, contact us. Thank you for joining! --- © 2025 All rights reserved """ return self.send_email( subject=subject, to_email=user_email, to_name=user_name, html_content=html_content, text_content=text_content ) except Exception as e: self.logger.error(f"Failed to send provider activation email: {str(e)}") return False ``` ## Phase 4: Integration in Views ### Step 1: Import Email Service **File**: `django-backend/payments/services/stripe_service.py` (or your relevant view file) ```python from payments.services.mailjet_service import MailjetEmailService # ... rest of imports ... ``` ### Step 2: Call Email Service from Views Add email sending after successful payment processing: ```python def handle_successful_payment(pass_purchase): """Example: Send confirmation after payment success""" try: mailjet = MailjetEmailService() mailjet.send_purchase_confirmation( user_email=pass_purchase.user.email, user_name=pass_purchase.user.full_name, plan_name=pass_purchase.plan.name, amount=str(pass_purchase.amount_paid), pass_code=pass_purchase.pass_code ) except Exception as e: logger.error(f"Failed to send confirmation email: {str(e)}") # Don't fail the purchase if email fails - log and continue ``` Add email sending after provider creation: ```python def create_provider_account(user, activation_link): """Example: Send activation email after provider creation""" try: mailjet = MailjetEmailService() mailjet.send_provider_activation_email( user_email=user.email, user_name=user.full_name, activation_link=activation_link ) except Exception as e: logger.error(f"Failed to send activation email: {str(e)}") # Don't fail account creation if email fails ``` ## Testing Your Implementation ### Test 1: Verify Credentials ```bash # Start Django shell python manage.py shell # Test credentials are loaded from django.conf import settings print(settings.MAILJET_API_KEY) print(settings.DEFAULT_FROM_EMAIL) ``` ### Test 2: Send Test Email ```bash python manage.py shell from payments.services import MailjetEmailService email_service = MailjetEmailService() result = email_service.send_email( subject="Test Email", to_email="your-test-email@example.com", to_name="Test User", html_content="

This is a test

", text_content="This is a test" ) print(f"Email sent: {result}") ``` ### Test 3: Monitor Logs ```bash # In another terminal, monitor Django logs tail -f logs/django.log # Look for "Email sent successfully" messages # Or look for error messages if email fails ``` ### Test 4: Check Mailjet Dashboard 1. Go to [Mailjet Dashboard](https://app.mailjet.com/mailbox) 2. You should see your test email in the "Sent Emails" section 3. Check for delivery status, opens, clicks ## Troubleshooting ### Issue: "ModuleNotFoundError: No module named 'requests'" **Solution**: ```bash pip install requests pip freeze > requirements.txt ``` ### Issue: "Mailjet API error (Status 400)" **Possible causes**: - Invalid API credentials - Malformed JSON payload - Invalid email address - FromEmail doesn't match Mailjet account sender **Solution**: 1. Verify API credentials in Mailjet dashboard 2. Check FromEmail is registered in Mailjet 3. Verify email addresses are valid 4. Check logs for exact error message ### Issue: "Mailjet rate limit reached (Status 429)" **Cause**: Too many emails sent too quickly **Solution**: - Implement message queue (Celery) for batch sending - Throttle email sending - Contact Mailjet to increase rate limits ### Issue: Email not received **Debug steps**: 1. Check Django logs for "Email sent successfully" message 2. Go to Mailjet dashboard - is it showing as sent? 3. Check spam folder in test email account 4. Verify recipient email is correct 5. Check Mailjet bounce reports ### Issue: "Authentication failed" **Solution**: ```bash python manage.py shell from django.conf import settings print(f"Key: {settings.MAILJET_API_KEY}") print(f"Secret: {settings.MAILJET_SECRET_KEY}") # Both should be non-empty ``` ## Best Practices 1. **Always catch exceptions** - Email failures shouldn't break your app 2. **Log everything** - Use logger for debugging 3. **Test with real emails** - Test SMTP integration fully before deploying 4. **Use retry logic** - The service already has 3 retries built in 5. **Monitor Mailjet dashboard** - Check delivery rates and bounces 6. **Set up sender addresses** - Register sender addresses in Mailjet account 7. **Use HTML + plain text** - Provide both formats for best compatibility 8. **Test from views** - Verify email sending in actual workflow ## Common Email Types to Implement 1. **Purchase Confirmations** - After payment 2. **Activation Emails** - Account creation with reset link 3. **Booking Notifications** - Provider notification of new booking 4. **Status Updates** - Booking status changes 5. **Alerts** - Issues, problems, urgent notifications 6. **Receipts** - Transaction receipts 7. **Password Resets** - Account recovery 8. **Reminders** - Upcoming appointments, renewals ## Advanced: Using Django Templates For more complex emails, use Django template rendering: ```python def send_with_template(self, user_email, user_name, context): """Send email using Django template""" html_content, text_content = self._render_template( 'emails/purchase_confirmation.html', context ) return self.send_email( subject="Purchase Confirmation", to_email=user_email, to_name=user_name, html_content=html_content, text_content=text_content ) ``` Create template file: `django-backend/templates/emails/purchase_confirmation.html` ```html

Purchase Confirmation

Thank you, {{ user_name }}!

Your purchase details:

Thank you for your purchase!

``` ## File Structure Summary ``` django-backend/ ├── config/ │ ├── settings.py # MAILJET_API_KEY, etc. │ └── .env # MAILJET_API_KEY=... ├── payments/ │ ├── services/ │ │ ├── __init__.py # Export MailjetEmailService │ │ ├── mailjet_service.py # NEW - Main email service │ │ └── stripe_service.py # Import and use email service │ ├── views.py # Call email service after payment │ └── services.py # Call email service after pass creation ├── accounts/ │ └── views.py # Call email service after provider activation ├── bookings/ │ └── views.py # Call email service for booking notifications └── templates/ └── emails/ ├── purchase_confirmation.html ├── provider_activation.html └── booking_notification.html ``` ## Support & Resources - [Mailjet API Documentation](https://dev.mailjet.com/email/guides/send-api-v3/) - [Mailjet REST API v3 Reference](https://app.mailjet.com/docs/api/send/post) - [Mailjet Python SDK](https://github.com/mailjet/mailjet-apiv3-python) (Alternative) - [Django Email Documentation](https://docs.djangoproject.com/en/stable/topics/email/) - [Mailjet Dashboard](https://app.mailjet.com/)