> ## Documentation Index
> Fetch the complete documentation index at: https://docs.senderr.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Sending an email via Senderr using Python

> Learn how to integrate Senderr email templates into your Python applications

# Sending an email via Senderr using Python

This guide shows you how to integrate Senderr email templates into your Python applications using the requests library and Resend for email delivery.

## Prerequisites

Before you begin, make sure you have:

* Python 3.7 or higher
* A Senderr account with templates in your library
* A Senderr API key ([generate one here](https://senderr.dev/dashboard/settings))
* A Resend account for sending emails ([sign up here](https://resend.com))

## Installation

Install the required packages:

```bash theme={null}
pip install requests resend
```

Or add to your `requirements.txt`:

```txt theme={null}
requests>=2.31.0
resend>=0.6.0
```

## Basic Setup

### Environment Configuration

Create a `.env` file or set environment variables:

```bash theme={null}
export SENDERR_API_KEY="your_senderr_api_key_here"
export RESEND_API_KEY="your_resend_api_key_here"
```

### Basic Senderr Client

Create a simple client class for interacting with Senderr:

```python theme={null}
import os
import requests
from typing import Dict, List, Optional, Any

class SenderrClient:
    def __init__(self, api_key: str = None):
        self.api_key = api_key or os.getenv('SENDERR_API_KEY')
        self.base_url = 'https://senderr.dev/api/v1'
        self.session = requests.Session()
        self.session.headers.update({
            'Authorization': f'Bearer {self.api_key}',
            'Content-Type': 'application/json'
        })

    def get_library(self) -> List[Dict]:
        """Get all templates in your library."""
        response = self.session.get(f'{self.base_url}/library')
        response.raise_for_status()
        return response.json()['templates']

    def get_template_schema(self, template_id: str) -> Dict:
        """Get the variable schema for a template."""
        response = self.session.get(f'{self.base_url}/templates/{template_id}/schema')
        response.raise_for_status()
        return response.json()

    def render_template(self, template_id: str, variables: Dict, format: str = 'html') -> Dict:
        """Render a template with the provided variables."""
        payload = {
            'variables': variables,
            'format': format
        }
        response = self.session.post(
            f'{self.base_url}/templates/{template_id}/render',
            json=payload
        )
        response.raise_for_status()
        return response.json()
```

## Email Service Integration

### Resend Integration

```python theme={null}
import resend
from typing import Dict, List, Optional

class EmailService:
    def __init__(self, api_key: str = None):
        self.api_key = api_key or os.getenv('RESEND_API_KEY')
        resend.api_key = self.api_key

    def send_email(
        self,
        to: str | List[str],
        subject: str,
        html_content: str,
        from_email: str = "noreply@yourcompany.com",
        reply_to: Optional[str] = None
    ) -> Dict:
        """Send an email using Resend."""
        params = {
            "from": from_email,
            "to": to if isinstance(to, list) else [to],
            "subject": subject,
            "html": html_content,
        }

        if reply_to:
            params["reply_to"] = reply_to

        try:
            email = resend.Emails.send(params)
            return {"success": True, "id": email.get("id")}
        except Exception as e:
            return {"success": False, "error": str(e)}
```

## Complete Example: Welcome Email

Here's a complete example that sends a welcome email using a Senderr template:

```python theme={null}
import os
from datetime import datetime
from dataclasses import dataclass
from typing import Optional

@dataclass
class User:
    email: str
    first_name: str
    last_name: str
    company: Optional[str] = None
    signup_date: Optional[datetime] = None

class WelcomeEmailService:
    def __init__(self):
        self.senderr = SenderrClient()
        self.email_service = EmailService()
        self.welcome_template_id = "your_welcome_template_id"  # Set your template ID

    def send_welcome_email(self, user: User) -> Dict:
        """Send a welcome email to a new user."""
        try:
            # Prepare template variables
            variables = {
                "firstName": user.first_name,
                "lastName": user.last_name,
                "email": user.email,
                "companyName": user.company or "there",
                "signupDate": user.signup_date.strftime("%B %d, %Y") if user.signup_date else datetime.now().strftime("%B %d, %Y"),
                "dashboardUrl": "https://yourapp.com/dashboard",
                "supportEmail": "support@yourcompany.com"
            }

            # Render the template
            rendered = self.senderr.render_template(
                self.welcome_template_id,
                variables
            )

            # Send the email
            result = self.email_service.send_email(
                to=user.email,
                subject=f"Welcome to our platform, {user.first_name}!",
                html_content=rendered['html']
            )

            return result

        except requests.exceptions.RequestException as e:
            return {"success": False, "error": f"Template rendering failed: {str(e)}"}
        except Exception as e:
            return {"success": False, "error": f"Email sending failed: {str(e)}"}

# Usage
if __name__ == "__main__":
    # Create a new user
    new_user = User(
        email="john.doe@example.com",
        first_name="John",
        last_name="Doe",
        company="Acme Corp",
        signup_date=datetime.now()
    )

    # Send welcome email
    welcome_service = WelcomeEmailService()
    result = welcome_service.send_welcome_email(new_user)

    if result['success']:
        print(f"Welcome email sent successfully! ID: {result['id']}")
    else:
        print(f"Failed to send email: {result['error']}")
```

## Advanced Features

### Template Management

```python theme={null}
class TemplateManager:
    def __init__(self, senderr_client: SenderrClient):
        self.client = senderr_client
        self._template_cache = {}

    def find_template_by_name(self, name: str) -> Optional[Dict]:
        """Find a template by its name."""
        templates = self.client.get_library()
        for template in templates:
            if template['name'].lower() == name.lower():
                return template
        return None

    def get_cached_schema(self, template_id: str) -> Dict:
        """Get template schema with caching."""
        if template_id not in self._template_cache:
            self._template_cache[template_id] = self.client.get_template_schema(template_id)
        return self._template_cache[template_id]

    def validate_variables(self, template_id: str, variables: Dict) -> List[str]:
        """Validate that all required variables are provided."""
        schema = self.get_cached_schema(template_id)
        required_vars = schema.get('required', [])
        missing_vars = []

        for var in required_vars:
            if var not in variables or variables[var] is None:
                missing_vars.append(var)

        return missing_vars
```

### Bulk Email Sending

```python theme={null}
import asyncio
import aiohttp
from concurrent.futures import ThreadPoolExecutor
from typing import List

class BulkEmailService:
    def __init__(self, max_workers: int = 5):
        self.senderr = SenderrClient()
        self.email_service = EmailService()
        self.max_workers = max_workers

    def send_bulk_emails(
        self,
        template_id: str,
        recipients: List[Dict],
        base_variables: Dict = None
    ) -> List[Dict]:
        """Send emails to multiple recipients with personalized variables."""

        def send_single_email(recipient):
            try:
                # Merge base variables with recipient-specific variables
                variables = {**(base_variables or {}), **recipient['variables']}

                # Render template
                rendered = self.senderr.render_template(template_id, variables)

                # Send email
                result = self.email_service.send_email(
                    to=recipient['email'],
                    subject=recipient.get('subject', 'Newsletter'),
                    html_content=rendered['html']
                )

                return {
                    "email": recipient['email'],
                    "success": result['success'],
                    "id": result.get('id'),
                    "error": result.get('error')
                }

            except Exception as e:
                return {
                    "email": recipient['email'],
                    "success": False,
                    "error": str(e)
                }

        # Use ThreadPoolExecutor for concurrent sending
        with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
            results = list(executor.map(send_single_email, recipients))

        return results

# Usage example
bulk_service = BulkEmailService()
recipients = [
    {
        "email": "user1@example.com",
        "variables": {"firstName": "John", "lastName": "Doe"},
        "subject": "Monthly Newsletter"
    },
    {
        "email": "user2@example.com",
        "variables": {"firstName": "Jane", "lastName": "Smith"},
        "subject": "Monthly Newsletter"
    }
]

results = bulk_service.send_bulk_emails("newsletter_template_id", recipients)
print(f"Sent {sum(1 for r in results if r['success'])} out of {len(results)} emails")
```

## Error Handling and Retry Logic

```python theme={null}
import time
from functools import wraps

def retry_on_failure(max_retries: int = 3, delay: float = 1.0):
    """Decorator to retry failed operations."""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except requests.exceptions.RequestException as e:
                    if attempt == max_retries - 1:
                        raise e
                    time.sleep(delay * (2 ** attempt))  # Exponential backoff
            return None
        return wrapper
    return decorator

class RobustSenderrClient(SenderrClient):
    @retry_on_failure(max_retries=3)
    def render_template(self, template_id: str, variables: Dict, format: str = 'html') -> Dict:
        """Render template with retry logic."""
        return super().render_template(template_id, variables, format)
```

## Django Integration Example

```python theme={null}
# models.py
from django.db import models

class EmailTemplate(models.Model):
    name = models.CharField(max_length=200)
    senderr_template_id = models.CharField(max_length=100)
    description = models.TextField()
    is_active = models.BooleanField(default=True)

class EmailLog(models.Model):
    template = models.ForeignKey(EmailTemplate, on_delete=models.CASCADE)
    recipient_email = models.EmailField()
    subject = models.CharField(max_length=200)
    sent_at = models.DateTimeField(auto_now_add=True)
    success = models.BooleanField()
    error_message = models.TextField(blank=True)

# services.py
from django.conf import settings
from .models import EmailTemplate, EmailLog

class DjangoEmailService:
    def __init__(self):
        self.senderr = SenderrClient()
        self.email_service = EmailService()

    def send_template_email(
        self,
        template_name: str,
        recipient_email: str,
        variables: Dict,
        subject: str
    ) -> bool:
        """Send email using a Django model template."""
        try:
            # Get template from database
            template = EmailTemplate.objects.get(name=template_name, is_active=True)

            # Render template
            rendered = self.senderr.render_template(
                template.senderr_template_id,
                variables
            )

            # Send email
            result = self.email_service.send_email(
                to=recipient_email,
                subject=subject,
                html_content=rendered['html']
            )

            # Log the result
            EmailLog.objects.create(
                template=template,
                recipient_email=recipient_email,
                subject=subject,
                success=result['success'],
                error_message=result.get('error', '')
            )

            return result['success']

        except EmailTemplate.DoesNotExist:
            EmailLog.objects.create(
                template=None,
                recipient_email=recipient_email,
                subject=subject,
                success=False,
                error_message=f"Template '{template_name}' not found"
            )
            return False
        except Exception as e:
            EmailLog.objects.create(
                template=template if 'template' in locals() else None,
                recipient_email=recipient_email,
                subject=subject,
                success=False,
                error_message=str(e)
            )
            return False
```

## Flask Integration Example

```python theme={null}
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
db = SQLAlchemy(app)

class EmailQueue(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    recipient_email = db.Column(db.String(255), nullable=False)
    template_id = db.Column(db.String(100), nullable=False)
    variables = db.Column(db.JSON)
    status = db.Column(db.String(50), default='pending')
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

@app.route('/api/send-email', methods=['POST'])
def send_email():
    data = request.get_json()

    # Validate input
    required_fields = ['email', 'template_id', 'variables']
    if not all(field in data for field in required_fields):
        return jsonify({'error': 'Missing required fields'}), 400

    try:
        # Initialize services
        senderr = SenderrClient()
        email_service = EmailService()

        # Render template
        rendered = senderr.render_template(
            data['template_id'],
            data['variables']
        )

        # Send email
        result = email_service.send_email(
            to=data['email'],
            subject=data.get('subject', 'Email from Senderr'),
            html_content=rendered['html']
        )

        if result['success']:
            return jsonify({'success': True, 'message': 'Email sent successfully'})
        else:
            return jsonify({'success': False, 'error': result['error']}), 500

    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500

if __name__ == '__main__':
    app.run(debug=True)
```

## Testing

```python theme={null}
import unittest
from unittest.mock import Mock, patch

class TestSenderrClient(unittest.TestCase):
    def setUp(self):
        self.client = SenderrClient(api_key="test_key")

    @patch('requests.Session.get')
    def test_get_library(self, mock_get):
        # Mock successful response
        mock_response = Mock()
        mock_response.json.return_value = {'templates': [{'id': '123', 'name': 'Test'}]}
        mock_response.raise_for_status.return_value = None
        mock_get.return_value = mock_response

        # Test
        templates = self.client.get_library()

        # Assert
        self.assertEqual(len(templates), 1)
        self.assertEqual(templates[0]['name'], 'Test')

    @patch('requests.Session.post')
    def test_render_template(self, mock_post):
        # Mock successful response
        mock_response = Mock()
        mock_response.json.return_value = {'html': '<h1>Hello World</h1>'}
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response

        # Test
        result = self.client.render_template('123', {'name': 'John'})

        # Assert
        self.assertIn('html', result)
        self.assertEqual(result['html'], '<h1>Hello World</h1>')

if __name__ == '__main__':
    unittest.main()
```

## Best Practices

### 1. Error Handling

Always handle API errors gracefully:

* Network timeouts
* Invalid API keys
* Template not found
* Missing variables

### 2. Caching

Cache template schemas and frequently used templates to reduce API calls.

### 3. Rate Limiting

Respect API rate limits by implementing backoff strategies.

### 4. Security

* Store API keys securely (environment variables, secrets management)
* Validate user input before passing to templates
* Use HTTPS for all API calls

### 5. Monitoring

Log email sending results for debugging and monitoring purposes.

## Next Steps

* [Check out n8n integration](/guides/integrations/n8n)
* [Learn about TypeScript integration](/guides/integrations/typescript)
* [Explore the API reference](/api-reference/introduction)
* [Browse available templates](https://senderr.dev/templates)

Need help? Contact [support@senderr.dev](mailto:support@senderr.dev)
