audience
Skill0 starsChanged 5 months agoMITView on GitHub
Use when managing Lettr audience data from code — contact lists, contacts (incl. double opt-in and bulk import), subscription topics, custom properties, and dynamic segments. This is the marketing/contacts side that campaigns send to.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- jamillazarev/opsinisttheir own version, first seen Jul 31, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- jamillazarev/opsinist★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 0 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 38
Properties are typed and pre-declared: Create a property (String/Number/Boolean/Date/Json) before writing it on contacts.
- Very specific: line 43
After a bulk import or segment change, read it back (GET the list/segment, or list contacts filtered by status/list) and report counts to …
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Manage Lettr audience
The audience is what campaigns target: lists (static collections), contacts (the people), topics (subscription categories), properties (typed custom fields), and segments (dynamic groups defined by conditions). All five share a CRUD shape.
1. Load the references
- SDK calls: detect the stack via
../_shared/detect-stack.md, then read../_generated/sdk/<lang>/audience.md— the sub-services, DTOs, enums, and bulk helpers. - Wire contract:
../_generated/api/index.md, Audience section — every endpoint with required fields bolded.
2. Map the task to the right resource
| Goal | Resource |
| Group contacts statically | list (POST /audience/lists) |
| Add/import people, all treated the same | contacts — …/bulk with a flat emails list |
| Import people who differ from each other | contacts — …/bulk with contacts[], one row per person |
| Let people opt in/out of categories | topics |
| Move many people on/off many topics | POST/DELETE /audience/contacts/topics/bulk |
| Store typed custom fields (plan, signup date…) | properties — create the property before setting it on contacts |
| Target by rules (plan = pro AND domain = …) | segments |
3. Gotchas that matter
- Double opt-in. Creating a contact with a double-opt-in config sends a confirmation email and leaves the contact
Unverifieduntil they confirm — they are not subscribed yet. Don't treat creation as subscription. Requires a verifiedfromdomain and (usually) a confirmation template. - Contact status is meaningful.
Subscribed,Unsubscribed,Bounced,Complained,Unverified. Don't resurrectUnsubscribed/Complainedcontacts by re-creating them — that's a compliance problem, not a bug. - Bulk over loops. For many contacts, or many list/topic attach/detach operations, use the
…/bulkendpoints, not N single calls (rate limit is 3 req/s per team). - A successful bulk import does not mean every row landed.
POST /audience/contacts/bulkanswers201even when rows failed: bad rows are skipped, the rest of the batch commits. Always readerror_countanderrors[](each carries a zero-basedindexinto the rows you sent, plusemail,error_codeanderror) and report skipped rows to the user. Never report "imported N contacts" from the row count you submitted. already_existedandupdatedoverlap — they never sum to the row count. They answer different questions: "was it already there?" and "did we change it?". A contact that already existed and got a list attached is counted in both. Reportingcreated + already_existed + updatedas a total is wrong arithmetic. Noteupdatedcounts any change, including a list or topic attach — not just properties being written.update_existinggates properties only. With it off, an existing contact keeps its properties but is still attached to the requested lists and topic opt-ins, and a row-levelopt_outstill drops an existing subscription. Consent and suppression imports do not need the flag. It used to gate opt-outs too, dropping them silently with no error row — so treat any advice to setupdate_existing: truein order to make opt-outs land as predating that fix.- Row-level
opt_outbeats batch-levelopt_in. That precedence is the point of the feature: a topic whosedefault_subscriptionisopt_outauto-subscribes newly created contacts, and a row-levelopt_outsuppresses that in the same request instead of needing a cleanup call afterwards. It applies to contacts that already exist as well: a row-levelopt_outunsubscribes them regardless ofupdate_existing. - A duplicate contact is a
409, not a server error.POST /audience/contactson an email that already exists returns409/resource_already_exists. It is client-correctable — update the existing contact, or bulk-create withupdate_existing— and it must not be retried. It used to escape as500/send_error; that code named email delivery, which was never involved, so ignore any older reference that mapssend_errorto this endpoint. contacts[]gives you the ids back. The bulk response returns{ id, email, created }for every contact that exists after the request, in submission order. Use it instead of a follow-up lookup to resolve ids.- Properties are typed and pre-declared. Create a property (
String/Number/Boolean/Date/Json) before writing it on contacts; afallback_valuecovers contacts that lack it. - Segment condition logic: conditions within a group are OR-ed, groups are AND-ed — i.e.
(A OR B) AND (C OR D). One group means "any of these"; add a group to narrow, add a condition to widen. Getting this inverted silently changes who gets mail. Scope a segment to alist_idwhen you don't want it spanning the whole audience.
4. Verify
After a bulk import or segment change, read it back (GET the list/segment, or list contacts filtered by status/list) and report counts to the user before anything sends to it. Sending to the audience is the campaigns skill.
For a bulk import specifically, report created, already_existed and updated as three separate answers rather than a total (they overlap), and state the skipped-row count explicitly — including when it is zero, so "all rows landed" is something you checked rather than assumed.
What this skill does not do
- It doesn't create or send campaigns — that's
campaigns. - It doesn't send transactional one-offs — that's
sending.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

