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

# Sending Mass Messages

> Send a targeted mass message to a segment of your audience using lists and the messaging API.

Learn how to send messages to multiple users at once using smart lists and custom lists. This tutorial covers the two-step process: discovering your audience lists and sending targeted mass messages.

## Overview

Mass messaging allows you to send a single message to many users efficiently. Instead of sending individual messages, you can target entire audience segments defined by:

* **Smart Lists**: Pre-built audience segments (subscribers, followers, expired subscribers, etc.)
* **Custom Lists**: User-created lists for custom audience targeting

### The Two-Step Flow

```mermaid theme={null}
graph LR
    A[Step 1: Get Lists] --> B[Smart Lists]
    A --> C[Custom Lists]
    B --> D[Step 2: Send Mass Message]
    C --> D
    D --> E[Message Sent to Recipients]
```

**Step 1: Discover Available Lists**

* Fetch smart lists to see pre-built audience segments
* Fetch custom lists to see user-created segments
* Optionally fetch members to preview recipients

**Step 2: Send the Mass Message**

* Select lists to include (required)
* Optionally select lists to exclude
* Send with text, media, or pay-to-view content

<Note>
  #### Authentication Required

  Mass messaging requires OAuth with these scopes:

  * `write:chat` - Permission to send messages
  * `read:fan` - Permission to read fan/subscriber data

  See the [OAuth Tutorial](/docs/authentication/quick-start) for setup instructions.
</Note>

## Prerequisites

* OAuth authentication with required scopes (see note above)
* Basic familiarity with REST APIs
* Understanding of your Fanvue audience structure

## Step 1: Discovering Your Lists

Before sending a mass message, you need to know which lists are available. There are two types:

### Smart Lists (Pre-built Audiences)

Smart lists are automatically maintained audience segments based on user behavior and relationships.

#### Fetch All Smart Lists

```bash theme={null}
curl -X GET "https://api.fanvue.com/chats/lists/smart" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Fanvue-API-Version: 2025-06-26"
```

**Response:**

```json theme={null}
[
  {
    "uuid": "subscribers",
    "name": "Subscribers",
    "count": 450
  },
  {
    "uuid": "followers",
    "name": "Followers",
    "count": 1250
  },
  {
    "uuid": "expired_subscribers",
    "name": "Expired subscribers",
    "count": 180
  }
]
```

Each entry's `uuid` is one of the fixed identifiers below. The key is named `uuid` for backward
compatibility, but the value is a fixed identifier string, never a real UUID.

**Available Smart List identifiers:**

These are a fixed, predefined set of lowercase string identifiers — **not UUIDs**, despite the
`uuid` key above and the legacy `smartListUuids` field name used when sending a mass message:

* `subscribers` - Users with an active subscription
* `auto_renewing` - Subscribers whose subscription will auto-renew
* `non_renewing` - Subscribers who have turned off auto-renew
* `followers` - Users following you without an active subscription
* `free_trial_subscribers` - Subscribers currently on a free trial
* `expired_subscribers` - Users whose subscription has expired
* `spent_more_than_50` - Users who have spent more than \$50
* `muted` - Users you have muted
* `creators` - Creators you have a chat with

<Info>
  Smart list identifiers are lowercase with underscores and come from the fixed set listed above.
  They are **not** UUIDs — do not build a UUID validator for smart list identifiers. Only
  `customListUuids` accepts real UUIDs.
</Info>

#### Preview Smart List Members (Optional)

```bash theme={null}
curl -X GET "https://api.fanvue.com/chats/lists/smart/subscribers?page=1&size=20" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Fanvue-API-Version: 2025-06-26"
```

**Response:**

```json theme={null}
{
  "data": [
    {
      "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "displayName": "Sarah Miller",
      "handle": "sarahmiller",
      "isCreator": false
    }
  ],
  "pagination": {
    "page": 1,
    "size": 20,
    "hasMore": true
  }
}
```

### Custom Lists (User-created Audiences)

Custom lists are manually created segments for specific targeting.

#### Fetch All Custom Lists

```bash theme={null}
curl -X GET "https://api.fanvue.com/chats/lists/custom?page=1&size=20" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Fanvue-API-Version: 2025-06-26"
```

**Response:**

```json theme={null}
{
  "data": [
    {
      "uuid": "f1e2d3c4-b5a6-9788-c0d1-e2f3g4h5i6j7",
      "name": "VIP Supporters",
      "count": 42,
      "createdAt": "2025-09-15T10:30:00Z"
    },
    {
      "uuid": "a9b8c7d6-e5f4-3210-9876-fedcba098765",
      "name": "Content Feedback Group",
      "count": 28,
      "createdAt": "2025-08-20T14:15:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "size": 20,
    "hasMore": false
  }
}
```

#### Preview Custom List Members (Optional)

```bash theme={null}
curl -X GET "https://api.fanvue.com/chats/lists/custom/f1e2d3c4-b5a6-9788-c0d1-e2f3g4h5i6j7?page=1&size=20" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Fanvue-API-Version: 2025-06-26"
```

**Response structure is the same as smart list members.**

### TypeScript Example: Fetching Lists

```typescript theme={null}
interface SmartList {
  uuid: string;
  name: string;
  count: number;
}

interface CustomList {
  uuid: string;
  name: string;
  count: number;
  createdAt: string;
}

async function discoverLists(accessToken: string) {
  const headers = {
    Authorization: `Bearer ${accessToken}`,
    "X-Fanvue-API-Version": "2025-06-26",
  };

  // Fetch smart lists
  const smartListsRes = await fetch("https://api.fanvue.com/chats/lists/smart", {
    headers,
  });
  const smartLists: SmartList[] = await smartListsRes.json();

  // Fetch custom lists (first page)
  const customListsRes = await fetch("https://api.fanvue.com/chats/lists/custom?page=1&size=20", {
    headers,
  });
  const customListsData = await customListsRes.json();
  const customLists: CustomList[] = customListsData.data;

  return { smartLists, customLists };
}

// Usage
const { smartLists, customLists } = await discoverLists("YOUR_ACCESS_TOKEN");
console.log("Smart Lists:", smartLists);
console.log("Custom Lists:", customLists);
```

## Step 2: Sending Mass Messages

Once you know your available lists, you can send a mass message to one or more lists.

### Basic Mass Message Request

```bash theme={null}
curl -X POST "https://api.fanvue.com/chats/mass-messages" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Fanvue-API-Version: 2025-06-26" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Hey everyone! New content is now available on my page!",
    "includedLists": {
      "smartListIds": ["subscribers"]
    }
  }'
```

**Response:**

```json theme={null}
{
  "id": "f1e2d3c4-b5a6-9788-c0d1-e2f3g4h5i6j7",
  "recipientCount": 450,
  "createdAt": "2025-10-03T12:00:00Z"
}
```

### Request Schema

```typescript theme={null}
interface MassMessageRequest {
  text?: string; // Message text (optional if media provided)
  mediaUuids?: string[]; // Array of media UUIDs
  price?: number | null; // Price in cents for pay-to-view (requires media)
  includedLists: {
    smartListIds?: string[]; // Fixed smart-list identifier strings (NOT UUIDs) — see note below
    smartListUuids?: string[]; // Deprecated alias of smartListIds — still accepted
    customListUuids?: string[]; // Custom list UUIDs to include (real UUIDs)
  };
  excludedLists?: {
    // Optional: lists to exclude
    smartListIds?: string[]; // Fixed smart-list identifier strings (NOT UUIDs)
    smartListUuids?: string[]; // Deprecated alias of smartListIds — still accepted
    customListUuids?: string[]; // Custom list UUIDs to exclude (real UUIDs)
  };
}
```

<Warning>
  **Smart list values are fixed identifier strings, not UUIDs.** The field accepts a fixed,
  predefined set of lowercase string identifiers (the same field on `excludedLists` behaves
  identically). Do not generate, validate against, or expect UUID values here — sending a UUID
  will be rejected. Only `customListUuids` accepts real UUIDs (those returned by the custom-lists
  endpoint).

  The complete set of accepted smart list identifiers is:

  * `subscribers` — users with an active subscription
  * `auto_renewing` — subscribers whose subscription will auto-renew
  * `non_renewing` — subscribers who have turned off auto-renew
  * `followers` — users following you without an active subscription
  * `free_trial_subscribers` — subscribers currently on a free trial
  * `expired_subscribers` — users whose subscription has expired
  * `spent_more_than_50` — users who have spent more than \$50
  * `muted` — users you have muted
  * `creators` — creators you have a chat with

  These are the only valid values; any other string is rejected.
</Warning>

<Note>
  **`smartListIds` is the current field name.** It replaced `smartListUuids`, which was
  misleading because the values were never UUIDs. `smartListUuids` is retained as a deprecated
  alias and is still accepted on `includedLists` and `excludedLists`, so existing integrations
  keep working — but new code should send `smartListIds`. If both are present, `smartListIds`
  wins. Responses echo both fields for backward compatibility; `smartListUuids` will be removed
  in a future API version.
</Note>

**Validation Rules:**

* Must provide either `text` or `mediaUuids` (or both)
* At least one list must be included
* If `price` is set, `mediaUuids` must be provided
* Price must be in cents (e.g., 999 = \$9.99) with a minimum of 200 (i.e., \$2.00)
* Smart list identifiers are lowercase (e.g., `"subscribers"`, not `"SUBSCRIBERS"`)

## Practical Examples

### Example 1: Send to All Subscribers

```bash theme={null}
curl -X POST "https://api.fanvue.com/chats/mass-messages" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Fanvue-API-Version: 2025-06-26" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Thank you for being a subscriber! 💜",
    "includedLists": {
      "smartListIds": ["subscribers"]
    }
  }'
```

### Example 2: Send to Multiple Lists

Target both active and expired subscribers:

```bash theme={null}
curl -X POST "https://api.fanvue.com/chats/mass-messages" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Fanvue-API-Version: 2025-06-26" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Special offer: 50% off subscription renewal!",
    "includedLists": {
      "smartListIds": ["subscribers", "expired_subscribers"]
    }
  }'
```

### Example 3: Send to Custom List with Exclusions

Send to VIP list but exclude users who already received a similar message:

```bash theme={null}
curl -X POST "https://api.fanvue.com/chats/mass-messages" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Fanvue-API-Version: 2025-06-26" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Exclusive VIP content just for you!",
    "includedLists": {
      "customListUuids": ["f1e2d3c4-b5a6-9788-c0d1-e2f3g4h5i6j7"]
    },
    "excludedLists": {
      "customListUuids": ["a9b8c7d6-e5f4-3210-9876-fedcba098765"]
    }
  }'
```

### Example 4: Send Pay-to-View Message with Media

Send a message with media that requires payment to unlock:

```bash theme={null}
curl -X POST "https://api.fanvue.com/chats/mass-messages" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Fanvue-API-Version: 2025-06-26" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Exclusive content available now!",
    "mediaUuids": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"],
    "price": 999,
    "includedLists": {
      "smartListIds": ["subscribers"]
    }
  }'
```

<Info>
  Price is specified in cents. In this example, `999` represents $9.99. Minimum price is 200 cents
      ($2.00).
</Info>

### Example 5: Combine Smart and Custom Lists

```bash theme={null}
curl -X POST "https://api.fanvue.com/chats/mass-messages" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Fanvue-API-Version: 2025-06-26" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Big announcement coming soon!",
    "includedLists": {
      "smartListIds": ["subscribers"],
      "customListUuids": ["f1e2d3c4-b5a6-9788-c0d1-e2f3g4h5i6j7"]
    }
  }'
```

## Complete TypeScript Implementation

Here's a full example combining list discovery and mass messaging:

```typescript theme={null}
interface MassMessageRequest {
  text?: string;
  mediaUuids?: string[];
  price?: number | null; // Price in cents (e.g., 999 = $9.99)
  includedLists: {
    smartListIds?: string[];
    customListUuids?: string[];
  };
  excludedLists?: {
    smartListIds?: string[];
    customListUuids?: string[];
  };
}

interface MassMessageResponse {
  id: string;
  recipientCount: number;
  createdAt: string;
}

class MassMessagingService {
  private baseUrl = "https://api.fanvue.com";
  private accessToken: string;

  constructor(accessToken: string) {
    this.accessToken = accessToken;
  }

  private async request(endpoint: string, options: RequestInit = {}) {
    const response = await fetch(`${this.baseUrl}${endpoint}`, {
      ...options,
      headers: {
        Authorization: `Bearer ${this.accessToken}`,
        "X-Fanvue-API-Version": "2025-06-26",
        "Content-Type": "application/json",
        ...options.headers,
      },
    });

    if (!response.ok) {
      const error = await response.json().catch(() => ({}));
      throw new Error(error.message || `HTTP ${response.status}: ${response.statusText}`);
    }

    return response.json();
  }

  // Step 1: Get available lists
  async getSmartLists() {
    return this.request("/chats/lists/smart");
  }

  async getCustomLists(page = 1, size = 20) {
    return this.request(`/chats/lists/custom?page=${page}&size=${size}`);
  }

  async getSmartListMembers(listId: string, page = 1, size = 20) {
    return this.request(`/chats/lists/smart/${listId}?page=${page}&size=${size}`);
  }

  async getCustomListMembers(listUuid: string, page = 1, size = 20) {
    return this.request(`/chats/lists/custom/${listUuid}?page=${page}&size=${size}`);
  }

  // Step 2: Send mass message
  async sendMassMessage(request: MassMessageRequest): Promise<MassMessageResponse> {
    return this.request("/chats/mass-messages", {
      method: "POST",
      body: JSON.stringify(request),
    });
  }
}

// Example usage
async function main() {
  const service = new MassMessagingService("YOUR_ACCESS_TOKEN");

  try {
    // Step 1: Discover available lists
    console.log("📋 Fetching available lists...");
    const smartLists = await service.getSmartLists();
    const customListsData = await service.getCustomLists();

    console.log("Smart Lists:", smartLists);
    console.log("Custom Lists:", customListsData.data);

    // Step 2: Send mass message to subscribers
    console.log("\n📤 Sending mass message...");
    const result = await service.sendMassMessage({
      text: "Hello everyone! Thank you for your support!",
      includedLists: {
        smartListIds: ["subscribers"],
      },
    });

    console.log("✅ Mass message sent!");
    console.log(`   Message ID: ${result.id}`);
    console.log(`   Recipients: ${result.recipientCount}`);
    console.log(`   Sent at: ${result.createdAt}`);
  } catch (error) {
    console.error("❌ Error:", error);
  }
}
```

## Best Practices

### 1. Start Small

When testing, start with a small custom list or use the preview feature to understand your audience:

```typescript theme={null}
// Preview recipients before sending
const preview = await service.getSmartListMembers("subscribers", 1, 5);
console.log("First 5 recipients:", preview.data);

// Then send to the full list
const result = await service.sendMassMessage({
  text: "Test message",
  includedLists: { smartListIds: ["subscribers"] },
});
```

### 2. Handle Rate Limits

The mass messages endpoint has rate limits. Handle them gracefully:

```typescript theme={null}
async function sendWithRetry(service: MassMessagingService, request: MassMessageRequest) {
  let retries = 0;
  const maxRetries = 3;

  while (retries < maxRetries) {
    try {
      return await service.sendMassMessage(request);
    } catch (error: any) {
      if (error.message.includes("429") && retries < maxRetries - 1) {
        // Rate limited - wait and retry
        const waitTime = Math.pow(2, retries) * 1000; // Exponential backoff
        console.log(`Rate limited. Waiting ${waitTime}ms before retry...`);
        await new Promise((resolve) => setTimeout(resolve, waitTime));
        retries++;
      } else {
        throw error;
      }
    }
  }
}
```

### 3. Validate Before Sending

```typescript theme={null}
function validateMassMessageRequest(request: MassMessageRequest): string | null {
  // Must have text or media
  if (!request.text && !request.mediaUuids?.length) {
    return "Must provide either text or media";
  }

  // Must have at least one included list
  const hasIncludedLists =
    (request.includedLists.smartListIds?.length ?? 0) > 0 ||
    (request.includedLists.customListUuids?.length ?? 0) > 0;

  if (!hasIncludedLists) {
    return "Must include at least one list";
  }

  // Price requires media
  if (request.price && !request.mediaUuids?.length) {
    return "Pay-to-view messages require media";
  }

  return null; // Valid
}

// Usage
const error = validateMassMessageRequest(myRequest);
if (error) {
  console.error("Invalid request:", error);
  return;
}
```

### 4. Track Send History

Store mass message results for analytics:

```typescript theme={null}
interface SendRecord {
  messageId: string;
  recipientCount: number;
  sentAt: string;
  lists: string[];
  text: string;
}

async function sendAndTrack(
  service: MassMessagingService,
  request: MassMessageRequest
): Promise<SendRecord> {
  const result = await service.sendMassMessage(request);

  const record: SendRecord = {
    messageId: result.id,
    recipientCount: result.recipientCount,
    sentAt: result.createdAt,
    lists: [
      ...(request.includedLists.smartListIds || []),
      ...(request.includedLists.customListUuids || []),
    ],
    text: request.text || "(media only)",
  };

  // Save to your database or analytics service
  console.log("Send record:", record);

  return record;
}
```

## Troubleshooting

### "At least one list must be provided"

Ensure you're providing at least one list in `includedLists`:

```typescript theme={null}
// ❌ Wrong - empty lists
{
  includedLists: {
  }
}

// ✅ Correct - at least one list
{
  includedLists: {
    smartListIds: ["subscribers"];
  }
}
```

### "Smart list not found"

Smart list identifiers must be lowercase:

```typescript theme={null}
// ❌ Wrong - uppercase
{
  smartListIds: ["SUBSCRIBERS"];
}

// ✅ Correct - lowercase
{
  smartListIds: ["subscribers"];
}
```

### 403 Forbidden Error

Verify you have the required OAuth scopes:

* `write:chat`
* `read:fan`

Check your OAuth configuration includes both scopes.

### Empty recipientCount

If `recipientCount` is 0, the lists might be empty or exclusions removed all recipients:

```typescript theme={null}
// Check list counts first
const smartLists = await service.getSmartLists();
const subscriberList = smartLists.find((l) => l.uuid === "subscribers");
console.log("Subscribers count:", subscriberList?.count);
```

## Additional Resources

* [OAuth Authentication](/docs/authentication/quick-start) - Set up OAuth with required scopes
* [API Reference: Send Mass Message](/docs/api-reference/overview) - Full endpoint documentation
* [API Reference: Chat Lists](/docs/api-reference/overview) - List endpoints reference
* [Rate Limits](/docs/authentication/rate-limits) - Understanding API rate limits
