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

# Subscribers

> Understanding subscriber management, lifecycle, and attributes

# Subscribers

Subscribers are the core of Sequenzy. They represent contacts in your email marketing database—people who receive your campaigns, transactional emails, and automated sequences.

## Subscriber Data Model

Each subscriber has the following properties:

| Property           | Type      | Description                                         |
| ------------------ | --------- | --------------------------------------------------- |
| `email`            | string    | Unique email address (required)                     |
| `firstName`        | string    | First name                                          |
| `lastName`         | string    | Last name                                           |
| `status`           | enum      | Current status: `active`, `unsubscribed`, `bounced` |
| `tags`             | string\[] | Array of tag names attached to subscriber           |
| `customAttributes` | object    | Flexible key-value store for custom data            |
| `createdAt`        | datetime  | When the subscriber was created                     |
| `updatedAt`        | datetime  | When the subscriber was last modified               |

## Subscriber Status

Subscribers can have one of three statuses:

### Active

The subscriber can receive emails. This is the default status when a subscriber is created.

```json theme={null}
{ "status": "active" }
```

### Unsubscribed

The subscriber has opted out. They will not receive campaigns or sequences, but may still receive transactional emails if required.

```json theme={null}
{ "status": "unsubscribed" }
```

### Bounced

The email address is invalid or unreachable. Sequenzy automatically updates this status when emails bounce.

```json theme={null}
{ "status": "bounced" }
```

<Warning>
  Never send marketing emails to unsubscribed or bounced subscribers. Sequenzy
  automatically filters them from campaigns.
</Warning>

## Double Opt-In

Double opt-in lets you require email confirmation before new signups become active subscribers. While confirmation is pending, Sequenzy stores the contact as `unsubscribed`, holds target list membership and signup tags, and sends a confirmation email. After the subscriber clicks the confirmation link, Sequenzy activates the subscriber, applies the held lists and tags, and triggers matching automations.

<Card title="Double Opt-In" icon="mail" href="/concepts/double-opt-in">
  Learn how to enable confirmation emails for subscriber signups.
</Card>

## Custom Attributes

Custom attributes allow you to store any data about subscribers. Use them for:

* User IDs from your application
* Subscription plans or tiers
* Geographic information
* Purchase history
* Preferences
* Dates and timestamps like signup time, renewal date, or latest checkout attempt

```json theme={null}
{
  "customAttributes": {
    "userId": "usr_12345",
    "plan": "pro",
    "company": "Acme Inc",
    "industry": "technology",
    "mrr": 99.99,
    "signupSource": "google-ads"
  }
}
```

### Using Attributes in Emails

Custom attributes are available as template variables:

```html theme={null}
<p>Hi {{FIRST_NAME}},</p>
<p>Your current plan: {{plan}}</p>
<p>Company: {{company}}</p>
```

### Using Attributes in Conditions

Filter campaigns and create sequence conditions based on attributes:

* `field_equals`: Check exact match
* `field_contains`: Check if value contains string
* `field_greater_than`: Numeric comparison
* `field_less_than`: Numeric comparison

Date and datetime attributes also work in these comparisons. If you send ISO
date values like `2026-03-20` or full timestamps like
`2026-03-19T10:56:03+00:00`, Sequenzy stores them in a way that supports exact
matches and before/after-style range filters.

### Commerce Attributes

Commerce integrations can sync revenue and order metrics onto subscriber profiles.

| Attribute     | Type   | Description                                      |
| ------------- | ------ | ------------------------------------------------ |
| `mrr`         | Number | Monthly recurring revenue from billing providers |
| `ltv`         | Number | Lifetime value                                   |
| `aov`         | Number | Average order value                              |
| `ordersCount` | Number | Total known order count                          |
| `totalOrders` | Number | Alias for total known order count                |
| `totalSpent`  | Number | Total known store spend                          |

The subscriber table shows revenue metrics directly, including `AOV` when either `aov` or legacy `averageOrderValue` is present.

<Note>
  Older commerce data may include `totalRevenue` or `averageOrderValue`.
  Sequenzy treats `totalRevenue` as an `ltv` fallback and `averageOrderValue` as
  an `aov` fallback so subscriber views do not show duplicate cards for the same
  metric.
</Note>

### Purchased Products

Subscriber profiles show purchased products when Sequenzy has product-level purchase data.

* Stripe purchased products come from Stripe payment history and product catalog data
* Shopify and WooCommerce purchased products come from `ecommerce.order_placed` line items
* Product cards can show image, name, provider, variant, quantity, price, and last purchase time when those fields are available

For Shopify and WooCommerce, make sure order events include `lineItems[].providerProductId`. Variant-level display and matching also uses `lineItems[].providerVariantId`.

## Tags

Tags are labels that categorize subscribers. They're essential for:

1. **Segmentation** - Target campaigns to specific groups
2. **Automation Triggers** - Start sequences when tags are added
3. **Filtering** - Find subscribers with specific characteristics

<Card title="Learn More About Tags" icon="tags" href="/concepts/tags">
  Deep dive into tag management and best practices
</Card>

## Lists And Visibility

Subscriber lists can be either public or private.

* Public lists appear on the hosted subscriber email preferences/unsubscribe page. Their names and descriptions are subscriber-facing, and subscribers can subscribe or unsubscribe from them individually.
* Private lists are omitted entirely from that hosted page and cannot be subscribed to or unsubscribed from individually there. They remain available in Sequenzy for targeting, organization, imports, and automation logic.

New lists are public by default. When you create a public list, name it as a subscriber-facing category such as `Newsletter` or `Product Updates`. Internal cohort names and operational buckets are usually a better fit for private lists.

If every list is private (or a company has no lists yet), the hosted page shows a single Unsubscribe button for the global opt-out instead of per-list toggles, and an already-unsubscribed visitor sees a Resubscribe option there.

List privacy controls visibility in the preference center; it does not override a subscriber's global unsubscribe. Choosing to unsubscribe from all email still unsubscribes the subscriber from every active list membership, including private lists.

## Subscriber Lifecycle

```
                    ┌─────────────────┐
                    │     Created     │
                    │   (via API or   │
                    │    dashboard)   │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
         ┌─────────│     Active      │──────────┐
         │         └────────┬────────┘          │
         │                  │                   │
    Unsubscribe        Hard Bounce         Sequences
         │                  │              Campaigns
         ▼                  ▼             Transactional
┌─────────────────┐ ┌─────────────────┐         │
│  Unsubscribed   │ │     Bounced     │         │
└─────────────────┘ └─────────────────┘         │
         │                  │                   │
         └──────────────────┼───────────────────┘
                            │
                            ▼
                   ┌─────────────────┐
                   │    Tracking     │
                   │ (opens, clicks) │
                   └─────────────────┘
```

## Creating Subscribers

### Via API

```bash theme={null}
curl -X POST "https://api.sequenzy.com/api/v1/subscribers" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "firstName": "Jane",
    "lastName": "Smith",
    "tags": ["lead", "saas.monthly"],
    "enrollInSequences": true
  }'
```

### Auto-Creation

Subscribers are automatically created when you:

* Add a tag to a non-existent email
* Trigger an event for a non-existent email
* Send a transactional email to a non-existent email
* Capture a native Webflow form submission through the
  [Webflow integration](/integrations/webflow)

This makes integration easier—you don't need to create subscribers before interacting with them.

## Updating Subscribers

Use PATCH to update subscriber data:

```bash theme={null}
curl -X PATCH "https://api.sequenzy.com/api/v1/subscribers/user@example.com" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "firstName": "Janet",
    "customAttributes": {
      "plan": "pro",
      "upgradedAt": "2024-01-15"
    }
  }'
```

<Note>
  By default, `customAttributes` replaces the existing public custom-attribute
  map. Add `"customAttributesStrategy": "merge"` to the PATCH body to retain
  unspecified existing keys while overwriting the keys you send.
</Note>

## Filtering Subscribers

When sending campaigns, you can filter subscribers by:

| Filter         | Operators                                  | Example                                            |
| -------------- | ------------------------------------------ | -------------------------------------------------- |
| Status         | `is`, `is_not`                             | Status is "active"                                 |
| Tags           | `contains`, `not_contains`                 | Tags contains "customer"                           |
| Email          | `contains`, `not_contains`, `is`, `is_not` | Email is "[qa@company.com](mailto:qa@company.com)" |
| Email Provider | `is`, `is_not`                             | Email Provider is `Google`                         |
| Date Added     | `less_than`, `more_than`                   | Added after `2026-03-01`                           |
| Name           | `contains`, `not_contains`                 | First name contains "John"                         |

Email `is` and `is_not` match the full address and ignore case, so use them for
explicit allowlists and exclusions. Use `contains` for domain or substring
matches like `@company.com`.

For date filters, you can use:

* Rolling relative values like `7 days ago`, `last week`, or `3 days from now`
* Fixed dates like `2026-03-01` or `Mar 1 2026`

`less_than` means "within/after this point" and `more_than` means "before/older than this point". Rolling values update automatically as time passes. Fixed dates stay fixed.

If a custom attribute stores a date or timestamp, you can use the same values in attribute comparisons such as `renewal_date < 3 days from now`.

Email provider detection runs in the background. We normalize major providers into stable buckets such as `Google`, `Microsoft`, and `iCloud`, so Gmail and Google Workspace filter together, Outlook/Hotmail/Microsoft 365 filter together, and dozens of regional providers like UKR.NET, Seznam, Onet, GMX, and Telenet can also be filtered directly.

### Engagement Filters

Engagement filters (`emailSent`, `emailOpened`, `emailClicked`, `emailBounced`, `emailComplained`) accept three kinds of value:

* A rolling time window: `7d`, `30d`, `90d`, `180d`, or `all`
* A specific sent campaign: `campaign:<campaign_id>`
* A count with a time window: `count:timeRange` (only with the `at_least` / `less_than_count` operators)

Count values let you segment by how many times someone opened or clicked, not just whether they did. For example:

* "Best clickers - clicked 10 or more times, ever":

```text theme={null}
emailClicked at_least 10:all
```

* "Suspicious openers - opened 10+ emails but never clicked (likely bot or spamtrap opens)":

```text theme={null}
emailOpened at_least 10:all
emailClicked is_not all
```

* "Fading subscribers - fewer than 2 opens in the last 90 days":

```text theme={null}
emailOpened less_than_count 2:90d
```

Count values cannot be combined with campaign-specific values - a `campaign:<campaign_id>` value always checks presence, not a count.

Combining two engagement filters with `AND` lets you target narrow audiences that rolling windows can't express. For example:

* "Subscribers who bounced the launch announcement but did not bounce the follow-up":

```text theme={null}
emailBounced is campaign:cmp_launch
emailBounced is_not campaign:cmp_follow_up
```

* "Subscribers who opened the March newsletter and then clicked the April promo":

```text theme={null}
emailOpened is campaign:cmp_march
emailClicked is campaign:cmp_april
```

Campaign-specific filters are evaluated across all time, so they do not interact with the 7/30/90/180 day windows. To combine "bounced this specific campaign" with "hasn't bounced anything in the last 30 days", use two separate filters.

In the dashboard value picker, rolling windows and sent campaigns now appear in separate sections so you can scroll straight to the campaign you want. The AI filter builder also understands natural phrasing like "bounced the welcome email but not the onboarding email" and resolves the names against your sent campaigns.

### Saved Segment Logic And Counts

Saved segments can combine filters with either `AND` or `OR` logic at every group level:

* Use `AND` when every filter must match
* Use `OR` when any one filter should match

You can also nest groups for expressions such as `last_login within 90 days AND (plan_end is empty OR plan_end is before today)`. In the API, nested segments use the v2 `root` shape with `kind: "group"` and `children` containing filter leaves or more groups. Existing flat `filters` plus `filterJoinOperator` requests still work.

Saved segments support two additional segment-only fields:

* `event` - filters by custom subscriber events. Use values like `saas.purchase:30d`, `saas.purchase:all`, or `saas.purchase:5:30d` with `at_least` / `less_than_count`.
* `segment` - references another saved segment by ID, so you can compose one segment from another. Sequenzy protects evaluation from cycles and excessive nesting depth.

In the dashboard, Sequenzy may show two different counts for a saved segment:

* **Matched contacts** - every contact that matches the segment definition, regardless of status
* **Active subscribers** - matched contacts who are still eligible for campaigns

Campaigns only send to active subscribers, so campaign recipient previews use the active count rather than the broader matched-contact total.

### Date Filter Examples

**Rolling relative examples**

```text theme={null}
Added less_than 30 days ago
Added more_than 90 days ago
Added less_than 7 days ago
```

**Fixed date examples**

```text theme={null}
Added less_than 2026-03-01
Added more_than Mar 1 2026
```

**Custom attribute date examples**

```text theme={null}
trial_ends_at lte 3 days from now
renewal_date gt 2026-04-01
last_seen_at gte Mar 15 2026
```

## Best Practices

### 1. Capture Data at Sign-up

Collect custom attributes when users first sign up:

```javascript theme={null}
await sequenzy.subscribers.create({
  email: user.email,
  firstName: user.firstName,
  customAttributes: {
    userId: user.id,
    signupDate: new Date().toISOString(),
    source: utm_source,
    medium: utm_medium,
    campaign: utm_campaign,
  },
  enrollInSequences: true,
});
```

### 2. Keep Attributes Updated

Sync changes from your app to Sequenzy:

```javascript theme={null}
// When user updates profile
await sequenzy.subscribers.update(user.email, {
  firstName: newFirstName,
  customAttributes: {
    timezone: newTimezone,
    language: newLanguage,
  },
});
```

### 3. Use Tags for Segments

Tags are more flexible than custom attributes for segmentation:

```javascript theme={null}
// Good: Use tags for states
await sequenzy.subscribers.addTag(email, "customer");

// Avoid: Using boolean attributes for states
await sequenzy.subscribers.update(email, {
  customAttributes: { isCustomer: true }, // Less flexible
});
```

### 4. Clean Your List

Regularly remove bounced and unengaged subscribers:

* Remove hard bounces immediately
* Consider removing subscribers who haven't opened in 6+ months
* Use re-engagement campaigns before removing inactive subscribers

## Related

<CardGroup cols={2}>
  <Card title="Tags" icon="tags" href="/concepts/tags">
    Learn about tagging and segmentation
  </Card>

  <Card title="Events" icon="bolt" href="/concepts/events">
    Track subscriber actions with events
  </Card>

  <Card title="Sequences" icon="diagram-project" href="/concepts/sequences">
    Automate emails based on subscriber data
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/subscribers/create">
    Subscribers API documentation
  </Card>
</CardGroup>
