---
"@context": https://schema.org
"@type": TechArticle
"@id": https://www.twilio.com/docs/cohorts/bulk-messaging-and-email#article
headline: Send personalized bulk messages and emails with Cohorts
description: Send personalized SMS or email to a Twilio Cohort. Resolve profile traits at send time with CEL and reference them in content with Liquid.
url: https://www.twilio.com/docs/cohorts/bulk-messaging-and-email
inLanguage: en
dateModified: 2026-10-21T13:26:26.000Z
author:
  "@type": Organization
  name: Twilio Developer Education Team
publisher:
  "@type": Organization
  name: Twilio
---

# Send personalized bulk messages and emails with Cohorts

Twilio Cohorts lets you send bulk messages across SMS, RCS, WhatsApp, and email without maintaining a static recipient list. When you target a cohort, Twilio resolves each recipient's profile traits at send time. Every message can include dynamic values, such as a first name, net spend, or plan tier.

Use this guide to plan, build, and monitor a personalized cohort-based send.

## Prerequisites

Before you begin, confirm you have the following:

* A Twilio account with access to Twilio Cohorts, Bulk Messaging, and Email.
* Your API key and secret, available from the Twilio Console.
* A connected profile store containing the traits you want to personalize on. The traits referenced in your personalization expressions must exist on the profiles that match your cohort criteria.
* Familiarity with cohort filter criteria. If you're new to Cohorts, start with [Getting started with Twilio Cohorts](/docs/cohorts/getting-started).

## Step 1: Define the audience with a cohort

Create a cohort whose filter criteria describes the population you want to reach. For personalization to work, the criteria should reference traits and address fields that exist on the profiles you expect to match.

The following criteria targets active enterprise customers in the US:

```txt
profile.trait.Account.status == "active" && profile.trait.Account.plan == "enterprise" && profile.trait.Account.country == "US"
```

Save the cohort so you can reference it by `cohortId` at send time. For the full request payload, see [Cohorts](/docs/cohorts/concepts/cohorts).

## Step 2: Validate before you send

Preview a sample of matching profiles to confirm the filter captures the population you expect. Project the same fields you plan to reference in your personalization so you can spot missing or malformed values before they end up in a customer message.

For the exact request, see [Validate cohort criteria with snapshots](/docs/cohorts/validating-cohort-criteria).

## Step 3: Send with runtime personalization

Reference the cohort by `cohortId` in your Bulk Messaging or Email request. Define each personalization value under `variables` as a Common Expression Language (CEL) expression wrapped in `${…}`. Reference the same variable in the message content with Liquid `{{ … }}` syntax. For Liquid syntax, filters, and control flow, see [Personalize message content](/docs/bulk-messaging/personalization).

### Bulk Messaging (SMS) example

```bash
curl -X POST 'https://comms.twilio.com/preview/Messages' \
-H 'Content-Type: application/json' \
-d '{
    "from": {
        "address": "<Your Purchased Twilio Phone Number>",
        "channel": "SMS"
    },
    "to": [
        {
            "cohortId": "cmp_cohort_01h9d8r0vte3hz8tykdj329t7r",
            "variables": {
                "firstName": "${profile.trait.Contact.firstName}",
                "netSpend": "${profile.trait.Metrics.revenue - profile.trait.Metrics.refunds}",
                "planTier": "${profile.trait.Account.plan}"
            }
        }
    ],
    "content": {
        "text": "Hi {{ firstName | default: '\''there'\'' }}, thanks for being on our {{ planTier }} plan. Your net spend with us is {{ netSpend }}."
    }
}' \
-u $TWILIO_API_KEY:$TWILIO_API_SECRET
```

### Email example

```bash
curl -X POST 'https://comms.twilio.com/preview/Emails' \
-H 'Content-Type: application/json' \
-d '{
    "from": {
        "address": "support@example.com",
        "name": "Support Team"
    },
    "to": [
        {
            "cohortId": "cmp_cohort_01h9d8r0vte3hz8tykdj329t7r",
            "variables": {
                "firstName": "${profile.trait.Contact.firstName}",
                "planTier": "${profile.trait.Account.plan}"
            }
        }
    ],
    "content": {
        "subject": "Hi {{ firstName | default: '\''there'\'' }}, an update on your {{ planTier }} plan",
        "html": "<p>Hi {{ firstName | default: '\''there'\'' }}, an update on your {{ planTier }} plan is available.</p>",
        "text": "Hi {{ firstName | default: '\''there'\'' }}, an update on your {{ planTier }} plan is available."
    }
}' \
-u $TWILIO_API_KEY:$TWILIO_API_SECRET
```

Twilio evaluates each expression against the matching profile at send time and substitutes the resulting values into the message content. To format numeric values, such as a currency amount or a rounded decimal, apply Liquid filters in the message content. For limits on personalization expressions and the requirements for the `to` array, see [Limits and validation](/docs/bulk-messaging/cohorts#limits-and-validation).

## Handle missing traits

A missing trait and an empty trait behave differently, so protect against both.

If a profile doesn't have the trait key at all, the send fails for that recipient, and the failure appears in the Operation. The Liquid `default` filter doesn't cover this case. To exclude those profiles from the audience, add a presence check to the cohort criteria:

```txt
has(profile.trait.Contact.firstName) && profile.trait.Account.status == "active"
```

For more information, see [Null handling in CEL](/docs/cohorts/cel/null-handling).

If the trait key exists but has no value, the `default` filter supplies a fallback:

```txt
Hi {{ firstName | default: 'there' }}
```

## Lock the recipient list before sending

If you need to review or audit the exact recipient list before sending, use a snapshot instead of the cohort ID. For example, you might do this for a regulated campaign, an approval workflow, or a scheduled send that must not shift with data changes. Generate the snapshot, review it, and then pass its `cohortSnapshotId` in your send request. Personalization variables still resolve against the profile data captured in the snapshot.

## Next steps

* [Get started with Twilio Cohorts](/docs/cohorts/getting-started)
* [Validate cohort criteria with snapshots](/docs/cohorts/validating-cohort-criteria)
* Review the [Operations and Message Tracking](/docs/bulk-messaging/operations-and-message-tracking) guide
