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:
[
{
"uuid": "all_fans",
"name": "All Fans",
"count": 1250
},
{
"uuid": "subscribers",
"name": "Active Subscribers",
"count": 450
},
{
"uuid": "expired_subscribers",
"name": "Expired Subscribers",
"count": 180
}
]
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:
{
"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
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:
{
"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)
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
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
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:
{
"id": "f1e2d3c4-b5a6-9788-c0d1-e2f3g4h5i6j7",
"recipientCount": 450,
"createdAt": "2025-10-03T12:00:00Z"
}
Request Schema
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: {
smartListUuids?: string[]; // Smart list IDs to include
customListUuids?: string[]; // Custom list UUIDs to include
};
excludedLists?: {
// Optional: lists to exclude
smartListUuids?: string[];
customListUuids?: string[];
};
}
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"]
}
}'
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(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:
interface MassMessageRequest {
text?: string;
mediaUuids?: string[];
price?: number | null; // Price in cents (e.g., 999 = $9.99)
includedLists: {
smartListUuids?: string[];
customListUuids?: string[];
};
excludedLists?: {
smartListUuids?: 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: {
smartListUuids: ["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:
// 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: { smartListUuids: ["subscribers"] },
});
2. Handle Rate Limits
The mass messages endpoint has rate limits. Handle them gracefully:
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
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.smartListUuids?.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:
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.smartListUuids || []),
...(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:
// ❌ Wrong - empty lists
{
includedLists: {
}
}
// ✅ Correct - at least one list
{
includedLists: {
smartListUuids: ["subscribers"];
}
}
“Smart list not found”
Smart list identifiers must be lowercase:
// ❌ Wrong - uppercase
{
smartListUuids: ["SUBSCRIBERS"];
}
// ✅ Correct - lowercase
{
smartListUuids: ["subscribers"];
}
403 Forbidden Error
Verify you have the required OAuth scopes:
Check your OAuth configuration includes both scopes.
Empty recipientCount
If recipientCount is 0, the lists might be empty or exclusions removed all recipients:
// 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