Skip to main content

Lemlist-Brevo Contact Sync Workflow

1. Purpose

This Python script automates bidirectional contact synchronization between Lemlist and Brevo (formerly Sendinblue) email marketing platforms. It enables seamless data transfer, contact management, and synchronization workflows between the two platforms, supporting marketing automation and contact database management operations.

Key Capabilities:

  • Import contacts from Lemlist campaigns to Brevo
  • Import contacts from Brevo to Lemlist campaigns
  • Export Lemlist contacts to CSV files
  • Search and find contacts across platforms
  • Date-based filtering for selective synchronization
  • Automatic attribute mapping and creation
  • Duplicate detection and handling

2. Trigger

Type: Python Script (Command Line)

Execution Methods:

Standalone Execution

The script can be run directly from the command line with various options:

Basic Usage:

python lemlist-brevo.py 

Default Behavior: When run without arguments, the script executes both:

  1. Lemlist to Brevo import (for today's date)
  2. Brevo to Lemlist import (for yesterday and today)

3. Step-by-Step Process

Step 1: Environment Configuration

Loads environment variables from .env file, validates required API credentials, and initializes logging configuration.

What input it uses:

  • .env file containing:
    • LEMLIST_AUTH - Lemlist API authentication token
    • BREVO_API_KEY - Brevo API key
    • LEMLIST_CAMPAIGN_ID - Default Lemlist campaign ID for imports
    • LEMLIST_CAMPAIGN_ID_BREVO_TO_LEMLIST - Campaign ID for Brevo-to-Lemlist imports
    • LOG_DIR - Log directory path (default: "logs")

What output it produces:

  • Initialized API headers for Lemlist and Brevo
  • Log file path: logs/lemlist_brevo_YYYYMMDD.log
  • Validated credentials ready for API calls

Why this step is needed: Ensures secure API access and proper logging for debugging and audit trails.

Required Environment Variables:

LEMLIST_AUTH=your_lemlist_auth_token
BREVO_API_KEY=your_brevo_api_key
LEMLIST_CAMPAIGN_ID=cam_izAb64Dp8ifZphk5F
LEMLIST_CAMPAIGN_ID_BREVO_TO_LEMLIST=cam_ieN79BAdD9w7ixzhF
LOG_DIR=logs

Step 2: Fetch Campaigns (Lemlist)

Retrieves all campaigns from Lemlist account using pagination to handle large datasets.

What input it uses:

  • Lemlist API base URL: https://api.lemlist.com/api
  • Authentication headers
  • Pagination parameters (limit: 100, offset: 0)

What output it produces:

  • Array of all campaigns with:
    • Campaign ID (_id)
    • Campaign name (name)
    • Other campaign metadata

Why this step is needed: Provides campaign list for fetching contacts from multiple campaigns, enabling comprehensive contact synchronization.

API Endpoint: GET /api/campaigns?limit=100&offset={offset}

Step 3: Export Leads from Campaigns (Lemlist)

Exports all leads from each Lemlist campaign, including contacts in all states (active, paused, completed, etc.).

What input it uses:

  • Campaign IDs from Step 2
  • Export format: JSON
  • State filter: all (includes all contact states)

What output it produces:

  • Array of contact objects containing:
    • Email address
    • Contact attributes (firstName, lastName, company, etc.)
    • Campaign metadata
    • Date fields (createdAt, addedToCampaign, etc.)

Why this step is needed: Retrieves complete contact data from Lemlist campaigns for synchronization with Brevo.

API Endpoint: GET /api/campaigns/{campaign_id}/export/leads?format=json&state=all

Step 4: Date Filtering (Optional)

Filters contacts based on creation or enrollment date if a date parameter is provided.

What input it uses:

  • Contact array from Step 3
  • Date string (format: YYYY-MM-DD or DD-MM-YYYY)
  • Date normalization function handles multiple formats

What output it produces:

  • Filtered contact array containing only contacts added on the specified date
  • Debug logging for date field identification

Why this step is needed: Enables selective synchronization of contacts added on specific dates, reducing unnecessary API calls and maintaining data freshness.

Date Field Detection: The script intelligently searches for date fields in contacts:

  • Standard fields: createdAt, created_at, addedAt, added_at
  • Campaign-specific: addedToCampaign, enrolledAt, enrollmentDate
  • Normalized matching handles variations in field naming

Step 5: Fetch Brevo Contacts

Retrieves contacts from Brevo account using pagination, with optional date filtering via API parameters.

What input it uses:

  • Brevo API base URL: https://api.brevo.com/v3
  • API authentication headers
  • Optional date range parameters:
    • createdSince - ISO 8601 format start date
    • createdBefore - ISO 8601 format end date
  • Pagination (limit: 500, offset: 0)

What output it produces:

  • Array of Brevo contact objects containing:
    • Email address
    • Attributes (nested in attributes field)
    • Creation date (createdAt)
    • Contact ID

Why this step is needed: Retrieves contact data from Brevo for synchronization to Lemlist or comparison purposes.

API Endpoint: GET /v3/contacts?limit=500&offset={offset}&createdSince={date}&createdBefore={date}

Date Filtering:

  • If date range provided: Uses API-level filtering for efficiency
  • If no date: Fetches all contacts with pagination

Step 6: Attribute Mapping and Creation (Brevo)

Maps Lemlist contact fields to Brevo attribute names and creates missing attributes in Brevo if they don't exist.

What input it uses:

  • Contact fields from Lemlist contacts
  • Existing Brevo attributes (fetched via API)
  • Field mapping dictionary (CSV_TO_BREVO_MAPPING)

What output it produces:

  • Created Brevo attributes (type: TEXT)
  • Mapping of Lemlist fields to Brevo attribute names
  • List of failed attribute creations (logged)

Why this step is needed: Ensures all contact data can be stored in Brevo by creating necessary custom attributes and mapping field names correctly.

Field Mapping Examples:

  • firstName / first_nameFIRSTNAME
  • lastName / last_nameLASTNAME
  • company / companyNameCOMPANY
  • linkedinUrl / linkedin_urlLINKEDIN
  • phone / mobileSMS
  • jobTitle / job_titleJOB_TITLE

System-Restricted Fields: These fields cannot be created or overwritten:

  • EMAIL, WHATSAPP, SMS_BLACKLISTED, EMAIL_BLACKLISTED
  • UPDATE_DATE, MODIFIED, STATUS, EMAILSTATUS

API Endpoint: POST /v3/contacts/attributes/normal/{attribute_name}

Step 7: Duplicate Detection

Checks if an email already exists in the target platform before importing to prevent duplicates.

What input it uses:

  • Email address from source contact
  • Target platform API (Brevo or Lemlist)

What output it produces:

  • Boolean result: True if email exists, False if not
  • For Brevo: HTTP 200 (exists) or 404 (not found)
  • For Lemlist: Searches across all campaigns

Why this step is needed: Prevents duplicate contacts and maintains data integrity across platforms.

Brevo Check: GET /v3/contacts/{email} Lemlist Check: Searches all campaigns for matching email

Step 8: Phone Number Formatting

Formats phone numbers to international format for Brevo SMS field, with validation and country code handling.

What input it uses:

  • Raw phone number string
  • Default country code: +91 (India)

What output it produces:

  • Formatted phone number in international format (e.g., +919876543210)
  • Empty string if phone number is invalid

Why this step is needed: Ensures phone numbers are properly formatted for SMS functionality in Brevo.

Formatting Rules:

  • Removes spaces, dashes, parentheses, dots
  • Validates international format (starts with +)
  • Handles Indian numbers (10 digits → adds +91 prefix)
  • Validates length (10-15 digits after country code)

Step 9: Import Contacts to Brevo

Creates new contacts in Brevo with mapped attributes, skipping duplicates and handling errors gracefully.

What input it uses:

  • Filtered and mapped contact data from Lemlist
  • Brevo attribute mappings
  • Formatted phone numbers

What output it produces:

  • Import results:
    • imported: Count of successfully imported contacts
    • failed: Count of failed imports with error messages
    • skipped: Count of contacts skipped (already exist)
    • Sample lists of each category (first 10)

Why this step is needed: Transfers contact data from Lemlist to Brevo, enabling unified contact management and email marketing campaigns.

API Endpoint: POST /v3/contacts Payload Structure:

{
"email": "contact@example.com",
"attributes": {
"FIRSTNAME": "John",
"LASTNAME": "Doe",
"COMPANY": "Example Corp",
"SMS": "+919876543210"
},
"updateEnabled": true
}

Import Limit: Maximum 500 contacts per execution (configurable via MAX_IMPORT_LIMIT)

Step 10: Import Contacts to Lemlist

Creates or updates contacts in Lemlist campaign from Brevo contact data.

What input it uses:

  • Brevo contacts (from yesterday and today by default)
  • Lemlist campaign ID: LEMLIST_CAMPAIGN_ID_BREVO_TO_LEMLIST
  • Contact lookup dictionary for duplicate detection

What output it produces:

  • Import results:
    • imported: Count of newly created contacts
    • updated: Count of updated existing contacts
    • failed: Count of failed operations
    • Sample lists of each category

Why this step is needed: Synchronizes contacts from Brevo back to Lemlist, maintaining bidirectional data flow for sales outreach campaigns.

API Endpoint: POST /api/campaigns/{campaign_id}/leads Payload Structure:

{
"email": "contact@example.com",
"firstName": "John",
"lastName": "Doe",
"companyName": "Example Corp",
"linkedinUrl": "https://linkedin.com/in/johndoe",
"phone": "+919876543210"
}

Field Mapping (Brevo → Lemlist):

  • FIRSTNAMEfirstName
  • LASTNAMElastName
  • COMPANYcompanyName
  • LINKEDINlinkedinUrl
  • SMSphone
  • WEBSITEcompanyDomain

Step 11: CSV Export (Optional)

Exports Lemlist contacts to CSV file for backup, analysis, or external processing.

What input it uses:

  • Contact data from Lemlist (optionally filtered by date)
  • Output directory: /Users/fleetstudio-47/Documents/GitHub/AI-Hubspoot/

What output it produces:

  • CSV file named:
    • lemlist_contacts_on_YYYYMMDD.csv (if date specified)
    • lemlist_leads_all.csv (if all contacts)
  • File contains all contact fields as columns

Why this step is needed: Provides data backup and enables external analysis using spreadsheet tools or other systems.

File Location: /Users/fleetstudio-47/Documents/GitHub/AI-Hubspoot/lemlist_contacts_on_YYYYMMDD.csv

Step 12: Logging and Reporting

Logs all operations, errors, and results to daily log files with timestamps.

What input it uses:

  • Operation results from all steps
  • Error messages and exceptions
  • Progress indicators

What output it produces:

  • Daily log file: logs/lemlist_brevo_YYYYMMDD.log
  • Console output with summary statistics
  • Detailed operation logs for debugging

Why this step is needed: Provides audit trail, debugging information, and operation history for troubleshooting and compliance.

Log Format:

[YYYY-MM-DD HH:MM:SS] Log message

Summary Output:

Import Summary:
- Dates: YYYY-MM-DD (yesterday) and YYYY-MM-DD (today)
- Total Contacts Fetched: XXX
- Successfully Imported: XXX
- Updated: XXX
- Failed: XXX
- Skipped (already exists): XXX

4. Workflow Logic Summary

The script executes the following sequence based on command-line arguments:

Default Execution (No Arguments):

  1. Lemlist to Brevo Import:

    • Fetches contacts from all Lemlist campaigns for today's date
    • Filters contacts by date if specified
    • Maps fields to Brevo attributes
    • Creates missing Brevo attributes
    • Checks for duplicates
    • Imports new contacts (max 500)
    • Logs results
  2. Brevo to Lemlist Import:

    • Fetches contacts from Brevo (yesterday and today)
    • Maps Brevo fields to Lemlist format
    • Checks for existing contacts in Lemlist
    • Creates or updates contacts in Lemlist campaign
    • Logs results

Export Mode (--export):

  1. Fetches contacts from Lemlist campaigns
  2. Applies date filtering if specified
  3. Exports to CSV file
  4. Returns file path
  1. Searches all Lemlist campaigns for email
  2. Returns contact details and campaign information
  3. Logs search results

Brevo to Lemlist Only (--brevo-to-lemlist):

  1. Fetches Brevo contacts (yesterday and today)
  2. Imports to Lemlist campaign
  3. Logs results

5. Configuration

Environment Variables (.env file)

Create a .env file in the script directory with the following variables:

# Required: API Credentials
LEMLIST_AUTH=your_lemlist_basic_auth_token
BREVO_API_KEY=your_brevo_api_key

# Optional: Campaign IDs (defaults provided)
LEMLIST_CAMPAIGN_ID=cam_izAb64Dp8ifZphk5F
LEMLIST_CAMPAIGN_ID_BREVO_TO_LEMLIST=cam_ieN79BAdD9w7ixzhF

# Optional: Logging
LOG_DIR=logs

API Credentials

Lemlist API:

  • Base URL: https://api.lemlist.com/api
  • Authentication: Basic Auth (token in LEMLIST_AUTH)
  • Format: Base64 encoded email:api_key

Brevo API:

Campaign Configuration

Default Campaigns:

  • LEMLIST_CAMPAIGN_ID: Used for Lemlist-to-Brevo imports (default: cam_izAb64Dp8ifZphk5F)
  • LEMLIST_CAMPAIGN_ID_BREVO_TO_LEMLIST: Used for Brevo-to-Lemlist imports (default: cam_ieN79BAdD9w7ixzhF)

6. Dependencies

External APIs

Lemlist API

  • Endpoint: https://api.lemlist.com/api
  • Purpose: Contact management and campaign data retrieval
  • Requires Auth: Yes (Basic Auth)
  • Rate Limits: Handled with retry logic (3 attempts, 5-second delay on 429)
  • Endpoints Used:
    • GET /campaigns - List all campaigns
    • GET /campaigns/{id}/export/leads - Export campaign leads
    • POST /campaigns/{id}/leads - Create/update contact

Brevo API

  • Endpoint: https://api.brevo.com/v3
  • Purpose: Email marketing and contact management
  • Requires Auth: Yes (API Key)
  • Rate Limits: Handled with pagination and retry logic
  • Endpoints Used:
    • GET /contacts - List contacts (with date filtering)
    • GET /contacts/{email} - Check if contact exists
    • GET /contacts/attributes - List existing attributes
    • POST /contacts/attributes/normal/{name} - Create custom attribute
    • POST /contacts - Create contact

Python Dependencies

Required Packages:

requests>=2.25.0  # HTTP API calls
csv # CSV file operations (built-in)
pathlib # File path handling (built-in)
datetime # Date/time operations (built-in)
urllib.parse # URL encoding (built-in)

Installation:

pip install requests

File System

Required Directories:

  • logs/ - Log file storage (created automatically)
  • /Users/fleetstudio-47/Documents/GitHub/AI-Hubspoot/ - CSV export directory (must exist or be writable)

7. Error Handling

API Error Handling

Retry Logic:

  • Maximum 3 attempts for failed requests
  • 5-second delay on rate limit (HTTP 429)
  • 2-second delay between retries for other errors
  • Logs errors to file for debugging

Common Error Scenarios:

  • 429 Too Many Requests: Automatic retry with delay
  • 404 Not Found: Handled gracefully (contact doesn't exist)
  • 400 Bad Request: Logged with error message
  • Network Errors: Retried with exponential backoff

Data Validation

Email Validation:

  • Checks for missing email addresses
  • Normalizes email to lowercase
  • Skips contacts without valid email

Phone Number Validation:

  • Validates length (10-15 digits)
  • Formats to international standard
  • Skips invalid phone numbers with logging

Date Parsing:

  • Handles multiple date formats:
    • ISO format: YYYY-MM-DD
    • European format: DD-MM-YYYY
    • ISO datetime: YYYY-MM-DDTHH:MM:SS
  • Gracefully handles parsing errors

Duplicate Handling

Brevo Import:

  • Checks if email exists before import
  • Skips existing contacts (logged as "skipped")
  • Prevents duplicate entries

Lemlist Import:

  • Searches all campaigns for existing email
  • Updates existing contacts instead of creating duplicates
  • Tracks created vs updated counts

8. Usage Examples

Example 1: Daily Synchronization

Scenario: Sync contacts added today between Lemlist and Brevo

# Run default sync (today's contacts)
python lemlist-brevo.py

Output:

  • Imports today's Lemlist contacts to Brevo
  • Imports yesterday and today's Brevo contacts to Lemlist
  • Logs results to daily log file

Example 2: Historical Import

Scenario: Import contacts from a specific date

# Import contacts from December 1, 2024
python lemlist-brevo.py 2024-12-01

# Or European date format
python lemlist-brevo.py 01-12-2024

Example 3: Export All Contacts

Scenario: Backup all Lemlist contacts to CSV

python lemlist-brevo.py --export --all

Output: lemlist_leads_all.csv in export directory

Example 4: Find Contact

Scenario: Search for a contact across all Lemlist campaigns

python lemlist-brevo.py --search john.doe@example.com

Output: Contact details and campaign information logged

Example 5: One-Way Import

Scenario: Only import from Brevo to Lemlist

python lemlist-brevo.py --brevo-to-lemlist

Output: Imports Brevo contacts (yesterday and today) to Lemlist

Example 6: Scheduled Execution

Scenario: Run as cron job for daily synchronization

# Add to crontab (runs daily at 2 AM)
0 2 * * * cd /path/to/script && python lemlist-brevo.py >> /var/log/lemlist-sync.log 2>&1

9. Logging

Log File Location

Default: logs/lemlist_brevo_YYYYMMDD.log

Example: logs/lemlist_brevo_20241205.log

Log Format

[YYYY-MM-DD HH:MM:SS] Log message

Log Levels

  • Info: Normal operations, progress updates
  • Error: API errors, validation failures
  • Debug: Detailed field information (when debug mode enabled)

Sample Log Output

[2024-12-05 10:30:15] ============================================================
[2024-12-05 10:30:15] Starting Lemlist to Brevo import for 2024-12-05
[2024-12-05 10:30:15] ============================================================
[2024-12-05 10:30:16] Step 1: Fetching contacts from Lemlist...
[2024-12-05 10:30:18] Found 25 contacts from Lemlist
[2024-12-05 10:30:18] Importing contacts to Brevo...
[2024-12-05 10:30:20] SUCCESS: Email john@example.com imported to Brevo
[2024-12-05 10:30:25]
Import Summary:
- Date: 2024-12-05
- Total Contacts Fetched: 25
- Successfully Imported: 20
- Failed: 2
- Skipped (already exists): 3
[2024-12-05 10:30:25] ============================================================

10. Limitations and Considerations

Rate Limits

  • Lemlist API: No documented rate limits, but script implements retry logic
  • Brevo API: Free tier: 300 requests/day, Paid tiers: Higher limits
  • Recommendation: Use date filtering to limit API calls

Import Limits

  • Maximum Import: 500 contacts per execution (configurable)
  • Reason: Prevents timeout and rate limit issues
  • Workaround: Run script multiple times with date ranges

Date Filtering

  • Lemlist: Client-side filtering (fetches all, then filters)
  • Brevo: API-level filtering (more efficient)
  • Impact: Large Lemlist campaigns may take longer to process

Attribute Creation

  • Brevo: Creates attributes as TEXT type only
  • Limitation: Cannot create date, number, or category attributes automatically
  • Workaround: Create complex attributes manually in Brevo dashboard

Phone Number Formatting

  • Default Country Code: +91 (India)
  • Limitation: Assumes Indian numbers for 10-digit inputs
  • Customization: Modify format_phone_number() function for other countries

CSV Export Path

  • Hardcoded Path: /Users/fleetstudio-47/Documents/GitHub/AI-Hubspoot/
  • Limitation: Not configurable via environment variable
  • Customization: Modify export_lemlist_contacts_to_csv() function

11. Troubleshooting

Common Issues

Issue: Missing API Credentials

ValueError: LEMLIST_AUTH is required in .env file

Solution: Ensure .env file exists with valid credentials

Issue: Import Fails Silently Check:

  • Log file for error messages
  • API key validity
  • Network connectivity
  • Rate limit status

Issue: Contacts Not Found Check:

  • Date format (use YYYY-MM-DD)
  • Campaign IDs are correct
  • Contacts exist in source platform

Issue: Attribute Creation Fails Check:

  • Attribute name follows Brevo rules (uppercase, alphanumeric)
  • Not a system-restricted field
  • Brevo API key has attribute creation permissions

Issue: Phone Numbers Not Imported Check:

  • Phone number format (must be valid)
  • Country code handling
  • Log file for "Invalid phone length" messages

Script Location

File Path: lemlist-brevo.py

Repository: https://github.com/TeamFleetStudio/Lemlist-Brevo

API Documentation

Lemlist API:

Brevo API: