@audienti/cli 0.1.145 → 0.2.0-exora.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -895
- package/bin/audienti-mcp.mjs +2 -0
- package/cli/account-intake-operations.mjs +28 -0
- package/cli/account-intake.mjs +33 -0
- package/cli/account-replies.mjs +35 -0
- package/cli/account-reply-operations.mjs +55 -0
- package/cli/agents.mjs +160 -0
- package/cli/assignment-operations.mjs +218 -0
- package/cli/assignment.mjs +116 -0
- package/cli/attachment-operations.mjs +9 -0
- package/cli/attachments.mjs +28 -0
- package/cli/audience-operations.mjs +21 -0
- package/cli/audiences.mjs +29 -0
- package/cli/audienti.mjs +217 -0
- package/cli/audit-operations.mjs +17 -0
- package/cli/audit.mjs +39 -0
- package/cli/budget-operations.mjs +8 -0
- package/cli/budgets.mjs +34 -0
- package/cli/campaign-find.mjs +155 -0
- package/cli/campaign-operations.mjs +30 -0
- package/cli/campaigns.mjs +61 -0
- package/cli/cohort-operations.mjs +22 -0
- package/cli/cohorts.mjs +45 -0
- package/cli/connection-registry-operations.mjs +8 -0
- package/cli/content.mjs +29 -0
- package/cli/discovery-operations.mjs +28 -0
- package/cli/discovery.mjs +15 -0
- package/cli/file-operations.mjs +29 -0
- package/cli/files.mjs +14 -0
- package/cli/guardrails-operations.mjs +12 -0
- package/cli/guardrails.mjs +37 -0
- package/cli/json-file.mjs +13 -0
- package/cli/kv-operations.mjs +17 -0
- package/cli/kv.mjs +58 -0
- package/cli/mcp.mjs +47 -0
- package/cli/notification-operations.mjs +8 -0
- package/cli/offer-operations.mjs +12 -0
- package/cli/offers.mjs +24 -0
- package/cli/outbound-operations.mjs +16 -0
- package/cli/outbound.mjs +54 -0
- package/cli/outcome-operations.mjs +26 -0
- package/cli/outcomes.mjs +16 -0
- package/cli/owner-account-client.mjs +18 -0
- package/cli/playbook-help.mjs +79 -0
- package/cli/playbook-operations.mjs +36 -0
- package/cli/profiles.mjs +29 -0
- package/cli/progress.mjs +26 -0
- package/cli/prospecting-settings-operations.mjs +16 -0
- package/cli/prospecting-settings.mjs +15 -0
- package/cli/question-operations.mjs +12 -0
- package/cli/questions.mjs +32 -0
- package/cli/queue-operations.mjs +12 -0
- package/cli/queue.mjs +25 -0
- package/cli/recipient-operations.mjs +13 -0
- package/cli/recipients.mjs +34 -0
- package/cli/rooms-operations.mjs +48 -0
- package/cli/rooms.mjs +160 -0
- package/cli/search-operations.mjs +9 -0
- package/cli/secret-operations.mjs +7 -0
- package/cli/secrets.mjs +30 -0
- package/cli/sender-account-operations.mjs +28 -0
- package/cli/sender-accounts.mjs +33 -0
- package/cli/skills-mcp.mjs +31 -0
- package/cli/skills.mjs +113 -0
- package/cli/suppression-operations.mjs +8 -0
- package/cli/suppression.mjs +12 -0
- package/cli/tools-operations.mjs +78 -0
- package/cli/tools.mjs +100 -0
- package/cli/usage-operations.mjs +8 -0
- package/cli/usage.mjs +41 -0
- package/cli/work-execution-operations.mjs +42 -0
- package/cli/work-execution.mjs +29 -0
- package/cli/work-feedback-operations.mjs +17 -0
- package/cli/work-feedback.mjs +18 -0
- package/cli/work-operations.mjs +30 -0
- package/cli/work.mjs +32 -0
- package/cli/workspaces.mjs +255 -0
- package/package.json +15 -26
- package/skills/agent-rooms/SKILL.md +140 -0
- package/skills/agent-rooms/references/commands.md +78 -0
- package/skills/audienti/SKILL.md +161 -317
- package/skills/audienti/references/helpers.md +162 -0
- package/skills/audienti/references/profile-workflows.md +214 -0
- package/skills/audienti/references/workflow-receipt.md +49 -0
- package/skills/audienti/scripts/contracts.mjs +72 -0
- package/skills/audienti/scripts/profile-helpers.mjs +138 -0
- package/skills/audienti/scripts/run.mjs +40 -0
- package/skills/getting-started/SKILL.md +227 -0
- package/skills/getting-started/references/audience.md +143 -0
- package/skills/getting-started/references/campaign-draft.md +80 -0
- package/skills/getting-started/references/capabilities.md +237 -0
- package/skills/getting-started/references/mailbox-check.md +64 -0
- package/skills/getting-started/references/outcome-checks.md +114 -0
- package/skills/getting-started/references/replies.md +186 -0
- package/skills/getting-started/references/reply-checks.md +54 -0
- package/skills/playbook-design/SKILL.md +136 -0
- package/skills/playbook-design/references/fields.md +130 -0
- package/source-manifest.json +104 -0
- package/.mcp.json +0 -13
- package/CHANGELOG.md +0 -1018
- package/bin/audienti-mcp.js +0 -5
- package/bin/audienti.js +0 -6
- package/install +0 -83
- package/skills/audienti/references/methodology.md +0 -111
- package/skills/audienti-gift-research/SKILL.md +0 -47
- package/src/agent-skills.js +0 -60
- package/src/api-client.js +0 -1639
- package/src/cli.js +0 -16992
- package/src/config.js +0 -130
- package/src/mcp.js +0 -103
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Capability report format
|
|
2
|
+
|
|
3
|
+
Report what you can do for the user right now, as rows. One row per capability,
|
|
4
|
+
provider, source and account. The capability ids come from Audienti's catalog
|
|
5
|
+
([ADR 015](https://github.com/omalab/exora/blob/main/docs/decisions/015-capability-taxonomy.md));
|
|
6
|
+
the full list is below.
|
|
7
|
+
|
|
8
|
+
```json
|
|
9
|
+
{
|
|
10
|
+
"runtime": { "name": "Claude Code", "model": "claude-opus-5-5" },
|
|
11
|
+
"capabilities": [
|
|
12
|
+
{ "capability": "web.search", "provider": "web", "via": "native", "access": "read", "status": "connected" },
|
|
13
|
+
{ "capability": "web.page.read", "provider": "web", "via": "native", "access": "read", "status": "connected" },
|
|
14
|
+
{ "capability": "profile.message.send", "provider": "email", "via": "composio:gmail", "account": "owner@example.com", "access": "send", "status": "connected" },
|
|
15
|
+
{ "capability": "profile.inbox.read", "provider": "email", "via": "composio:gmail", "account": "owner@example.com", "access": "read", "status": "connected" },
|
|
16
|
+
{ "capability": "profile.read", "provider": "linkedin", "via": "composio:linkedin", "access": "read", "status": "connected" },
|
|
17
|
+
{ "capability": "profile.message.send", "provider": "linkedin", "via": "composio:linkedin", "account": "linkedin.com/in/owner", "access": "send", "status": "connected", "limits": { "daily": 20 } },
|
|
18
|
+
{ "capability": "crm.records.search", "provider": "salesforce", "via": "mcp:salesforce", "access": "read", "status": "needs_sign_in" }
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
audienti agent capabilities report --file CAPABILITIES.json --profile AGENT_PROFILE
|
|
25
|
+
audienti agent capabilities --profile AGENT_PROFILE
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The first command replaces your previous report. The second prints what Audienti now
|
|
29
|
+
counts for you: the rows that work now, and the rows that work with Audienti's hosted
|
|
30
|
+
tools in the selected workspace. Over MCP, use `agent_capabilities_report` and
|
|
31
|
+
`agent_capabilities_list`. Over HTTP, `PUT` and `GET /api/v1/agent-capabilities`.
|
|
32
|
+
|
|
33
|
+
## Fields
|
|
34
|
+
|
|
35
|
+
- `runtime.name`: your product or runtime as the user knows it. `runtime.model` is
|
|
36
|
+
optional.
|
|
37
|
+
- `capability`: an id from the list below. An unknown id is refused.
|
|
38
|
+
- `provider`: which kind of profile the work touches. Required where the list names
|
|
39
|
+
providers; leave it out where it says "none". Where it says "any", leave it out or
|
|
40
|
+
name the one you use.
|
|
41
|
+
- `via`: where you reach it. `native` for a connector built into your runtime;
|
|
42
|
+
`composio:<app>` (for example `composio:gmail`), `treg:<tool>` or `mcp:<server>` for
|
|
43
|
+
a hub or MCP server. Check your hubs first: list each app connected there as its own
|
|
44
|
+
row.
|
|
45
|
+
- `account`: the user's own mailbox, LinkedIn account or handle you act through, such as
|
|
46
|
+
`owner@example.com` or `linkedin.com/in/owner`. Required for every verb that acts as
|
|
47
|
+
the user (marked "as the user" below).
|
|
48
|
+
- `access`: `read`, `write` or `send`, as listed for the capability. `send` reaches
|
|
49
|
+
another person; drafting is `write`.
|
|
50
|
+
- `status`: `connected` when you can call it now, `needs_sign_in` when it is installed
|
|
51
|
+
but signed out, `unavailable` when it exists but fails.
|
|
52
|
+
- `cost`: leave it out. Your own tools are `owner`.
|
|
53
|
+
- `limits`: optional `daily` and `hourly` caps you respect.
|
|
54
|
+
- `identity`: optional, on an email or LinkedIn row that names an `account`. The
|
|
55
|
+
account's own stable id, as `{ "namespace": "...", "accountId": "..." }`, so Audienti
|
|
56
|
+
can match the LinkedIn account or mailbox to the one the user set up. Use the same identity on
|
|
57
|
+
every row for that account. Examples: `{ "namespace": "linkedin", "accountId":
|
|
58
|
+
"ACoAAB1234" }` for a LinkedIn account, `{ "namespace": "gmail", "accountId":
|
|
59
|
+
"108234567890" }` for a Gmail mailbox.
|
|
60
|
+
|
|
61
|
+
At most 64 rows and 32 KiB.
|
|
62
|
+
|
|
63
|
+
## LinkedIn accounts
|
|
64
|
+
|
|
65
|
+
For each LinkedIn account you send as, add an entry to `seats` with what you know about
|
|
66
|
+
it. Audienti shows it to the user on the LinkedIn account's page and plans around it.
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"seats": [
|
|
71
|
+
{
|
|
72
|
+
"provider": "linkedin",
|
|
73
|
+
"account": "linkedin.com/in/owner",
|
|
74
|
+
"checkEverySeconds": 21600,
|
|
75
|
+
"plan": "premium_business",
|
|
76
|
+
"inmail": { "available": true, "quotaPerMonth": 15, "creditsLeft": 11 },
|
|
77
|
+
"limits": { "connectionRequestsPerDay": 20, "connectionRequestsPerWeek": 100, "messagesPerDay": 50, "inmailPerMonth": 15 }
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- `account`: the same LinkedIn account you name in your LinkedIn rows.
|
|
84
|
+
- `checkEverySeconds`: how often you can check this LinkedIn account's sent invitations, at least
|
|
85
|
+
3600 (one hour). Audienti schedules outcome checks at this cadence; every 6 hours
|
|
86
|
+
when you leave it out. The user never changes it.
|
|
87
|
+
- `plan`: the LinkedIn plan the account has, as the LinkedIn interface shows it: `free`,
|
|
88
|
+
`premium_career`, `premium_business`, `premium_all_in_one`, `sales_navigator_core`,
|
|
89
|
+
`sales_navigator_advanced` or `recruiter_lite`. Leave it out when you cannot tell; never guess.
|
|
90
|
+
The plan sets the InMail allowance. Audienti keeps connection notes to 200 characters for
|
|
91
|
+
now, whatever the plan.
|
|
92
|
+
- `inmail`: whether the LinkedIn account can send InMail, its monthly quota, and `creditsLeft`,
|
|
93
|
+
the balance the interface shows now (Sales Navigator shows it in the compose window and on
|
|
94
|
+
Settings). With a balance, Audienti plans an InMail only while `creditsLeft` is above 0.
|
|
95
|
+
Report `profile.inmail.send` as well; a LinkedIn account counts as InMail-capable only with both.
|
|
96
|
+
- `limits`: the safe limits for this LinkedIn account. Audienti never plans more LinkedIn sends on
|
|
97
|
+
it than its per-day and per-week limits. The user can lower a limit, never
|
|
98
|
+
raise it above the one you report.
|
|
99
|
+
|
|
100
|
+
Report `profile.invitations.read` on the LinkedIn account too, so outcome checks can be assigned to
|
|
101
|
+
you.
|
|
102
|
+
|
|
103
|
+
## Rules
|
|
104
|
+
|
|
105
|
+
- **Report, never connect.** List only what you can call now. Do not install or sign in
|
|
106
|
+
to anything to fill the list.
|
|
107
|
+
- **Never invent.** Leave out anything you cannot call. A wrong row sends tasks to you
|
|
108
|
+
that you cannot do.
|
|
109
|
+
- **Never report `via: audienti`.** Audienti adds rows for its own hosted tools itself
|
|
110
|
+
when the workspace has credits; a report that sends `audienti` is refused. Leave out
|
|
111
|
+
Audienti's hosted tools unless you reach them through another connector.
|
|
112
|
+
- **Never include secrets** (tokens, cookies, keys) or personal data about the user's
|
|
113
|
+
contacts.
|
|
114
|
+
- Report again when your tools change, and at least every seven days. The user sees
|
|
115
|
+
older rows as stale.
|
|
116
|
+
|
|
117
|
+
## The catalog
|
|
118
|
+
|
|
119
|
+
Launch rows can be required by queue tasks. Later rows are accepted and shown to the
|
|
120
|
+
user, but no task requires them yet.
|
|
121
|
+
|
|
122
|
+
### People and companies
|
|
123
|
+
|
|
124
|
+
| Capability | Providers | Access | What it does | Launch |
|
|
125
|
+
| --- | --- | --- | --- | --- |
|
|
126
|
+
| `people.search` | any | read | Find people from audience filters or a question | yes |
|
|
127
|
+
| `people.discover` | any | read | Find look-alikes from a seed person or company | yes |
|
|
128
|
+
| `person.profiles.discover` | any | read | Expand one person into their other profiles | yes |
|
|
129
|
+
| `companies.search` | any | read | Find companies from filters | yes |
|
|
130
|
+
| `company.enrich` | any | read | Fill size, industry, location, site | yes |
|
|
131
|
+
| `company.posts.list` | linkedin, x, instagram, tiktok | read | What a company page has posted | yes |
|
|
132
|
+
|
|
133
|
+
### Profile: research
|
|
134
|
+
|
|
135
|
+
| Capability | Providers | Access | What it does | Launch |
|
|
136
|
+
| --- | --- | --- | --- | --- |
|
|
137
|
+
| `profile.read` | linkedin, x, instagram, tiktok, github, web | read | Fetch one profile by URL or handle | yes |
|
|
138
|
+
| `profile.enrich` | linkedin, email | read | Fill title, company, location, history from one handle | yes |
|
|
139
|
+
| `profile.find` | email, phone | read | Find a person's handle on this provider | yes |
|
|
140
|
+
| `profile.verify` | email | read | Check the handle is live and deliverable | yes |
|
|
141
|
+
| `profile.posts.list` | linkedin, x, instagram, tiktok | read | What this profile has posted | yes |
|
|
142
|
+
| `profile.comments.list` | linkedin, x, instagram, tiktok | read | Comments this profile wrote on others' posts | yes |
|
|
143
|
+
| `profile.reactions.list` | linkedin, x, instagram, tiktok | read | Reactions this profile gave | yes |
|
|
144
|
+
|
|
145
|
+
### Profile: as the user (needs `account`)
|
|
146
|
+
|
|
147
|
+
| Capability | Providers | Access | What it does | Launch |
|
|
148
|
+
| --- | --- | --- | --- | --- |
|
|
149
|
+
| `profile.message.send` | email, linkedin, whatsapp, phone, x, instagram | send | Send an approved message | email, linkedin |
|
|
150
|
+
| `profile.inbox.read` | email, linkedin, whatsapp | read | Watch the user's inbox for replies | email, linkedin |
|
|
151
|
+
| `profile.invitations.read` | linkedin | read | Read the user's sent invitations, to see which were accepted or closed | yes |
|
|
152
|
+
| `profile.invitations.received` | linkedin | read | Read the invitations people sent to the user (as the user) | linkedin |
|
|
153
|
+
| `profile.inmail.send` | linkedin | send | Send an approved InMail | yes |
|
|
154
|
+
| `profile.connection.request` | linkedin | send | Send an approved connection request | yes |
|
|
155
|
+
| `profile.connection.withdraw` | linkedin | send | Withdraw a pending request | yes |
|
|
156
|
+
| `profile.connection.remove` | linkedin | send | Remove a connection | later |
|
|
157
|
+
| `profile.follow` | linkedin, x, instagram, tiktok | send | Follow a person or company | linkedin |
|
|
158
|
+
| `profile.unfollow` | linkedin, x, instagram, tiktok | send | Unfollow | linkedin |
|
|
159
|
+
| `profile.mute` | x, instagram | send | Mute | later |
|
|
160
|
+
| `profile.block` | x, instagram, linkedin | send | Block | later |
|
|
161
|
+
| `profile.call` | phone | send | Call a prospect | later |
|
|
162
|
+
|
|
163
|
+
### Posts and topics
|
|
164
|
+
|
|
165
|
+
| Capability | Providers | Access | What it does | Launch |
|
|
166
|
+
| --- | --- | --- | --- | --- |
|
|
167
|
+
| `topic.track` | any | read | Track a topic for signals | yes |
|
|
168
|
+
| `posts.search` | linkedin, x, instagram, tiktok, web | read | Find posts about a topic | yes |
|
|
169
|
+
| `post.read` | linkedin, x, instagram, tiktok | read | Fetch one post with its type | yes |
|
|
170
|
+
| `post.comments.list` | linkedin, x, instagram, tiktok | read | Who commented, and what they said | yes |
|
|
171
|
+
| `post.reactions.list` | linkedin, x, instagram, tiktok | read | Who reacted | yes |
|
|
172
|
+
| `post.comment` | linkedin, x, instagram, tiktok | send | Comment as the user (as the user) | linkedin |
|
|
173
|
+
| `post.react` | linkedin, x, instagram, tiktok | send | React as the user (as the user) | linkedin |
|
|
174
|
+
| `post.publish` | linkedin, x, instagram, tiktok | send | Publish a post as the user (as the user) | linkedin |
|
|
175
|
+
| `profile.visit` | linkedin | read | Visit a profile as a person would (as the user) | linkedin |
|
|
176
|
+
| `profile.views.read` | linkedin | read | Read who viewed the user's profile (as the user) | linkedin |
|
|
177
|
+
|
|
178
|
+
### Web, calendar, records, owner, integration
|
|
179
|
+
|
|
180
|
+
| Capability | Providers | Access | What it does | Launch |
|
|
181
|
+
| --- | --- | --- | --- | --- |
|
|
182
|
+
| `web.search` | web | read | Search the web | yes |
|
|
183
|
+
| `web.page.read` | web | read | Read one page | yes |
|
|
184
|
+
| `calendar.read` | google, microsoft | read | Free and busy time | later |
|
|
185
|
+
| `calendar.freebusy.read` | google, microsoft | read | Read the user's free and busy times to offer slots in a reply | later |
|
|
186
|
+
| `calendar.event.create` | google, microsoft | write | Create an event and send the invite from the user's calendar | later |
|
|
187
|
+
| `meeting.book` | google, microsoft | write | Book or propose a meeting | later |
|
|
188
|
+
| `meeting.reschedule` | google, microsoft | write | Move a booked meeting | later |
|
|
189
|
+
| `meeting.cancel` | google, microsoft | write | Cancel a booked meeting | later |
|
|
190
|
+
| `crm.records.search` | hubspot, salesforce, pipedrive, other | read | Find records in the user's CRM | yes |
|
|
191
|
+
| `crm.record.read` | hubspot, salesforce, pipedrive, other | read | Read one CRM record | yes |
|
|
192
|
+
| `crm.record.write` | hubspot, salesforce, pipedrive, other | write | Write back to one CRM record | yes |
|
|
193
|
+
| `owner.notify` | push, slack, sms, email | send | Reach the user on their own channel | yes |
|
|
194
|
+
| `webhook.receive` | none | read | Take a push instead of polling | later |
|
|
195
|
+
| `webhook.send` | none | send | Post events to the user's URL | later |
|
|
196
|
+
|
|
197
|
+
`owner.notify` means you can reach your user outside Audienti: chat, push, Slack, SMS or email. Report it
|
|
198
|
+
and Audienti puts a **Tell your user about an alert** task in your queue for each alert it records in
|
|
199
|
+
the workspace; the task carries the message and a link, and you mark it done once you delivered it. An
|
|
200
|
+
agent that has not reported `owner.notify` gets no such task.
|
|
201
|
+
|
|
202
|
+
### Tools you use for yourself
|
|
203
|
+
|
|
204
|
+
| Capability | Providers | Access | What it does | Launch |
|
|
205
|
+
| --- | --- | --- | --- | --- |
|
|
206
|
+
| `tool.media.transcribe` | none | read | Video or audio to text | later |
|
|
207
|
+
| `tool.image.read` | none | read | Read text and content out of an image | later |
|
|
208
|
+
| `tool.proxy.session.start` | none | write | Sticky residential session for your own browser, metered by bandwidth | yes |
|
|
209
|
+
| `tool.proxy.session.rotate` | none | write | New IP on the current session | yes |
|
|
210
|
+
| `tool.proxy.config.read` | none | read | Read region, stickiness and bandwidth cap | yes |
|
|
211
|
+
| `tool.proxy.config.write` | none | write | Set region, stickiness and bandwidth cap | yes |
|
|
212
|
+
|
|
213
|
+
Your runtime's documents, spreadsheets, files, code, memory, tasks and chat are not
|
|
214
|
+
capabilities. Leave them out.
|
|
215
|
+
|
|
216
|
+
## Older reports
|
|
217
|
+
|
|
218
|
+
`audienti agent inventory report` (categories such as `web_search` or `crm`) and the
|
|
219
|
+
per-account `audienti agent capabilities report` with `provider` and `accountRef` are
|
|
220
|
+
still accepted for one release. Audienti translates both into rows. Send the format above
|
|
221
|
+
instead.
|
|
222
|
+
|
|
223
|
+
Translated inventory reports match fewer queue tasks than before:
|
|
224
|
+
|
|
225
|
+
- `company_data` alone matches none of the find, verify or enrich tasks.
|
|
226
|
+
- `linkedin` alone no longer matches find or enrich tasks.
|
|
227
|
+
- `web_search` alone no longer matches enrich tasks.
|
|
228
|
+
- `people_data` alone no longer matches verify tasks.
|
|
229
|
+
- `email` and `linkedin` match send tasks only for their own provider and only with
|
|
230
|
+
`send` access; `social` matches no send task.
|
|
231
|
+
|
|
232
|
+
A LinkedIn message can carry a file. Upload it first with `audienti attachments upload FILE` (an image up to 5 MB,
|
|
233
|
+
or a voice note up to 5 MB and 2 minutes; Audienti reads a WAV, M4A, OGG or MP3 voice note's length from the file; add `--duration SECONDS` only for a file it cannot read). Name the returned id in
|
|
234
|
+
`content.attachments` of the `linkedin.send` action, and tag that action `profile.message.send`. A connection
|
|
235
|
+
request carries no file. A voice note goes only to a connection. When you send from your own account, run
|
|
236
|
+
`audienti attachments get ID --out PATH` to fetch the file, then attach it in LinkedIn. A managed account sends it
|
|
237
|
+
for you.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# The mailbox test email (`mailbox.confirm`)
|
|
2
|
+
|
|
3
|
+
Audienti never signs in to your user's mailbox. You send from it through your own email
|
|
4
|
+
connector (Gmail or Outlook, through Composio, a Claude connector or similar), so you also
|
|
5
|
+
prove it works. A mailbox counts for campaigns only once you sent a test email from it to
|
|
6
|
+
itself, read it back and reported the code. Your user does nothing in this check: never ask
|
|
7
|
+
them for the code, and never send them to Settings to confirm a mailbox.
|
|
8
|
+
|
|
9
|
+
When your user connects a mailbox you reported, or asks for another test email in Settings →
|
|
10
|
+
Sending, you get one `mailbox.confirm` task for it. Its `detail.test` gives the mailbox as
|
|
11
|
+
you reported it (`mailbox`), its address as Audienti has it (`address`, or `null` when the
|
|
12
|
+
mailbox was reported under a name or an account id that is no address), the code (`code`) and
|
|
13
|
+
the subject to use (`subject`, `Audienti mailbox check` and the code).
|
|
14
|
+
|
|
15
|
+
1. Claim the task: `audienti queue claim TASK_ID`.
|
|
16
|
+
2. Send one email from that mailbox to its own address, and to nobody else. Use the address
|
|
17
|
+
your connector reports for the mailbox; `address` is only a hint, and if your connector
|
|
18
|
+
reports another, use that one. Use the subject the task gives and put the code in the body
|
|
19
|
+
too. Send nothing else.
|
|
20
|
+
3. Read that mailbox's inbox with your connector until the email arrives: about once a
|
|
21
|
+
minute, for up to 15 minutes. Read only the test email. Note its message id and the time
|
|
22
|
+
it arrived.
|
|
23
|
+
4. Report what you read, with the address you sent it to (`sentTo`):
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{ "results": [{ "kind": "mailbox.received", "code": "CODE", "sentTo": "ADDRESS_YOU_SENT_TO", "message": "MESSAGE_ID", "receivedAt": "2026-11-04T12:01:00Z" }] }
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
audienti outcomes report TASK_ID --file RESULTS.json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
5. Delete both copies of the test email, the sent one and the received one, then report it:
|
|
34
|
+
`{ "results": [{ "kind": "mailbox.deleted", "message": "MESSAGE_ID" }] }`.
|
|
35
|
+
6. Report the task done: `audienti queue done TASK_ID --note "test email confirmed"`.
|
|
36
|
+
|
|
37
|
+
Use the code exactly as you read it back from the email; the dash and case do not matter.
|
|
38
|
+
Only the task's own code confirms the mailbox. A code of an older test email is refused
|
|
39
|
+
(`stale_code`), any other code too (`wrong_code`), and so is an email that arrived before
|
|
40
|
+
the task asked for it (`received_before_asked`), and so is an email sent to another of your
|
|
41
|
+
user's mailboxes (`other_mailbox`). Reporting the same email again returns
|
|
42
|
+
`already_recorded`.
|
|
43
|
+
|
|
44
|
+
You can do this check even if you can only read the workspace: your own test email is the
|
|
45
|
+
one result a read-only agent may report.
|
|
46
|
+
|
|
47
|
+
## When you cannot send or read
|
|
48
|
+
|
|
49
|
+
Report which step failed, with a short reason, then report the task done:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{ "results": [{ "kind": "mailbox.failed", "step": "send", "reason": "No Gmail connector" }] }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- `send`: you have no connector that can send from the mailbox, or the send failed. Tell
|
|
56
|
+
your user exactly: "I need a Gmail or Outlook connector to send from your mailbox."
|
|
57
|
+
- `read`: the email went out but you cannot read the mailbox's inbox, or it did not arrive
|
|
58
|
+
in 15 minutes. Tell your user exactly: "I need a Gmail or Outlook connector that can read
|
|
59
|
+
your mailbox's inbox."
|
|
60
|
+
|
|
61
|
+
Settings → Sending and Capabilities then show your user which step failed. No new task comes
|
|
62
|
+
until they fix your connector and choose Send another test email. The task cannot be
|
|
63
|
+
reported done until you reported `mailbox.received` or `mailbox.failed`
|
|
64
|
+
(`mailbox_not_reported`).
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Outcome checks
|
|
2
|
+
|
|
3
|
+
An outcome check is a queue task (`outcomes.check`) that asks you whether the people
|
|
4
|
+
you invited on one of the user's LinkedIn accounts have accepted. Audienti schedules it
|
|
5
|
+
for you at the cadence you reported for that LinkedIn account (`checkEverySeconds`, every 6 hours
|
|
6
|
+
when you report none), and only while someone on that LinkedIn account waits for an accept. Each
|
|
7
|
+
task lists at most 100 people, so a LinkedIn account with more people waiting gets several tasks
|
|
8
|
+
in the same interval, and every waiting person is on one of them. You never decide when to check, and you never check
|
|
9
|
+
anyone the task does not list.
|
|
10
|
+
|
|
11
|
+
## What the task gives you
|
|
12
|
+
|
|
13
|
+
The task's `detail` holds:
|
|
14
|
+
|
|
15
|
+
- `seat`: the LinkedIn account to check (`account` is the LinkedIn account you reported, for example
|
|
16
|
+
`linkedin.com/in/owner`).
|
|
17
|
+
- `waiting`: the people to check, at most 100. The list stays as it is while you hold
|
|
18
|
+
the task, so you can report on everyone it lists; no other check lists them meanwhile,
|
|
19
|
+
even one for the next interval. Each has `person` (the Audienti person
|
|
20
|
+
id you report against), `name`, `profileUrl` and `invitedAt` (when the invitation was
|
|
21
|
+
sent).
|
|
22
|
+
|
|
23
|
+
The task requires `profile.invitations.read` on that LinkedIn account. If you hold it, the task is
|
|
24
|
+
assigned to you.
|
|
25
|
+
|
|
26
|
+
## The procedure
|
|
27
|
+
|
|
28
|
+
1. Claim the task: `audienti queue claim TASK_ID`.
|
|
29
|
+
2. Read the LinkedIn account's own sent invitations with your tool, or read each listed person's
|
|
30
|
+
profile to see the connection degree. Look up only the people in `waiting`. Do not
|
|
31
|
+
list, search or open anyone else, and do not page through the user's whole network.
|
|
32
|
+
3. Keep to the limits you reported for this LinkedIn account. Spread reads out if your tool asks
|
|
33
|
+
you to. If you cannot check now (signed out, rate limited), release the task with a
|
|
34
|
+
note: `audienti queue release TASK_ID --note "why"`.
|
|
35
|
+
4. Write what you found to a file and report it:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"results": [
|
|
40
|
+
{ "kind": "linkedin.accept", "person": "PERSON_ID", "at": "2026-11-01T09:30:00Z" },
|
|
41
|
+
{ "kind": "linkedin.invite_closed", "person": "PERSON_ID", "reason": "withdrawn" },
|
|
42
|
+
{ "kind": "linkedin.profile_degree", "person": "PERSON_ID", "degree": 2 }
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
audienti outcomes report TASK_ID --file RESULTS.json
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
5. Report the task done: `audienti queue done TASK_ID --note "3 checked, 1 accepted"`.
|
|
52
|
+
|
|
53
|
+
## Result kinds
|
|
54
|
+
|
|
55
|
+
- `linkedin.accept {person, at}`: the person accepted. `at` is when they accepted, as
|
|
56
|
+
close as your tool tells you. Audienti ends the person's wait and plans the next step
|
|
57
|
+
of the playbook, which appears in your queue.
|
|
58
|
+
- `linkedin.invite_closed {person, reason}`: the invitation is no longer pending.
|
|
59
|
+
`reason` is `withdrawn` (the user or you withdrew it) or `expired` (LinkedIn closed
|
|
60
|
+
it). Audienti records the fact; the wait still ends at its timeout.
|
|
61
|
+
- `linkedin.profile_degree {person, degree}`: optional evidence, the connection degree
|
|
62
|
+
you read on the profile (1, 2 or 3). A 1 means connected; report it as an accept as
|
|
63
|
+
well when you know the time.
|
|
64
|
+
|
|
65
|
+
A `steps.run` task (a comment, react, withdraw, follow or unfollow step of a playbook, for one person) takes these
|
|
66
|
+
results instead. The task names the step in `detail.step` and tells you how to find the post.
|
|
67
|
+
|
|
68
|
+
- `linkedin.comment {person, postUrl}`: you commented on the post. Comment only when you can add
|
|
69
|
+
material value; otherwise react.
|
|
70
|
+
- `linkedin.reaction {person, postUrl, reaction}`: you reacted. `reaction` is `like` (the default),
|
|
71
|
+
`celebrate`, `support`, `love`, `insightful` or `funny`.
|
|
72
|
+
- `linkedin.follow {person}` and `linkedin.unfollow {person}`: for a follow or unfollow step. Audienti records
|
|
73
|
+
that your account now follows the person, or no longer does. It skips a later Follow step for someone you
|
|
74
|
+
already follow, and an Unfollow step for someone you do not.
|
|
75
|
+
- `linkedin.step_skipped {person, reason}`: nothing to engage with, or your user declined.
|
|
76
|
+
- `linkedin.invite_closed {person, reason: withdrawn}`: for a withdraw step.
|
|
77
|
+
|
|
78
|
+
Audienti records the step as run, and the playbook goes on to its next step.
|
|
79
|
+
|
|
80
|
+
An `invitations.receive` task (every 6 hours, only on a seat whose connector can read received invitations)
|
|
81
|
+
asks you to read the connection requests people sent to your user. Read only the pending received list. Never
|
|
82
|
+
accept, decline or open anyone. Report each pending request:
|
|
83
|
+
|
|
84
|
+
- `linkedin.invitation_received {ref, profileUrl, name, sentAt}`: give `ref` (the request id your connector shows,
|
|
85
|
+
otherwise the person's profile address) or `profileUrl`; `name` and `sentAt` when you have them. Audienti records
|
|
86
|
+
each request once and tells your user once. Report the same request again and it returns `already_recorded`.
|
|
87
|
+
|
|
88
|
+
Report done with no results when no request is pending.
|
|
89
|
+
|
|
90
|
+
Leave out anyone still pending. They stay on the next check until they accept or the
|
|
91
|
+
wait times out.
|
|
92
|
+
|
|
93
|
+
## What the report returns
|
|
94
|
+
|
|
95
|
+
`results` has one entry per result you sent, in the same order. Each person is recorded
|
|
96
|
+
on their own, so one person who cannot be recorded does not hold back the others.
|
|
97
|
+
|
|
98
|
+
- `status`: `recorded` (written now), `already_recorded` (an earlier report wrote it;
|
|
99
|
+
`eventId` is that earlier event) or `not_recorded` (nothing was written).
|
|
100
|
+
- `reason`: why, when there is one. `participation_ended` or
|
|
101
|
+
`participation_unavailable` means the fact was recorded but nobody moves, because
|
|
102
|
+
the person's campaign participation ended or its playbook cannot be read.
|
|
103
|
+
`not_found` with `not_recorded` means the person is no longer in the workspace.
|
|
104
|
+
- `next`: the person's next step after the report, when there is one.
|
|
105
|
+
- `failed`: the accept was recorded, but planning the next step failed. Audienti shows
|
|
106
|
+
the failure on the person's timeline; `reason` holds its code. Sending the same
|
|
107
|
+
accept again returns the same failure until it is resolved.
|
|
108
|
+
|
|
109
|
+
## Errors
|
|
110
|
+
|
|
111
|
+
- `not_claimed`: claim the task first, or your lease expired. Read the queue again.
|
|
112
|
+
- `not_on_list`: you reported a person the task does not list. Report only the listed
|
|
113
|
+
people.
|
|
114
|
+
- `validation_error`: a result has a wrong kind, person id or time.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Handling a reply
|
|
2
|
+
|
|
3
|
+
A reply task (`replies.handle`) means a prospect answered. The inbound itself is never
|
|
4
|
+
approved: inbound wins, and you record it. What the user approves is your answer.
|
|
5
|
+
|
|
6
|
+
## What to do
|
|
7
|
+
|
|
8
|
+
1. Read the conversation: `audienti prospects conversation list PERSON --workspace ID`.
|
|
9
|
+
2. Classify the reply as one of `interested`, `question`, `later`, `out_of_office`,
|
|
10
|
+
`stop`, `bounce`, `referral` or `wrong_person` (the prospect is the wrong person and
|
|
11
|
+
names no one else). A stop, a bounce or a referral must carry its typed
|
|
12
|
+
`result`, or the report is refused; see [Stop, bounce and referral](#stop-bounce-and-referral).
|
|
13
|
+
3. Draft the answer from the mailbox or LinkedIn account the reply came to (the assignee's).
|
|
14
|
+
- Bind the recipient and the sender, or the answer waits in Blocked: the recipient's
|
|
15
|
+
`contactId` comes from `audienti prospects contacts list PERSON`, and the
|
|
16
|
+
`senderAccountId` is the user's confirmed sender account for that mailbox or LinkedIn account
|
|
17
|
+
(`audienti sender accounts list`).
|
|
18
|
+
- If you report `calendar.freebusy.read`, read the user's free times and offer up
|
|
19
|
+
to six slots in the answer. Put the same times in `slots`, so the user sees them on
|
|
20
|
+
the card: "Slots from your calendar · Thu 10:00, 14:30 ET". A scheduling tool that
|
|
21
|
+
reports only `calendar.read` is not the user's calendar and offers no slots.
|
|
22
|
+
- Without a calendar, ask the prospect for a time that suits them, and leave `slots`
|
|
23
|
+
out. The user's card says no agent has a calendar and links to the Capabilities
|
|
24
|
+
screen.
|
|
25
|
+
4. Record the classification and the draft in one result, keeping the reply open:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
audienti prospects replies set PERSON --workspace ID --evidence EVIDENCE --file RESULT.json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"requestId": "a new UUID for each result",
|
|
34
|
+
"expectedVersion": 0,
|
|
35
|
+
"status": "needs_handling",
|
|
36
|
+
"outcome": null,
|
|
37
|
+
"reason": "Interested and asked for a time on Thursday.",
|
|
38
|
+
"responseEvidenceId": null,
|
|
39
|
+
"classification": "interested",
|
|
40
|
+
"draft": {
|
|
41
|
+
"type": "email.send",
|
|
42
|
+
"content": { "subject": "Re: Late invoices", "body": "Thursday works. I have 10:00 or 14:30 ET. Which suits you?" },
|
|
43
|
+
"recipient": { "prospectId": "PERSON", "email": "jane@example.com", "contactId": "CONTACT" },
|
|
44
|
+
"sender": { "provider": "email", "accountRef": "owner@example.com", "senderAccountId": "SENDER" },
|
|
45
|
+
"reason": "Offers the two free slots on Thursday the prospect asked for.",
|
|
46
|
+
"slots": [
|
|
47
|
+
{ "start": "2026-11-05T15:00:00Z", "end": "2026-11-05T15:30:00Z", "timeZone": "America/New_York" },
|
|
48
|
+
{ "start": "2026-11-05T19:30:00Z", "end": "2026-11-05T20:00:00Z", "timeZone": "America/New_York" }
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`expectedVersion` is the reply's current version (`audienti prospects replies get`).
|
|
55
|
+
The draft becomes a revision the user reviews in Operator. Nothing is sent until the
|
|
56
|
+
user approves it. A second result for the same reply replaces a draft the user has
|
|
57
|
+
not decided yet.
|
|
58
|
+
5. Report the task done.
|
|
59
|
+
|
|
60
|
+
The owner can also close a reply in Operator with "Mark handled" or "Not a reply". That records the same
|
|
61
|
+
state as `replies set` with status `handled` or `not_reply`, and withdraws any answer you drafted for it
|
|
62
|
+
that no agent has claimed to send. Read `replies get` before you set a reply again.
|
|
63
|
+
|
|
64
|
+
To close a reply for the owner by CLI, pass the version you read as `expectedVersion`. Handled with no
|
|
65
|
+
answer is `{"requestId":"<uuid>","expectedVersion":1,"status":"handled","outcome":"closed_without_response","reason":"The owner answered by phone."}`.
|
|
66
|
+
Not a reply is `{"requestId":"<uuid>","expectedVersion":1,"status":"not_reply","reason":"An automatic message."}`;
|
|
67
|
+
it has no outcome. Give `--file` an absolute path.
|
|
68
|
+
|
|
69
|
+
The classification records a `replied` event on the person's active campaigns, except
|
|
70
|
+
for `bounce` and `out_of_office`, which are not replies from the person.
|
|
71
|
+
An `out_of_office` reply records an auto-reply instead. The playbook's out-of-office rule
|
|
72
|
+
snoozes the person: the wait and the next step hold for 7 days, or until the return date
|
|
73
|
+
when you pass `returnsOn`. Nothing is sent meanwhile and the person does not move. A
|
|
74
|
+
`wrong_person` reply that names no one stops the sends and leaves the reply for the user to
|
|
75
|
+
settle: ask who the right person is. When the reply names the right person, classify it as a
|
|
76
|
+
`referral` with its typed `result`.
|
|
77
|
+
|
|
78
|
+
## Stop, bounce and referral
|
|
79
|
+
|
|
80
|
+
These three need no decision from the user. Classify the reply, report the typed
|
|
81
|
+
`result` next to the classification it matches, and Audienti records it against the
|
|
82
|
+
person. The classification without its result is refused with 400 naming the result it
|
|
83
|
+
needs (`reply.stop`, `email.bounce` or `reply.referral`). Use the prospect's words,
|
|
84
|
+
verbatim, for `quote`.
|
|
85
|
+
|
|
86
|
+
- **Stop** (`stop`): the prospect asked not to be contacted. Report status `handled`,
|
|
87
|
+
outcome `suppressed` and no draft:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{ "classification": "stop", "status": "handled", "outcome": "suppressed",
|
|
91
|
+
"result": { "type": "reply.stop", "scope": "person", "quote": "Please remove me from your list." } }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The person is suppressed for good on every channel, their pending sends and open
|
|
95
|
+
tasks are withdrawn, and they leave their campaigns. The user cannot override it.
|
|
96
|
+
Use `"scope": "company"` only when they asked to stop for their whole company:
|
|
97
|
+
everyone at the company's domain is suppressed, and so is anyone added later. The
|
|
98
|
+
domain goes on the do-not-contact list marked "Asked to stop", and no one can remove it.
|
|
99
|
+
Never contact a person who asked to stop again, on any channel.
|
|
100
|
+
- **Bounce** (`bounce`): a delivery failure came back instead of a reply. Report
|
|
101
|
+
status `not_reply` and no draft:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{ "classification": "bounce", "status": "not_reply", "outcome": null,
|
|
105
|
+
"result": { "type": "email.bounce", "address": "priya@example.com", "kind": "hard", "evidence": "550 5.1.1 User unknown" } }
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`address` is the address that bounced: the person's own address, or one nobody in the
|
|
109
|
+
workspace holds. An address another person holds is refused with 409
|
|
110
|
+
`address_belongs_to_another_person`; report the bounce on the reply that came back
|
|
111
|
+
about that person instead.
|
|
112
|
+
`hard` means the address does not exist or refuses mail: it is marked bounced and
|
|
113
|
+
blocked in every campaign, and the person leaves the campaign as Bounced. `soft`
|
|
114
|
+
means a full mailbox or a temporary failure: the address is held for 24 hours, then
|
|
115
|
+
a retry task asks you to send the message once more. Retried once means exactly one
|
|
116
|
+
send: after the 24 hours the address takes one send, and every other send to it is
|
|
117
|
+
refused with `recipient_contact_bounced` until that send's outcome is known. A second
|
|
118
|
+
bounce within 14 days of the retry counts as hard; with no bounce in that time the
|
|
119
|
+
address opens again. Put the bounce message and its status code in `evidence`.
|
|
120
|
+
- **Referral** (`referral`): the prospect is the wrong person and named someone else.
|
|
121
|
+
Report status `needs_handling` with a thank-you as the draft, and name the person:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{ "classification": "referral", "status": "needs_handling", "outcome": null,
|
|
125
|
+
"result": { "type": "reply.referral", "name": "Sam Lee", "title": "Finance lead",
|
|
126
|
+
"email": "sam@example.com", "quote": "Wrong person, try Sam Lee in finance." },
|
|
127
|
+
"draft": { "type": "email.send", "content": { "subject": "Re: Late invoices", "body": "Thank you, Jane. I will reach out to Sam." }, "...": "recipient, sender and reason as above" } }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Give `linkedinUrl` or `email` when the prospect gave them. Audienti finds or adds the
|
|
131
|
+
named person (an email someone in the workspace already holds names that person), links
|
|
132
|
+
the two records, and leaves the referrer as Not a fit with the note "referred to Sam
|
|
133
|
+
Lee". When the email and the LinkedIn profile belong to different people, the referral
|
|
134
|
+
is refused with 409 `identity_conflict`. Reporting the same referral again, from
|
|
135
|
+
another reply, changes nothing. The named person is not in the queue yet: a
|
|
136
|
+
`prospects.verify` task asks you to check them against the campaign's audience
|
|
137
|
+
first. The thank-you is an answer like any other: the user approves it in Operator
|
|
138
|
+
before it is sent.
|
|
139
|
+
|
|
140
|
+
Every first email you draft ends with one plain sentence that lets the prospect opt
|
|
141
|
+
out, such as "If this is not for you, reply and I will not write again." When they do,
|
|
142
|
+
report it as a stop.
|
|
143
|
+
|
|
144
|
+
## After the user approves
|
|
145
|
+
|
|
146
|
+
A `work.execute` task with `action: send_reply` follows. Claim it, send the answer in
|
|
147
|
+
the thread the prospect wrote in, report the result, then close the reply with status
|
|
148
|
+
`handled`, outcome `response_recorded` and the sent message's evidence as
|
|
149
|
+
`responseEvidenceId`.
|
|
150
|
+
|
|
151
|
+
## When a meeting is booked
|
|
152
|
+
|
|
153
|
+
- The prospect picks a slot: send the invite from the user's calendar
|
|
154
|
+
(`calendar.event.create`), then record `"meeting": { "at": "…Z", "source": "sent_invite" }`
|
|
155
|
+
in a `replies set` result.
|
|
156
|
+
- The prospect sent an invite: set `"invite": true` with the classification. A
|
|
157
|
+
`meetings.confirm` task follows, assigned to one agent of the person's assignee that
|
|
158
|
+
is allowed to make workspace changes and reports `calendar.event.create` or
|
|
159
|
+
`meeting.book`. It accepts the invite on the
|
|
160
|
+
assignee's calendar and records `"source": "received_invite"`. If the assignee has no
|
|
161
|
+
such agent, no task appears.
|
|
162
|
+
- The meeting moves the person to the playbook's goal stage. Record it once.
|
|
163
|
+
|
|
164
|
+
## Before a meeting is booked
|
|
165
|
+
|
|
166
|
+
Record where an unbooked meeting stands with `"meetingState"` in a `replies set` result:
|
|
167
|
+
|
|
168
|
+
- `requested`: a time was asked for or offered.
|
|
169
|
+
- `accepted`: they agreed, and no time is fixed yet.
|
|
170
|
+
- `declined`: they said no to the meeting. A plain no is not a request to stop; classify the
|
|
171
|
+
reply on its own.
|
|
172
|
+
|
|
173
|
+
Each state is written once, and only when it differs from the last one. Nothing is written after
|
|
174
|
+
a booking. If a playbook step waits for a meeting and the wait runs out while the request is
|
|
175
|
+
`requested` or `accepted`, Audienti first asks you for a follow-up, up to 3 times (the playbook
|
|
176
|
+
can change the count and the wait; the default wait is 5 days). Write each follow-up yourself
|
|
177
|
+
after you read the whole conversation, and send it on the thread's channel. Do not use a fixed
|
|
178
|
+
template unless the user put one in your instructions. Save it as a plan action with the
|
|
179
|
+
`playbookStepKey` the task names. After the last wait runs out, Audienti records
|
|
180
|
+
`meeting_no_answer` itself and tells the owner. Do not record it.
|
|
181
|
+
|
|
182
|
+
## Taken over
|
|
183
|
+
|
|
184
|
+
When the user takes a person over, every open task and draft for them is withdrawn
|
|
185
|
+
and no new task about them appears, in any campaign. There is no hand back. Do not
|
|
186
|
+
contact them again. A draft for them is refused with `taken_over`.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Mailbox checks
|
|
2
|
+
|
|
3
|
+
Audienti never signs in to your user's mailbox. You send from it through your own email
|
|
4
|
+
connector (Gmail or Outlook, through Composio, a Claude connector or similar), so you also
|
|
5
|
+
prove it works and read it for replies. Two queue tasks ask for that. If you have no email
|
|
6
|
+
connector, tell your user exactly: "I need a Gmail or Outlook connector to send from your
|
|
7
|
+
mailbox." Never send them to Settings to connect a mailbox.
|
|
8
|
+
|
|
9
|
+
## The test email (`mailbox.confirm`)
|
|
10
|
+
|
|
11
|
+
A mailbox counts for campaigns only once you sent a test email from it to itself, read it
|
|
12
|
+
back and reported its code. [The mailbox test email](mailbox-check.md) gives the steps. Your
|
|
13
|
+
user does nothing in that check. It proves the mailbox sends and that you can read its
|
|
14
|
+
inbox, which the reply checks below need too.
|
|
15
|
+
|
|
16
|
+
## The reply check (`replies.check`)
|
|
17
|
+
|
|
18
|
+
Before any next step goes to someone you emailed, in a campaign or not, Audienti asks you to
|
|
19
|
+
check the mailbox the email went out from for their reply. Until you report each person, no
|
|
20
|
+
next step is planned or sent for them. Each task lists at most 100 people in `waiting`, each
|
|
21
|
+
with `person`, `name`, `participation` (null when the step is in no campaign), `attempt`
|
|
22
|
+
(the email) and `sentAt` (when it went out). A task for a mailbox you confirm is assigned to
|
|
23
|
+
you.
|
|
24
|
+
While you hold the task, its list stays as it was when you claimed it, even after a reply
|
|
25
|
+
you record. Report everyone on it.
|
|
26
|
+
|
|
27
|
+
1. Claim the task.
|
|
28
|
+
2. Search the mailbox with your connector for mail from the listed people since their
|
|
29
|
+
`sentAt` only. Do not read anything else.
|
|
30
|
+
3. Record each reply you find the way you record any reply: `audienti prospects
|
|
31
|
+
conversation append PERSON --workspace ID --file ITEM.json` with `direction: inbound`
|
|
32
|
+
and `conversationType: direct`. It returns the evidence id. Audienti then ends the
|
|
33
|
+
person's wait, holds their sends and gives you a reply task, as for any reply.
|
|
34
|
+
4. Report every listed person:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"results": [
|
|
39
|
+
{ "kind": "email.no_reply", "person": "PERSON_ID" },
|
|
40
|
+
{ "kind": "email.reply", "person": "PERSON_ID", "evidence": "EVIDENCE_ID" }
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
audienti outcomes report TASK_ID --file RESULTS.json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
5. Report the task done. If you cannot read the mailbox now, release the task with a note.
|
|
50
|
+
|
|
51
|
+
An `email.reply` whose evidence is not an inbound message of that person is not recorded
|
|
52
|
+
(`reply_not_recorded`): record the reply first. A person you leave out comes back on the
|
|
53
|
+
next check, about an hour later. Reporting the same person again for the same email returns
|
|
54
|
+
`already_recorded`.
|