Skip to content

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 API
  • apps/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 source
  • mcp__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

  1. Contact Management:
  2. Endpoint: POST /api/contacts/new for creating contacts
  3. Endpoint: PATCH /api/contacts/{ID}/edit for updates
  4. Use email as unique identifier (isUniqueIdentifier: true)
  5. Batch operations supported: POST /api/contacts/batch/new

  6. Custom Fields:

  7. Must create custom fields before using them: POST /api/fields/contact/new
  8. Field types: text, email, lookup, multiselect, datetime, etc.
  9. Fields identified by alias (e.g., 'city', 'subscriber_source')

  10. Segments:

  11. Create dynamic segments with filters: POST /api/segments/new
  12. Manually add contacts to segments: POST /api/segments/{ID}/contact/{CONTACT_ID}/add
  13. Batch add: POST /api/segments/{ID}/contacts/add
  14. Filter examples: city='San Francisco', tag='subscriber'

  15. Tags:

  16. Add tags to contacts for easy segmentation
  17. 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:

  1. New Django App Module: apps/api/subscriber/mautic_integration.py
  2. Contains MauticClient class
  3. Helper functions for data mapping

  4. New Signal Handler: apps/api/subscriber/signals.py

  5. @receiver(post_save, sender=Subscriber)
  6. Triggers Celery task

  7. New Celery Task: apps/api/subscriber/tasks.py

  8. sync_subscriber_to_mautic(subscriber_id)
  9. Handles API calls with retries

  10. Settings Configuration: apps/api/brktickets/settings.py

  11. Add Mautic credentials to environment variables

  12. Management Command: apps/api/subscriber/management/commands/sync_mautic_fields.py

  13. One-time setup: Create custom fields in Mautic
  14. 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_ENABLED environment 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

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)