Tutorials

Sending mass messages

Mass messages let creators broadcast a single DM to a targeted audience — for promo drops, PPV releases, or re-engagement of expired subscribers. This guide covers how to discover available lists, send a broadcast, and stay inside FrontRow’s rate limits.

Required scopes

The API key used for these calls must have write:chat and read:fan scopes. Generate the key in Settings → Developer with both boxes checked.

Examples below use curl for raw HTTP and Node.js / TypeScript for the helper. Translate to any language by porting the request shape.

01

Discover available lists

Every creator has two kinds of audience lists. Smart lists are maintained automatically based on fan behavior. Custom lists are groups the creator builds by hand.

Smart lists

bash · GET /chats/lists/smart
1curl https://frontrow.center/api/v1/chats/lists/smart \
2 -H "Authorization: Bearer $FRONTROW_API_KEY"

Common smart list IDs:

all_fans

Anyone who has ever followed or subscribed.

subscribers

Active paid subscribers right now.

expired_subscribers

Subscribers whose plan lapsed in the last 90 days.

top_spenders

The top 10% of fans ranked by lifetime spend.

recent_tippers

Fans who tipped in the last 30 days.

Smart list IDs are always lowercase. Passing Subscribers will return a 400.

Custom lists

bash · GET /chats/lists/custom
1curl "https://frontrow.center/api/v1/chats/lists/custom?page=1&size=20" \
2 -H "Authorization: Bearer $FRONTROW_API_KEY"

Each custom list has a UUID, a name, and a member count. Preview the recipients before broadcasting:

bash · preview list members
1curl "https://frontrow.center/api/v1/chats/lists/custom/${LIST_ID}/members?page=1&size=10" \
2 -H "Authorization: Bearer $FRONTROW_API_KEY"

02

Send the broadcast

Mass sends go to POST /chats/mass-messages. A request must include either text or media (or both) and at least one entry in includeLists.

bash · POST /chats/mass-messages
1curl https://frontrow.center/api/v1/chats/mass-messages \
2 -H "Authorization: Bearer $FRONTROW_API_KEY" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "text": "New PPV drop tonight at 8pm — first 100 fans get it free.",
6 "includeLists": [
7 { "type": "smart", "id": "subscribers" }
8 ],
9 "excludeLists": [
10 { "type": "smart", "id": "recent_tippers" }
11 ]
12 }'

The response includes the broadcast ID, total recipient count after exclusions, and a queued-at timestamp:

json · 202 Accepted
1{
2 "id": "mm_9a3c1b04",
3 "recipientCount": 847,
4 "queuedAt": "2026-04-28T20:00:00Z"
5}

Pay-to-view broadcasts

For PPV, attach media uploaded via the media endpoints and set a price (in cents, minimum 200 = $2.00).

json · PPV broadcast body
1{
2 "text": "Behind-the-scenes from yesterday's shoot.",
3 "media": ["med_a1b2c3", "med_d4e5f6"],
4 "priceCents": 999,
5 "includeLists": [{ "type": "smart", "id": "top_spenders" }]
6}

03

TypeScript helper

A reusable service with retry-on-rate-limit and basic validation:

ts · masssend.ts
1type ListRef = { type: "smart" | "custom"; id: string };
2 
3interface MassMessage {
4 text?: string;
5 media?: string[];
6 priceCents?: number;
7 includeLists: ListRef[];
8 excludeLists?: ListRef[];
9}
10 
11const API_BASE = "https://frontrow.center/api/v1";
12 
13export async function sendMassMessage(payload: MassMessage) {
14 if (!payload.text && !payload.media?.length) {
15 throw new Error("Mass message needs text or media.");
16 }
17 if (!payload.includeLists.length) {
18 throw new Error("At least one include list is required.");
19 }
20 
21 for (let attempt = 0; attempt < 5; attempt++) {
22 const res = await fetch(`${API_BASE}/chats/mass-messages`, {
23 method: "POST",
24 headers: {
25 Authorization: `Bearer ${process.env.FRONTROW_API_KEY}`,
26 "Content-Type": "application/json",
27 },
28 body: JSON.stringify(payload),
29 });
30 
31 if (res.status === 429) {
32 const retryAfter = Number(res.headers.get("Retry-After") ?? 2);
33 await new Promise((r) => setTimeout(r, retryAfter * 1000));
34 continue;
35 }
36 
37 if (!res.ok) throw new Error(`Send failed: ${res.status}`);
38 return res.json();
39 }
40 
41 throw new Error("Rate limit exceeded after 5 retries.");
42}

Rate limits & best practices

  • Test with previews. Use the custom-list members endpoint to confirm the audience before broadcasting. Once queued, a mass message cannot be recalled.
  • Respect 429s. Mass-message endpoints are limited to 10 broadcasts per hour per creator. Use exponential backoff and honor the Retry-After header.
  • Combine includes and excludes. A common pattern is include: subscribers, exclude: recent_tippers to avoid messaging fans who just paid.
  • Track sends. Persist the returned id and recipient count so you can join broadcast metadata against subsequent message-engagement webhooks.
  • Keep it human. Frequent identical broadcasts hurt reply rates and can trigger spam classifiers. Personalize at least the opener using fan handles from the list members endpoint.