Skip to main content
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"]
    }
  }'

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:
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:
  • 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:
// 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