Mautic Email Segmentation Integration Plan¶
PiqueTickets Email Marketing System Integration
1. Executive Summary & Feature Overview¶
1.1 Feature Description¶
- Feature Name: Mautic Email Marketing Integration with Subscriber Segmentation
- Feature Type: New Feature / Integration
- Priority Level: High
1.2 Problem Statement¶
- Current State: PiqueTickets has a subscriber model that captures email addresses and demographic information (city, location, interests), but lacks integration with a marketing automation platform to segment and communicate with subscribers effectively.
- Pain Points:
- No automated email marketing campaigns
- Unable to segment subscribers by location (city), interests, or behavior
- Manual effort required to export and manage subscriber lists
- No centralized marketing automation workflows
- User Impact: Producers and marketing teams cannot effectively target subscribers with location-based promotions or personalized campaigns
- Business Value: Enable targeted email marketing campaigns, improve conversion rates through segmentation, and automate subscriber lifecycle management
1.3 Expected Outcomes¶
- Success Metrics:
- 100% of new subscribers automatically sync to Mautic
- Subscriber attributes (city, location, demographics) properly mapped to Mautic custom fields
- Real-time or near-real-time synchronization (< 5 minutes)
- Zero data loss during synchronization
- User Experience Goals: Seamless, automatic synchronization with no manual intervention required
- Technical Goals:
- Reliable signal-based integration using Django signals
- Async processing with Celery for performance
- Retry logic for failed API calls
- Comprehensive logging and error tracking
2. Stakeholder Analysis & Requirements¶
2.1 Affected Users & Systems¶
- Primary Users: Marketing team, producers managing email campaigns
- Secondary Users: Subscribers (receiving targeted emails), system administrators
- System Components:
apps/api/subscriber- Subscriber model and APIapps/api/tickets- Order/ticket purchase data- Celery task queue
- Mautic external API
- Integration Points:
- Subscriber creation endpoint (POST
/api/subscriber/create) - Ticket purchase workflow (Order model)
- Django signals (post_save)
- Mautic REST API
2.2 Functional Requirements¶
Must-Have Features: - Automatic synchronization of new subscribers to Mautic as contacts - Map subscriber attributes to Mautic custom fields: - Email (core field) - First name, last name (core fields) - City (custom field from SubscriberMetaData.ip_city) - Region/State (custom field from SubscriberMetaData.ip_region) - Country (custom field from SubscriberMetaData.ip_country) - Signup source/referer (custom field from SubscriberMetaData.url_referer) - Update existing Mautic contacts when subscriber data changes - Django signal-based trigger on subscriber creation/update - Celery async task for Mautic API calls - Error handling and retry logic
Should-Have Features: - Sync ticket purchasers (Order.email) to Mautic if not already subscribers - Add tags to contacts based on: - Subscriber status vs ticket purchaser - Geographic location - Interests (from SubscriberDemographics) - Create Mautic segments for: - Subscribers by city - Subscribers by region - Ticket purchasers - Bi-directional sync (handle unsubscribes from Mautic)
Could-Have Features: - Sync ticket purchase history as contact activities - Track show attendance in Mautic - Sync venue preferences based on ticket purchases - Integration with Mautic campaigns for automated workflows
Won't-Have Features (V1): - Historical data migration (will only sync new/updated records) - Real-time webhooks from Mautic to PiqueTickets - Campaign creation/management from PiqueTickets UI
2.3 Non-Functional Requirements¶
- Performance:
- Async processing should not block subscriber creation API (< 200ms response time)
- Celery tasks should complete within 30 seconds
- Support bulk operations (batch contact creation)
- Security:
- Mautic API credentials stored in environment variables
- Use Basic Auth or OAuth2 for Mautic API authentication
- Filter sensitive data (no credit card info, passwords)
- Follow GDPR compliance for data transfer
- Reliability:
- Retry failed API calls with exponential backoff (3 retries)
- Log all sync operations for auditing
- Handle Mautic API rate limits gracefully
- Maintain data integrity even on partial failures
3. Current State Analysis¶
3.1 Codebase Research Methodology¶
Primary Tools Used:
- Serena MCP:
- mcp__serena__find_file - Located subscriber models, signals, and task files
- mcp__serena__get_symbols_overview - Analyzed models.py structure
- mcp__serena__list_dir - Explored apps/api directory structure
- mcp__serena__search_for_pattern - Found existing signal patterns and Celery task usage
- Read operations on key files:
- apps/api/subscriber/models.py - Subscriber, SubscriberMetaData, SubscriberDemographics models
- apps/api/subscriber/views.py - Subscriber creation endpoint
- apps/api/tickets/signal.py - Existing signal pattern for Show notifications
- apps/api/brktickets/celery.py - Celery configuration
- apps/api/brktickets/settings.py - Django settings and installed apps
- Context7 MCP:
mcp__context7__resolve-library-id- Identified official Mautic documentation sourcemcp__context7__get-library-docs- Retrieved Mautic API documentation for:- Contact creation and updates
- Custom field management
- Segment management
- Contact-to-segment associations
- Authentication patterns
Secondary Tools: - Grep for searching email fields in Order model - Read for examining requirements.txt dependencies
3.2 Existing Architecture & Patterns¶
Tech Stack: - Backend: Django 5.1.14, Django REST Framework 3.15.2 - Task Queue: Celery 5.4.0, django-celery-beat 2.7.0, Redis 5.2.1 - Database: PostgreSQL (via psycopg2-binary) - Error Tracking: Sentry SDK 2.19.2 - Current Dependencies: No Mautic client library (will need to use requests or add mautic-api-library)
Architecture Pattern:
- Monolithic Django application with app-based organization
- RESTful API endpoints
- Signal-based event handling (see apps/api/tickets/signal.py:26-71 for Show notification pattern)
- Celery for async tasks (see apps/api/tickets/task.py for email sending patterns)
- Centralized settings in apps/api/brktickets/settings.py
Design Patterns:
- Django signals with @receiver decorators
- Celery @shared_task for async operations
- Model-Serializer-View pattern for REST API
- Database indexing for performance optimization
3.3 Relevant Existing Code¶
Subscriber Models (apps/api/subscriber/models.py):
class Subscriber(models.Model):
first_name = models.CharField(max_length=100, blank=True, null=True)
last_name = models.CharField(max_length=100, blank=True, null=True)
email = models.EmailField(unique=True) # Line 68
created_at = models.DateTimeField(auto_now_add=True)
class SubscriberMetaData(models.Model):
subscriber = models.ForeignKey("Subscriber", on_delete=models.CASCADE)
ip_address = models.GenericIPAddressField()
ip_country = models.CharField(max_length=100) # Line 44
ip_city = models.CharField(max_length=100) # Line 45
ip_region = models.CharField(max_length=100) # Line 46
ip_latitude = models.FloatField()
ip_longitude = models.FloatField()
user_agent = models.TextField()
url_referer = models.CharField(max_length=255, blank=True) # Line 51
class SubscriberDemographics(models.Model):
subscriber = models.ForeignKey("Subscriber", on_delete=models.CASCADE)
date_of_birth = models.DateField()
gender = models.CharField(max_length=100)
location = models.CharField(max_length=100)
interests = models.TextField()
Existing Signal Pattern (apps/api/tickets/signal.py:26-71):
- Shows how to use @receiver(post_save, sender=Model) pattern
- Demonstrates Celery task scheduling from signals
- Includes proper error handling and logging
- Handles both creation and update scenarios
Subscriber Creation Endpoint (apps/api/subscriber/views.py:39-106):
- Creates Subscriber via serializer
- Automatically captures IP-based location data
- Creates SubscriberMetaData with geographic info
- Includes Facebook Pixel tracking (similar pattern for Mautic)
Celery Configuration (apps/api/brktickets/celery.py):
- Celery app configured with namespace='CELERY'
- Autodiscover tasks from installed apps
- Task settings: 300s time limit, 240s soft limit, acks_late enabled
- Sentry integration for error tracking
Order Model (apps/api/tickets/models.py:702,778):
- Contains email field for ticket purchasers
- Indexed with ['email', 'show'] for performance
- Potential secondary source for Mautic contacts
3.4 Current Dependencies¶
From apps/api/requirements.txt:
- ✅ requests library available (via other dependencies)
- ✅ celery==5.4.0 - Task queue
- ✅ redis==5.2.1 - Broker
- ✅ django-celery-beat==2.7.0 - Scheduled tasks
- ❌ No Mautic-specific library
Dependency Decision: Use requests library for Mautic API calls rather than adding PHP-based mautic-api-library. This maintains consistency with Python stack and provides more control.
3.5 Potential Conflicts & Constraints¶
- No Historical Migration: V1 will only sync new/updated subscribers going forward
- API Rate Limits: Mautic may have rate limits - need exponential backoff
- Network Failures: External API calls may fail - need retry logic
- Data Consistency: Must handle partial failures (subscriber created but Mautic sync fails)
- GDPR Compliance: Must respect subscriber privacy and consent for data transfer
- Testing Challenges: Need Mautic sandbox/test instance for development
4. Research & Best Practices¶
4.1 Industry Standards Research¶
Mautic API Documentation (via Context7 MCP /websites/developer_mautic):
Best Practices Identified:
1. Authentication:
- Use Basic Auth (username/password) or OAuth2
- Store credentials in environment variables
- API base URL: https://your-mautic-instance.com
- Contact Management:
- Endpoint:
POST /api/contacts/newfor creating contacts - Endpoint:
PATCH /api/contacts/{ID}/editfor updates - Use email as unique identifier (
isUniqueIdentifier: true) -
Batch operations supported:
POST /api/contacts/batch/new -
Custom Fields:
- Must create custom fields before using them:
POST /api/fields/contact/new - Field types: text, email, lookup, multiselect, datetime, etc.
-
Fields identified by
alias(e.g., 'city', 'subscriber_source') -
Segments:
- Create dynamic segments with filters:
POST /api/segments/new - Manually add contacts to segments:
POST /api/segments/{ID}/contact/{CONTACT_ID}/add - Batch add:
POST /api/segments/{ID}/contacts/add -
Filter examples: city='San Francisco', tag='subscriber'
-
Tags:
- Add tags to contacts for easy segmentation
- Use tags to differentiate: 'subscriber' vs 'ticket_purchaser'
Codebase Pattern Validation:
- ✅ Existing signal pattern in apps/api/tickets/signal.py aligns with Django best practices
- ✅ Celery task pattern in apps/api/tickets/task.py shows proper async handling
- ✅ Error logging and Sentry integration already configured
4.2 Framework/Library Research¶
Django Signals:
- Use post_save signal for create/update events
- Check created kwarg to differentiate create vs update
- Handle raw=True kwarg to skip signals during fixtures/migrations
Celery Best Practices:
- Use @shared_task decorator for reusability
- Set bind=True to access task instance (for retries)
- Use autoretry_for for automatic retries
- Set retry_backoff=True for exponential backoff
- Add task-specific time_limit if needed
Mautic API Client Pattern:
import requests
from requests.auth import HTTPBasicAuth
class MauticClient:
def __init__(self, base_url, username, password):
self.base_url = base_url
self.auth = HTTPBasicAuth(username, password)
self.headers = {'Content-Type': 'application/json'}
def create_contact(self, data):
response = requests.post(
f"{self.base_url}/api/contacts/new",
json=data,
auth=self.auth,
headers=self.headers
)
response.raise_for_status()
return response.json()
5. Solution Design¶
5.1 Proposed Architecture¶
High-Level Flow:
Subscriber Created/Updated
↓
Django Signal Triggered (post_save)
↓
Celery Task Queued (async)
↓
Task Fetches Subscriber + MetaData
↓
Maps Data to Mautic Contact Format
↓
Calls Mautic API (Create or Update)
↓
Logs Success/Failure to Sentry
Component Design:
- New Django App Module:
apps/api/subscriber/mautic_integration.py - Contains MauticClient class
-
Helper functions for data mapping
-
New Signal Handler:
apps/api/subscriber/signals.py @receiver(post_save, sender=Subscriber)-
Triggers Celery task
-
New Celery Task:
apps/api/subscriber/tasks.py sync_subscriber_to_mautic(subscriber_id)-
Handles API calls with retries
-
Settings Configuration:
apps/api/brktickets/settings.py -
Add Mautic credentials to environment variables
-
Management Command:
apps/api/subscriber/management/commands/sync_mautic_fields.py - One-time setup: Create custom fields in Mautic
- Can be run via:
python manage.py sync_mautic_fields
Data Model Changes: Add tracking fields to Subscriber model:
class Subscriber(models.Model):
# ... existing fields ...
mautic_contact_id = models.IntegerField(null=True, blank=True)
mautic_last_sync = models.DateTimeField(null=True, blank=True)
mautic_sync_status = models.CharField(
max_length=20,
choices=[('pending', 'Pending'), ('synced', 'Synced'), ('failed', 'Failed')],
default='pending'
)
Mautic Custom Fields to Create:
| Field Alias | Field Label | Type | Source |
|-------------|-------------|------|--------|
| piquetickets_city | PiqueTickets City | text | SubscriberMetaData.ip_city |
| piquetickets_region | PiqueTickets Region | text | SubscriberMetaData.ip_region |
| piquetickets_country | PiqueTickets Country | text | SubscriberMetaData.ip_country |
| signup_source | Signup Source | text | SubscriberMetaData.url_referer |
| subscriber_type | Subscriber Type | lookup | 'email_subscriber', 'ticket_purchaser' |
API Design - Mautic Contact Payload:
{
"email": "user@example.com",
"firstname": "John",
"lastname": "Doe",
"piquetickets_city": "San Francisco",
"piquetickets_region": "California",
"piquetickets_country": "United States",
"signup_source": "https://piquetickets.com/shows",
"subscriber_type": "email_subscriber",
"tags": ["subscriber", "san_francisco"]
}
Integration Strategy: - Signal fires immediately after subscriber save - Celery task runs async (doesn't block API response) - Task fetches related SubscriberMetaData - Maps data to Mautic format - Checks if contact exists (by email) - If exists: PATCH update - If new: POST create - Updates Subscriber with mautic_contact_id and sync status - Logs all operations to Sentry
5.2 Technology Decisions¶
New Dependencies:
- None required (use built-in requests library)
Alternative Solutions Considered: 1. Mautic PHP API Library - Rejected: Requires PHP, not Python-native 2. Zapier/n8n Integration - Rejected: Adds external dependency, less control 3. Direct Database Write to Mautic - Rejected: Bypasses API, loses validation 4. Webhook-based sync - Deferred to V2: More complex, requires webhook endpoint
Why This Approach:
- ✅ Uses existing Django signal pattern (consistent with tickets/signal.py)
- ✅ Leverages existing Celery infrastructure
- ✅ No new dependencies
- ✅ Async processing (doesn't block user experience)
- ✅ Retry logic for reliability
- ✅ Uses official Mautic REST API
5.3 Security Considerations¶
Threat Model: - API credentials exposure - PII data leakage during transit - Unauthorized access to Mautic instance
Authentication/Authorization: - Store Mautic credentials in environment variables (never in code) - Use HTTPS for all API calls - Consider OAuth2 for production (more secure than Basic Auth)
Data Protection:
- Only sync necessary fields (email, name, location)
- Never sync: passwords, payment info, IP addresses (store in MetaData only)
- Respect GDPR: Include consent tracking
- Use Sentry's before_send to filter sensitive data from error logs
Security Testing: - Validate Mautic API responses for injection attempts - Test with invalid credentials - Verify rate limiting doesn't expose API endpoint
6. Implementation Plan¶
6.1 Development Phases¶
Phase 1: Foundation & Setup
- [ ] Add Mautic configuration to settings.py (MAUTIC_URL, credentials)
- [ ] Create apps/api/subscriber/mautic_integration.py with MauticClient class
- [ ] Create management command sync_mautic_fields.py to setup custom fields
- [ ] Add migration for Subscriber model (mautic_contact_id, mautic_last_sync, mautic_sync_status)
- [ ] Write unit tests for MauticClient
- Deliverable: Mautic client ready, custom fields created in Mautic
Phase 2: Core Sync Implementation
- [ ] Create apps/api/subscriber/signals.py with post_save signal
- [ ] Create apps/api/subscriber/tasks.py with sync_subscriber_to_mautic Celery task
- [ ] Implement data mapping logic (Subscriber → Mautic contact format)
- [ ] Add retry logic and error handling
- [ ] Update subscriber view to handle new sync status fields
- [ ] Write integration tests for signal → task flow
- Deliverable: Working sync from PiqueTickets to Mautic
Phase 3: Segmentation & Polish - [ ] Create Mautic segments via API or management command - [ ] Add tag management (subscriber, city-based tags) - [ ] Implement bulk sync management command for admin use - [ ] Add admin interface fields for viewing sync status - [ ] Comprehensive logging and Sentry error tracking - [ ] Create monitoring dashboard (Celery Flower) - Deliverable: Full segmentation capability, production-ready
6.2 Detailed Task Breakdown¶
| Task | Files Affected | Dependencies | Estimated Effort |
|---|---|---|---|
| Add Mautic settings | apps/api/brktickets/settings.py, .env |
None | 30 min |
| Create MauticClient class | apps/api/subscriber/mautic_integration.py |
Settings | 2 hours |
| Management command for field setup | apps/api/subscriber/management/commands/sync_mautic_fields.py |
MauticClient | 1.5 hours |
| Add Subscriber tracking fields | apps/api/subscriber/models.py, new migration |
None | 1 hour |
| Create signal handler | apps/api/subscriber/signals.py |
Models | 1 hour |
| Register signals in apps.py | apps/api/subscriber/apps.py |
Signals | 15 min |
| Create Celery sync task | apps/api/subscriber/tasks.py |
MauticClient, Models | 3 hours |
| Data mapping helper | apps/api/subscriber/mautic_integration.py |
Models | 1 hour |
| Unit tests for client | apps/api/subscriber/tests.py |
All above | 2 hours |
| Integration tests | apps/api/subscriber/tests.py |
All above | 2 hours |
| Admin interface updates | apps/api/subscriber/admin.py |
Models | 1 hour |
| Segment creation script | apps/api/subscriber/management/commands/create_mautic_segments.py |
MauticClient | 1.5 hours |
| Documentation | apps/api/subscriber/README.md |
All | 1 hour |
| Total | ~17.5 hours |
6.3 File Change Summary¶
New Files:
- apps/api/subscriber/mautic_integration.py - Mautic API client and helpers
- apps/api/subscriber/signals.py - Django signal handlers
- apps/api/subscriber/tasks.py - Celery tasks for sync
- apps/api/subscriber/management/commands/sync_mautic_fields.py - Setup command
- apps/api/subscriber/management/commands/create_mautic_segments.py - Segment setup
- apps/api/subscriber/management/commands/__init__.py - Package init
- apps/api/subscriber/migrations/000X_add_mautic_fields.py - Database migration
- apps/api/subscriber/README.md - Integration documentation
Modified Files:
- apps/api/subscriber/models.py - Add mautic_contact_id, mautic_last_sync, mautic_sync_status fields
- apps/api/subscriber/apps.py - Import signals in ready() method
- apps/api/subscriber/admin.py - Add sync status fields to admin interface
- apps/api/subscriber/tests.py - Add comprehensive tests
- apps/api/brktickets/settings.py - Add Mautic configuration
- apps/api/.env.example - Add Mautic credential placeholders
- apps/api/.env - Add actual Mautic credentials (not committed)
No Files Deleted
7. Testing Strategy¶
7.1 Test Coverage Plan¶
Unit Tests:
- MauticClient.create_contact() - Mock requests, verify payload
- MauticClient.update_contact() - Mock requests, verify PATCH
- MauticClient.create_custom_field() - Verify field creation
- map_subscriber_to_mautic_format() - Data transformation logic
- Test with missing MetaData (graceful degradation)
- Test retry logic with simulated API failures
Integration Tests: - Create Subscriber → Signal fires → Task queued - Task processes → Mautic API called → Subscriber updated with contact_id - Update Subscriber → Task updates Mautic contact - Handle duplicate email gracefully - Test with Mautic API errors (4xx, 5xx responses) - Test with network timeout
End-to-End Tests:
- Full flow: POST to /api/subscriber/create → Check Mautic dashboard for contact
- Verify custom fields populated correctly
- Verify tags applied
- Check contact appears in segments
Performance Tests: - Bulk subscriber creation (100 subscribers) - should not block API - Celery queue processing time - Mautic API response times
Test Environment Requirements:
- Development: Mautic sandbox instance at https://mautic-dev.piquetickets.com
- Staging: Mautic staging instance with test data
- Production: Production Mautic instance (feature flag controlled)
7.2 Acceptance Criteria¶
Definition of Done: - [ ] All unit tests pass (>90% coverage for new code) - [ ] Integration tests pass - [ ] Manual E2E test: Create subscriber → Appears in Mautic within 5 minutes - [ ] Code review completed by 2+ team members - [ ] Documentation updated (README, inline comments) - [ ] Mautic custom fields created and verified - [ ] At least 2 segments created (e.g., "SF Subscribers", "All Subscribers") - [ ] Admin interface shows sync status correctly - [ ] Sentry error tracking verified - [ ] Performance benchmarks met (API < 200ms, Task < 30s) - [ ] Security review completed (credentials not in code) - [ ] GDPR compliance verified
8. Risk Assessment & Mitigation¶
8.1 Technical Risks¶
| Risk | Probability | Impact | Mitigation Strategy |
|---|---|---|---|
| Mautic API rate limiting | Medium | High | Implement exponential backoff, queue throttling, monitor rate limits |
| Network failures during sync | High | Medium | Retry logic (3 attempts), log failures, manual resync command |
| Mautic instance downtime | Low | High | Queue tasks for retry, alert on sustained failures, fallback to manual export |
| Data mapping errors | Medium | Medium | Comprehensive unit tests, validation before API call, error logging |
| Duplicate contacts created | Low | Low | Use email as unique identifier, check for existing contact before create |
| Signal fires but task fails | Medium | Medium | Track sync_status in model, admin view for failed syncs, retry command |
| Subscriber model changes break sync | Low | High | Version mapping logic, schema validation, comprehensive tests |
8.2 Resource Risks¶
- Schedule Risks:
- Mautic sandbox setup delays → Start setup early, document process
- API documentation gaps → Use Context7 MCP docs, test thoroughly
- Skill Gaps:
- Team unfamiliar with Mautic API → Provide training, create runbook
- Celery debugging → Document common issues, set up Flower monitoring
- External Dependencies:
- Mautic instance availability → SLA with hosting provider, monitoring
- API changes → Version pin API, monitor Mautic release notes
8.3 Rollback Strategy¶
- Feature Flags:
MAUTIC_SYNC_ENABLEDenvironment variable (default: False)- Admin toggle to disable sync without code deployment
- Database Migrations:
- New fields nullable, can be added/removed safely
- Rollback migration:
python manage.py migrate subscriber <previous_migration> - Deployment Strategy:
- Deploy to staging first, monitor for 48 hours
- Gradual rollout: Enable for 10% of subscribers, then 50%, then 100%
- If issues: Disable via feature flag, investigate, fix, re-enable
9. Deployment & Operations¶
9.1 Deployment Plan¶
Environment Progression:
1. Dev: Local Mautic sandbox + local PiqueTickets
- Test all flows manually
- Run full test suite
2. Staging: Staging Mautic + staging PiqueTickets
- Deploy code
- Run migration: python manage.py migrate
- Create custom fields: python manage.py sync_mautic_fields
- Create segments: python manage.py create_mautic_segments
- Enable feature flag: MAUTIC_SYNC_ENABLED=True
- Monitor for 48 hours
3. Production: Production Mautic + production PiqueTickets
- Deploy during low-traffic window
- Run migration
- Create custom fields and segments
- Enable for 10% traffic (canary)
- Monitor for 24 hours
- Full rollout if no issues
Configuration Updates:
# Add to .env
MAUTIC_URL=https://mautic.piquetickets.com
MAUTIC_USERNAME=api_user
MAUTIC_PASSWORD=secure_password
MAUTIC_SYNC_ENABLED=True
9.2 Monitoring & Observability¶
Key Metrics: - Subscriber creation rate (per hour) - Mautic sync success rate (%) - Mautic sync failure rate (%) - Average task completion time - Celery queue depth - Mautic API response times
Alerting: - Sync failure rate > 5% for 10 minutes → Alert team - Celery queue depth > 100 for 30 minutes → Alert ops - Mautic API errors (5xx) > 10 in 5 minutes → Alert team - Task completion time > 60 seconds → Alert ops
Logging:
logger.info(f"Syncing subscriber {subscriber.id} to Mautic")
logger.info(f"Mautic contact created: {mautic_id}")
logger.error(f"Mautic sync failed for subscriber {subscriber.id}: {error}")
Dashboards: - Celery Flower: Monitor task status, retry counts - Grafana: Sync metrics, API latency - Sentry: Error tracking, performance monitoring
9.3 Support & Maintenance¶
Documentation:
- apps/api/subscriber/README.md - Setup instructions, architecture overview
- Inline code comments for complex logic
- Mautic custom field mapping reference
- Troubleshooting guide for common issues
Training: - Team walkthrough of architecture and flow - Demo of admin interface for viewing sync status - Runbook for handling sync failures
Ongoing Maintenance: - Weekly review of Sentry errors - Monthly audit of sync success rates - Quarterly review of Mautic segment health - Update when Mautic API changes
Common Operations:
# Resync failed subscribers
python manage.py shell
>>> from subscriber.tasks import sync_subscriber_to_mautic
>>> from subscriber.models import Subscriber
>>> failed = Subscriber.objects.filter(mautic_sync_status='failed')
>>> for s in failed: sync_subscriber_to_mautic.delay(s.id)
# Check Celery queue
celery -A brktickets inspect active
# Monitor tasks in Flower
http://localhost:5555
10. Success Measurement¶
10.1 Success Metrics¶
Technical Metrics: - Sync success rate: > 99% - Task completion time: < 30 seconds (avg) - API response time: < 200ms (p95) - Error rate: < 1%
User Metrics: - Marketing team can create city-based segments within 5 minutes - Email campaigns reach correct segments (100% accuracy) - Subscriber growth tracked in Mautic dashboard
Business Metrics: - Email campaign open rates (baseline vs segmented campaigns) - Conversion rate for location-based promotions - Time saved vs manual export/import (hours per week)
10.2 Review Schedule¶
- Week 1: Initial stability review - Check error rates, sync success
- Week 2: Adoption review - Marketing team using segments?
- Month 1: Full success evaluation - Metrics review, team feedback
- Quarter 1: ROI analysis - Compare segmented vs broadcast campaigns
11. Appendices¶
Appendix A: Research References¶
- Mautic Developer Documentation - Official API docs
- Context7 MCP Library:
/websites/developer_mautic- Comprehensive API examples - Django Signals Documentation - Signal patterns
- Celery Best Practices - Task patterns
Appendix B: Technical Diagrams¶
Signal-Based Sync Flow:
┌─────────────────────┐
│ API Request │
│ POST /subscriber/ │
│ create │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Subscriber.save() │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ post_save Signal │
│ (Immediate) │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Queue Celery Task │
│ (Async) │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ sync_subscriber_ │
│ to_mautic.delay() │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Fetch Subscriber + │
│ MetaData │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Map to Mautic │
│ Contact Format │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Check if Contact │
│ Exists (by email) │
└──────────┬──────────┘
│
┌────┴────┐
│ │
Exists New
│ │
▼ ▼
┌─────────┐ ┌────────┐
│ PATCH │ │ POST │
│ Update │ │ Create │
└────┬────┘ └───┬────┘
│ │
└────┬─────┘
│
▼
┌─────────────────────┐
│ Update Subscriber │
│ - mautic_contact_id│
│ - mautic_last_sync │
│ - sync_status │
└─────────────────────┘
Data Mapping:
PiqueTickets Mautic Contact
────────────────────────────────────────────────────
Subscriber.email → email (core)
Subscriber.first_name → firstname (core)
Subscriber.last_name → lastname (core)
MetaData.ip_city → piquetickets_city (custom)
MetaData.ip_region → piquetickets_region (custom)
MetaData.ip_country → piquetickets_country (custom)
MetaData.url_referer → signup_source (custom)
"email_subscriber" → subscriber_type (custom)
["subscriber", city] → tags
Appendix C: Code Examples¶
MauticClient Basic Structure:
import requests
from requests.auth import HTTPBasicAuth
from django.conf import settings
import logging
logger = logging.getLogger(__name__)
class MauticClient:
def __init__(self):
self.base_url = settings.MAUTIC_URL
self.auth = HTTPBasicAuth(
settings.MAUTIC_USERNAME,
settings.MAUTIC_PASSWORD
)
self.headers = {'Content-Type': 'application/json'}
def create_contact(self, contact_data):
"""Create a new contact in Mautic."""
try:
response = requests.post(
f"{self.base_url}/api/contacts/new",
json=contact_data,
auth=self.auth,
headers=self.headers,
timeout=30
)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
logger.error(f"Failed to create Mautic contact: {e}")
raise
def update_contact(self, contact_id, contact_data):
"""Update an existing contact."""
try:
response = requests.patch(
f"{self.base_url}/api/contacts/{contact_id}/edit",
json=contact_data,
auth=self.auth,
headers=self.headers,
timeout=30
)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
logger.error(f"Failed to update Mautic contact {contact_id}: {e}")
raise
def get_contact_by_email(self, email):
"""Check if contact exists by email."""
try:
response = requests.get(
f"{self.base_url}/api/contacts",
params={'search': f"email:{email}"},
auth=self.auth,
headers=self.headers,
timeout=30
)
response.raise_for_status()
data = response.json()
contacts = data.get('contacts', {})
return list(contacts.values())[0] if contacts else None
except (requests.exceptions.RequestException, IndexError) as e:
logger.error(f"Failed to fetch contact by email {email}: {e}")
return None
Signal Handler:
# apps/api/subscriber/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from subscriber.models import Subscriber
from subscriber.tasks import sync_subscriber_to_mautic
import logging
logger = logging.getLogger(__name__)
@receiver(post_save, sender=Subscriber)
def sync_to_mautic_on_save(sender, instance, created, **kwargs):
"""
Trigger Mautic sync when Subscriber is created or updated.
"""
# Skip for fixtures/raw data
if kwargs.get('raw', False):
return
# Only sync if Mautic integration is enabled
from django.conf import settings
if not getattr(settings, 'MAUTIC_SYNC_ENABLED', False):
return
action = "created" if created else "updated"
logger.info(f"Subscriber {instance.id} {action}, queuing Mautic sync")
# Queue async task
sync_subscriber_to_mautic.delay(instance.id)
Celery Task:
# apps/api/subscriber/tasks.py
from celery import shared_task
from django.utils import timezone
from subscriber.models import Subscriber, SubscriberMetaData
from subscriber.mautic_integration import MauticClient, map_subscriber_to_mautic
import logging
logger = logging.getLogger(__name__)
@shared_task(
bind=True,
autoretry_for=(Exception,),
retry_backoff=True,
retry_kwargs={'max_retries': 3},
time_limit=60
)
def sync_subscriber_to_mautic(self, subscriber_id):
"""
Sync subscriber to Mautic.
Creates new contact or updates existing.
"""
try:
subscriber = Subscriber.objects.get(id=subscriber_id)
logger.info(f"Starting Mautic sync for subscriber {subscriber_id}")
# Update status to pending
subscriber.mautic_sync_status = 'pending'
subscriber.save(update_fields=['mautic_sync_status'])
# Initialize client
client = MauticClient()
# Map data
contact_data = map_subscriber_to_mautic(subscriber)
# Check if exists
existing = client.get_contact_by_email(subscriber.email)
if existing:
# Update
mautic_id = existing['id']
result = client.update_contact(mautic_id, contact_data)
logger.info(f"Updated Mautic contact {mautic_id}")
else:
# Create
result = client.create_contact(contact_data)
mautic_id = result['contact']['id']
logger.info(f"Created Mautic contact {mautic_id}")
# Update subscriber
subscriber.mautic_contact_id = mautic_id
subscriber.mautic_last_sync = timezone.now()
subscriber.mautic_sync_status = 'synced'
subscriber.save(update_fields=[
'mautic_contact_id',
'mautic_last_sync',
'mautic_sync_status'
])
return f"Successfully synced subscriber {subscriber_id} to Mautic contact {mautic_id}"
except Subscriber.DoesNotExist:
logger.error(f"Subscriber {subscriber_id} not found")
raise
except Exception as e:
logger.error(f"Mautic sync failed for subscriber {subscriber_id}: {e}")
# Update status
try:
subscriber.mautic_sync_status = 'failed'
subscriber.save(update_fields=['mautic_sync_status'])
except:
pass
raise
Data Mapping Helper:
# apps/api/subscriber/mautic_integration.py
def map_subscriber_to_mautic(subscriber):
"""
Map Subscriber model to Mautic contact format.
"""
contact_data = {
'email': subscriber.email,
'firstname': subscriber.first_name or '',
'lastname': subscriber.last_name or '',
}
# Add metadata if available
try:
metadata = subscriber.subscribermetadata_set.first()
if metadata:
contact_data.update({
'piquetickets_city': metadata.ip_city or '',
'piquetickets_region': metadata.ip_region or '',
'piquetickets_country': metadata.ip_country or '',
'signup_source': metadata.url_referer or '',
})
# Add tags
tags = ['subscriber']
if metadata.ip_city:
tags.append(metadata.ip_city.lower().replace(' ', '_'))
contact_data['tags'] = tags
except Exception as e:
logger.warning(f"Could not fetch metadata for subscriber {subscriber.id}: {e}")
contact_data['subscriber_type'] = 'email_subscriber'
return contact_data
Document Information: - Created: 2025-01-23 - Version: 1.0 - Author: Claude Code (Planning Agent) - Status: Draft - Ready for Review
Quick Implementation Checklist¶
Pre-Implementation¶
- Review and approve this plan with team
- Set up Mautic development instance
- Obtain Mautic API credentials for dev/staging/prod
- Create feature flag plan
Phase 1: Foundation (Week 1)¶
- Environment setup (credentials, settings)
- MauticClient implementation
- Management commands for field/segment setup
- Database migration
Phase 2: Core Implementation (Week 2)¶
- Signal handler
- Celery task with retry logic
- Data mapping helpers
- Unit and integration tests
Phase 3: Polish & Deploy (Week 3)¶
- Admin interface updates
- Monitoring and logging
- Documentation
- Staging deployment
- Production rollout
Post-Launch¶
- Monitor metrics for 2 weeks
- Gather marketing team feedback
- Optimize based on learnings
- Plan V2 features (bi-directional sync, campaign integration)