# Send email to a segment

Send emails to contacts matching segment criteria using dynamic segmentation or saved segments.

Features:
- Send to saved segments (by segment_id)
- Send with inline segment filters (dynamic targeting)
- NEW: Exclude unengaged contacts (improve deliverability & engagement metrics)
- Account-level merge fields (same for all recipients)
- Contact-level personalization (name, email, custom properties)
- Asynchronous processing (returns immediately, processes in background)

Unengaged Filtering (NEW):
Automatically exclude contacts who haven't opened or clicked any email in the last N days.
This improves sender reputation, reduces costs, and increases engagement rates.
- Set exclude_unengaged: true and unengaged_days: 90 (recommended)
- Contacts with NO email activity in the lookback period are excluded
- Perfect for newsletters, promotions, and regular campaigns
- Disable for transactional emails (password resets, receipts, etc.)

Merge Fields:
- Account-level: account_merge_fields - same value for all recipients (e.g., company_name, promo_code)
- Contact-level: Automatically available ({{name}}, {{email}}, {{first_name}}, {{last_name}}, plus all custom properties)

Segmentation Options:
- Option 1: Use existing segment → Provide segment_id
- Option 2: Dynamic filters → Provide segment_filters object with filter conditions

Segment Filter Structure:
json
{
  "match_type": "all",  // "all" (AND) or "any" (OR)
  "filter_conditions": [
    {
      "property": "status",
      "operator": "equals",
      "value": "subscribed"
    },
    {
      "property": "tags",
      "operator": "contains",
      "value": "vip"
    }
  ]
}


Available Operators:
- equals, not_equals, contains, not_contains
- greater_than, less_than, greater_than_or_equal, less_than_or_equal
- is_empty, is_not_empty, is_true, is_false

Limits:
- Max 10 filter groups
- Max 20 filters per group
- Max 100 total filters

Use Cases:
- Newsletter to all subscribed contacts
- Promotional email to contacts tagged "vip"
- Re-engagement campaign for inactive subscribers
- Product updates to contacts with specific custom properties

Endpoint: POST /api/v1/send-segment
Version: 1.0.0
Security: ApiKeyAuth

## Request fields (application/json):

  - `campaign_id` (string)
    Optional campaign identifier for grouping related emails and tracking bounce rates.

Auto-pause protection: If bounce rate exceeds thresholds, the campaign will be automatically paused to protect IP reputation.

How Campaign Grouping Works:
- All emails with the same campaign_id group into ONE campaign session
- Bounce rates are calculated across the entire group
- Auto-pause applies to ALL future sends using this campaign_id
- Campaign reports aggregate all metrics together

⚠️ CRITICAL: Each campaign MUST use a UNIQUE campaign_id

Best Practices:
- ✅ DO: Use unique IDs per campaign send
  - Good: "monthly-newsletter-2026-03"
  - Good: "vip-flash-sale-2026-week12"
  - Good: "product-update-v3-2026-03-05"
- ❌ DON'T: Reuse campaign_id across different campaigns
  - Bad: "newsletter" (reused every month)
  - Bad: "segment-send" (too generic)
  - Bad: "promo" (reused for all promos)

Why This Matters:
If you reuse a campaign_id:
- Bounce rates from OLD campaigns affect NEW campaigns
- New campaign might be immediately paused due to old bounces
- Campaign reports mix data from different time periods
- Cannot track individual campaign performance

Recommended Format:
{campaign-name}-{date} or {campaign-name}-{version}-{date}

Examples:
- Monthly newsletters: "newsletter-2026-03", "newsletter-2026-04"
- VIP promotions: "vip-sale-2026-w1", "vip-sale-2026-w2"
- Segment campaigns: "re-engagement-inactive-2026-q1"

If not provided, system auto-generates: segment-{segment_id}-{timestamp}
    Example: "monthly-newsletter-2026-march"

  - `segment_id` (string)
    ID of saved segment (use this OR segment_filters, not both)
    Example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"

  - `segment_filters` (object)
    Inline segment filters (use this OR segment_id, not both)

  - `segment_filters.match_type` (string)
    all=AND logic, any=OR logic
    Enum: "all", "any"

  - `segment_filters.filter_conditions` (array)

  - `segment_filters.filter_conditions.property` (string)
    Example: "status"

  - `segment_filters.filter_conditions.operator` (string)
    Enum: "equals", "not_equals", "contains", "not_contains", "greater_than", "less_than", "greater_than_or_equal", "less_than_or_equal", "is_empty", "is_not_empty", "is_true", "is_false"

  - `segment_filters.filter_conditions.value` (any)
    Value to compare (type depends on property)
    Example: "subscribed"

  - `subject` (string, required)
    Email subject with merge field support.

Important: If using personalization, also provide subject_template for proper campaign tracking and bounce grouping.
    Example: "Hello {{first_name}}!"

  - `subject_template` (string)
    Recommended when using personalized subjects. The template before variable substitution.

Why needed: Helps system track campaigns correctly when subjects are personalized. Without this, emails with different personalized subjects (e.g., "Hello John" vs "Hello Mary") are treated as separate campaigns, breaking bounce rate tracking.

Campaign Grouping:
- ✅ With subject_template: All emails group into ONE campaign
- ❌ Without: Each personalized subject creates a separate campaign

Example:
- subject_template: "Hello {{first_name}}! Special offer inside"
- Actual subject for John: "Hello John! Special offer inside"
- Actual subject for Mary: "Hello Mary! Special offer inside"
- Result: Both emails tracked together under the same campaign

If not provided, system normalizes the subject automatically (less accurate).
    Example: "Hello {{first_name}}! Special offer inside"

  - `preview_text` (string)
    Preview text shown in email clients (40-130 chars recommended)
    Example: "Exclusive offer just for you"

  - `html` (string, required)
    HTML email body with merge field support
    Example: "<h1>Hi {{name}}</h1><p>Use code {{promo_code}}</p>"

  - `text` (string)
    Plain text email body with merge field support. Supports the same {{variable}} syntax as html.

Auto-generation: If omitted, a plain-text version is automatically generated from
html before sending. Supplying your own text is recommended for best plain-text
client rendering.
    Example: "Hi {{name}}, Use code {{promo_code}}"

  - `from` (string, required)
    From email address (must use verified domain)
    Example: "hello@mail.yourdomain.com"

  - `from_name` (string)
    Display name for sender
    Example: "Your Company"

  - `track_opens` (boolean)
    Enable open tracking

  - `track_clicks` (boolean)
    Enable click tracking

  - `tag` (string)
    Tag for organizing/filtering this campaign
    Example: "newsletter-2025-02"

  - `stream_type` (string)
    Stream type (locked to broadcast for segment sending)
    Enum: "broadcast"

  - `account_merge_fields` (object)
    Account-level merge fields (same value for all recipients)
    Example: {"promo_code":"SAVE20","company_name":"Acme Corp","unsubscribe_url":"https://example.com/unsubscribe"}

  - `exclude_unengaged` (boolean)
    NEW: Exclude unengaged contacts from this campaign

An unengaged contact is one who hasn't opened or clicked ANY email
in the last N days (specified by unengaged_days parameter).

Benefits:
- Improves sender reputation by avoiding inactive contacts
- Reduces costs by not sending to non-responders
- Increases campaign engagement metrics (higher open/click rates)

Best Practices:
- Use 90 days for regular newsletters (recommended)
- Use 30 days for time-sensitive promotions
- Use 180+ days for re-engagement campaigns
- Disable (false) for transactional emails

Example Use Cases:
- Monthly newsletter → exclude_unengaged: true, unengaged_days: 90
- Flash sale → exclude_unengaged: true, unengaged_days: 30
- Win-back campaign → exclude_unengaged: false (send to all)
- Password reset → exclude_unengaged: false (transactional)
    Example: true

  - `unengaged_days` (integer)
    Number of days to look back when determining engagement

Only used if exclude_unengaged is true.

A contact is considered "engaged" if they opened or clicked
any email from your account in the last N days.

Recommended values:
- 30 days: Aggressive filtering, very active users only
- 60 days: Moderately active users
- 90 days: Standard (recommended for most campaigns)
- 180 days: Conservative, includes less frequent engagers
- 365 days: Very conservative, annual engagement check

Impact on List Size:
Typical engagement rates:
- 30 days: ~40-60% of list (most aggressive)
- 90 days: ~60-80% of list (balanced)
- 180 days: ~70-90% of list (conservative)
    Enum: 30, 60, 90, 180, 365

## Response 202 fields (application/json):

  - `success` (boolean)
    Example: true

  - `message` (string)
    Example: "Segment email queued for delivery"

  - `job_id` (string)
    Background job ID for tracking
    Example: "f1e2d3c4-b5a6-7890-cdef-123456789abc"

  - `status` (string)
    Example: "processing"

## Response 400 fields (application/json):

  - `success` (boolean)

  - `error` (string)

  - `details` (array)
    Validation error details (if applicable)

  - `limits` (object)
    Complexity limits (if exceeded)

  - `limits.MAX_GROUPS` (integer)
    Example: 10

  - `limits.MAX_FILTERS_PER_GROUP` (integer)
    Example: 20

  - `limits.MAX_TOTAL_FILTERS` (integer)
    Example: 100

## Response 401 fields (application/json):

  - `error` (string)
    Error code or message

## Response 404 fields (application/json):

  - `success` (boolean)

  - `error` (string)
    Example: "Segment not found"


