Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.lettr.com/llms.txt

Use this file to discover all available pages before exploring further.

The client.audience resource manages everything campaigns send to. Each kind is a sub-resource:
client.audience.contacts;    // individual contacts
client.audience.lists;       // contact lists
client.audience.topics;      // subscription topics
client.audience.properties;  // custom contact properties
client.audience.segments;    // dynamic segments
All list() methods return paginated data with a pagination object (total, per_page, current_page, last_page).

Contacts

import { Lettr } from "lettr";
const client = new Lettr(process.env.LETTR_API_KEY!);

// Create (optionally attach to a list and set properties)
const { data: contact } = await client.audience.contacts.create({
  email: "jane@example.com",
  list_id: "list-uuid",
  properties: { first_name: "Jane", plan: "pro" },
});

// List with filters
const { data, error } = await client.audience.contacts.list({
  page: 1,
  per_page: 50,
  search: "jane",
  status: "subscribed",
  list_id: "list-uuid",
});

if (!error) {
  for (const c of data.contacts) console.log(c.email, c.status);
  console.log(data.pagination.total);
}

// Get, update, delete
await client.audience.contacts.get("contact-uuid");
await client.audience.contacts.update("contact-uuid", {
  status: "unsubscribed",
  properties: { plan: "enterprise" },
});
await client.audience.contacts.delete("contact-uuid");
Contact status is "subscribed", "unsubscribed", "bounced", "complained", or "unverified".

API Reference

GET /audience/contacts

Double opt-in

Pass double_opt_in to create the contact as unverified and send a confirmation email — they become subscribed after clicking the link:
await client.audience.contacts.create({
  email: "jane@example.com",
  double_opt_in: {
    from: "hello@example.com",
    subject: "Confirm your subscription",
    template_slug: "email-confirmation",
    redirect_url: "https://example.com/confirmed",
  },
});

Bulk operations & membership

// Bulk create
await client.audience.contacts.bulkCreate({
  emails: ["a@example.com", "b@example.com"],
  list_id: "list-uuid",
});

// Single list / topic membership
await client.audience.contacts.attachList("contact-uuid", "list-uuid");
await client.audience.contacts.detachList("contact-uuid", "list-uuid");
await client.audience.contacts.subscribeTopic("contact-uuid", "topic-uuid");
await client.audience.contacts.unsubscribeTopic("contact-uuid", "topic-uuid");

// Bulk list membership
await client.audience.contacts.bulkAttachLists({
  contact_ids: ["c1", "c2"],
  list_ids: ["l1", "l2"],
});
await client.audience.contacts.bulkDetachLists({
  contact_ids: ["c1"],
  list_ids: ["l1"],
});

Lists

const { data: list } = await client.audience.lists.create({ name: "Newsletter" });
console.log(list!.id, list!.name, list!.contacts_count);

await client.audience.lists.list({ page: 1, per_page: 20 });
await client.audience.lists.get("list-uuid");
await client.audience.lists.update("list-uuid", { name: "Weekly digest" });
await client.audience.lists.delete("list-uuid");

// Bulk delete
await client.audience.lists.bulkDelete({ list_ids: ["l1", "l2"] });

API Reference

GET /audience/lists

Segments

A segment is a dynamic group defined by conditions. Groups are joined by OR; conditions within a group by AND.
await client.audience.segments.create({
  name: "Pro users at example.com",
  list_id: "list-uuid", // optional: restrict to one list
  conditions: {
    groups: [
      {
        conditions: [
          { field: "email", operator: "ends_with", value: "@example.com" },
          { field: "plan", operator: "equals", value: "pro" },
        ],
      },
    ],
  },
});

await client.audience.segments.list({ list_id: "list-uuid" });
await client.audience.segments.get("segment-uuid");
await client.audience.segments.delete("segment-uuid");
Operators include equals, not_equals, contains, starts_with, ends_with, is_true, is_false, and others.

API Reference

GET /audience/segments

Topics

await client.audience.topics.create({
  name: "Product updates",
  description: "Occasional product news",
  default_subscription: "opt_in",  // "opt_in" | "opt_out" — immutable after creation
  visibility: "public",            // "public" | "private"
});

await client.audience.topics.list({ per_page: 20 });
await client.audience.topics.get("topic-uuid");
await client.audience.topics.update("topic-uuid", { name: "Product news", visibility: "private" });
await client.audience.topics.delete("topic-uuid");

API Reference

GET /audience/topics

Properties

Custom contact properties have an immutable name and type; only the fallback can be updated.
await client.audience.properties.create({
  name: "plan",
  type: "string",         // "string" | "number" | "boolean" | "date" | "json"
  fallback_value: "free",
});

await client.audience.properties.list({ page: 1 });
await client.audience.properties.get("property-uuid");
await client.audience.properties.delete("property-uuid");

API Reference

GET /audience/properties

What’s Next

Campaigns

Send to your audience

API Reference

Full audience API reference