Sending Mass Messages

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 (all fans, subscribers, top spenders, etc.)
  • Custom Lists: User-created lists for custom audience targeting

The Two-Step Flow

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

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 for setup instructions.

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

$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:

1[
2 {
3 "uuid": "all_fans",
4 "name": "All Fans",
5 "count": 1250
6 },
7 {
8 "uuid": "subscribers",
9 "name": "Active Subscribers",
10 "count": 450
11 },
12 {
13 "uuid": "expired_subscribers",
14 "name": "Expired Subscribers",
15 "count": 180
16 }
17]

Common Smart Lists:

  • all_fans - Everyone who follows you
  • subscribers - Users with active subscriptions
  • expired_subscribers - Users whose subscriptions expired
  • top_spenders - Your highest spending fans

Smart list identifiers are lowercase with underscores. The actual values available depend on your account configuration.

Preview Smart List Members (Optional)

$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:

1{
2 "data": [
3 {
4 "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
5 "displayName": "Sarah Miller",
6 "handle": "sarahmiller",
7 "isCreator": false
8 }
9 ],
10 "pagination": {
11 "page": 1,
12 "size": 20,
13 "hasMore": true
14 }
15}

Custom Lists (User-created Audiences)

Custom lists are manually created segments for specific targeting.

Fetch All Custom Lists

$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:

1{
2 "data": [
3 {
4 "uuid": "f1e2d3c4-b5a6-9788-c0d1-e2f3g4h5i6j7",
5 "name": "VIP Supporters",
6 "count": 42,
7 "createdAt": "2025-09-15T10:30:00Z"
8 },
9 {
10 "uuid": "a9b8c7d6-e5f4-3210-9876-fedcba098765",
11 "name": "Content Feedback Group",
12 "count": 28,
13 "createdAt": "2025-08-20T14:15:00Z"
14 }
15 ],
16 "pagination": {
17 "page": 1,
18 "size": 20,
19 "hasMore": false
20 }
21}

Preview Custom List Members (Optional)

$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

1interface SmartList {
2 uuid: string;
3 name: string;
4 count: number;
5}
6
7interface CustomList {
8 uuid: string;
9 name: string;
10 count: number;
11 createdAt: string;
12}
13
14async function discoverLists(accessToken: string) {
15 const headers = {
16 Authorization: `Bearer ${accessToken}`,
17 "X-Fanvue-API-Version": "2025-06-26",
18 };
19
20 // Fetch smart lists
21 const smartListsRes = await fetch("https://api.fanvue.com/chats/lists/smart", {
22 headers,
23 });
24 const smartLists: SmartList[] = await smartListsRes.json();
25
26 // Fetch custom lists (first page)
27 const customListsRes = await fetch("https://api.fanvue.com/chats/lists/custom?page=1&size=20", {
28 headers,
29 });
30 const customListsData = await customListsRes.json();
31 const customLists: CustomList[] = customListsData.data;
32
33 return { smartLists, customLists };
34}
35
36// Usage
37const { smartLists, customLists } = await discoverLists("YOUR_ACCESS_TOKEN");
38console.log("Smart Lists:", smartLists);
39console.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

$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": {
> "smartListUuids": ["subscribers"]
> }
> }'

Response:

1{
2 "id": "f1e2d3c4-b5a6-9788-c0d1-e2f3g4h5i6j7",
3 "recipientCount": 450,
4 "createdAt": "2025-10-03T12:00:00Z"
5}

Request Schema

1interface MassMessageRequest {
2 text?: string; // Message text (optional if media provided)
3 mediaUuids?: string[]; // Array of media UUIDs
4 price?: number | null; // Price in cents for pay-to-view (requires media)
5 includedLists: {
6 smartListUuids?: string[]; // Smart list IDs to include
7 customListUuids?: string[]; // Custom list UUIDs to include
8 };
9 excludedLists?: {
10 // Optional: lists to exclude
11 smartListUuids?: string[];
12 customListUuids?: string[];
13 };
14}

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

$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": {
> "smartListUuids": ["subscribers"]
> }
> }'

Example 2: Send to Multiple Lists

Target both active and expired subscribers:

$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": {
> "smartListUuids": ["subscribers", "expired_subscribers"]
> }
> }'

Example 3: Send to Custom List with Exclusions

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

$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:

$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": {
> "smartListUuids": ["all_fans"]
> }
> }'

Price is specified in cents. In this example, 999 represents 9.99.Minimumpriceis200cents(9.99. Minimum price is 200 cents (2.00).

Example 5: Combine Smart and Custom Lists

$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": {
> "smartListUuids": ["subscribers"],
> "customListUuids": ["f1e2d3c4-b5a6-9788-c0d1-e2f3g4h5i6j7"]
> }
> }'

Complete TypeScript Implementation

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

1interface MassMessageRequest {
2 text?: string;
3 mediaUuids?: string[];
4 price?: number | null; // Price in cents (e.g., 999 = $9.99)
5 includedLists: {
6 smartListUuids?: string[];
7 customListUuids?: string[];
8 };
9 excludedLists?: {
10 smartListUuids?: string[];
11 customListUuids?: string[];
12 };
13}
14
15interface MassMessageResponse {
16 id: string;
17 recipientCount: number;
18 createdAt: string;
19}
20
21class MassMessagingService {
22 private baseUrl = "https://api.fanvue.com";
23 private accessToken: string;
24
25 constructor(accessToken: string) {
26 this.accessToken = accessToken;
27 }
28
29 private async request(endpoint: string, options: RequestInit = {}) {
30 const response = await fetch(`${this.baseUrl}${endpoint}`, {
31 ...options,
32 headers: {
33 Authorization: `Bearer ${this.accessToken}`,
34 "X-Fanvue-API-Version": "2025-06-26",
35 "Content-Type": "application/json",
36 ...options.headers,
37 },
38 });
39
40 if (!response.ok) {
41 const error = await response.json().catch(() => ({}));
42 throw new Error(error.message || `HTTP ${response.status}: ${response.statusText}`);
43 }
44
45 return response.json();
46 }
47
48 // Step 1: Get available lists
49 async getSmartLists() {
50 return this.request("/chats/lists/smart");
51 }
52
53 async getCustomLists(page = 1, size = 20) {
54 return this.request(`/chats/lists/custom?page=${page}&size=${size}`);
55 }
56
57 async getSmartListMembers(listId: string, page = 1, size = 20) {
58 return this.request(`/chats/lists/smart/${listId}?page=${page}&size=${size}`);
59 }
60
61 async getCustomListMembers(listUuid: string, page = 1, size = 20) {
62 return this.request(`/chats/lists/custom/${listUuid}?page=${page}&size=${size}`);
63 }
64
65 // Step 2: Send mass message
66 async sendMassMessage(request: MassMessageRequest): Promise<MassMessageResponse> {
67 return this.request("/chats/mass-messages", {
68 method: "POST",
69 body: JSON.stringify(request),
70 });
71 }
72}
73
74// Example usage
75async function main() {
76 const service = new MassMessagingService("YOUR_ACCESS_TOKEN");
77
78 try {
79 // Step 1: Discover available lists
80 console.log("📋 Fetching available lists...");
81 const smartLists = await service.getSmartLists();
82 const customListsData = await service.getCustomLists();
83
84 console.log("Smart Lists:", smartLists);
85 console.log("Custom Lists:", customListsData.data);
86
87 // Step 2: Send mass message to subscribers
88 console.log("\n📤 Sending mass message...");
89 const result = await service.sendMassMessage({
90 text: "Hello everyone! Thank you for your support!",
91 includedLists: {
92 smartListUuids: ["subscribers"],
93 },
94 });
95
96 console.log("✅ Mass message sent!");
97 console.log(` Message ID: ${result.id}`);
98 console.log(` Recipients: ${result.recipientCount}`);
99 console.log(` Sent at: ${result.createdAt}`);
100 } catch (error) {
101 console.error("❌ Error:", error);
102 }
103}

Best Practices

1. Start Small

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

1// Preview recipients before sending
2const preview = await service.getSmartListMembers("subscribers", 1, 5);
3console.log("First 5 recipients:", preview.data);
4
5// Then send to the full list
6const result = await service.sendMassMessage({
7 text: "Test message",
8 includedLists: { smartListUuids: ["subscribers"] },
9});

2. Handle Rate Limits

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

1async function sendWithRetry(service: MassMessagingService, request: MassMessageRequest) {
2 let retries = 0;
3 const maxRetries = 3;
4
5 while (retries < maxRetries) {
6 try {
7 return await service.sendMassMessage(request);
8 } catch (error: any) {
9 if (error.message.includes("429") && retries < maxRetries - 1) {
10 // Rate limited - wait and retry
11 const waitTime = Math.pow(2, retries) * 1000; // Exponential backoff
12 console.log(`Rate limited. Waiting ${waitTime}ms before retry...`);
13 await new Promise((resolve) => setTimeout(resolve, waitTime));
14 retries++;
15 } else {
16 throw error;
17 }
18 }
19 }
20}

3. Validate Before Sending

1function validateMassMessageRequest(request: MassMessageRequest): string | null {
2 // Must have text or media
3 if (!request.text && !request.mediaUuids?.length) {
4 return "Must provide either text or media";
5 }
6
7 // Must have at least one included list
8 const hasIncludedLists =
9 (request.includedLists.smartListUuids?.length ?? 0) > 0 ||
10 (request.includedLists.customListUuids?.length ?? 0) > 0;
11
12 if (!hasIncludedLists) {
13 return "Must include at least one list";
14 }
15
16 // Price requires media
17 if (request.price && !request.mediaUuids?.length) {
18 return "Pay-to-view messages require media";
19 }
20
21 return null; // Valid
22}
23
24// Usage
25const error = validateMassMessageRequest(myRequest);
26if (error) {
27 console.error("Invalid request:", error);
28 return;
29}

4. Track Send History

Store mass message results for analytics:

1interface SendRecord {
2 messageId: string;
3 recipientCount: number;
4 sentAt: string;
5 lists: string[];
6 text: string;
7}
8
9async function sendAndTrack(
10 service: MassMessagingService,
11 request: MassMessageRequest
12): Promise<SendRecord> {
13 const result = await service.sendMassMessage(request);
14
15 const record: SendRecord = {
16 messageId: result.id,
17 recipientCount: result.recipientCount,
18 sentAt: result.createdAt,
19 lists: [
20 ...(request.includedLists.smartListUuids || []),
21 ...(request.includedLists.customListUuids || []),
22 ],
23 text: request.text || "(media only)",
24 };
25
26 // Save to your database or analytics service
27 console.log("Send record:", record);
28
29 return record;
30}

Troubleshooting

”At least one list must be provided”

Ensure you’re providing at least one list in includedLists:

1// ❌ Wrong - empty lists
2{
3 includedLists: {
4 }
5}
6
7// ✅ Correct - at least one list
8{
9 includedLists: {
10 smartListUuids: ["subscribers"];
11 }
12}

“Smart list not found”

Smart list identifiers must be lowercase:

1// ❌ Wrong - uppercase
2{
3 smartListUuids: ["SUBSCRIBERS"];
4}
5
6// ✅ Correct - lowercase
7{
8 smartListUuids: ["subscribers"];
9}

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:

1// Check list counts first
2const smartLists = await service.getSmartLists();
3const subscriberList = smartLists.find((l) => l.uuid === "subscribers");
4console.log("Subscribers count:", subscriberList?.count);

Additional Resources