Docs · Guides
Contacts and memory
Who your agent is talking to: a record per outside address, with notes and metadata your agent can read and update.
A contact is an outside address your workspace has exchanged mail with. Agentboxd creates and updates contacts on every inbound and outbound message, so your agent can ask “who is this?” before it reads a word. Your own inbox addresses are never contacts, and neither are automated senders.
{
"id": "c1000000-…",
"address": "priya.n@gmail.com",
"name": "Priya N",
"kind": "person",
"kind_source": "auto",
"name_source": "auto",
"domain": "gmail.com",
"last_subject": "Order 1042",
"notes": "Wholesale. Prefers short replies. Out of office until 2 October.",
"metadata": { "crm_id": "hs_48213", "plan": "wholesale", "shopify_customer": 7730912 },
"labels": ["customer", "wholesale"],
"message_count": 3,
"first_seen_at": "2026-09-23T09:12:00.000Z",
"last_seen_at": "2026-09-25T11:02:00.000Z",
"created_at": "2026-09-23T09:12:00.000Z"
}#How contacts are filled in
- Inbound mail: the sender, and the other people in To and Cc (none of them when there are more than 20). Sent mail: every To, Cc and Bcc recipient.
- Automated senders never become contacts: mail with an
Auto-Submittedheader (other thanno), no-reply style addresses (noreply@, mailer-daemon@, bounces@, notifications@, …) and complaint reports. The mail arrives as usual, withcontact_id: null. A reply to mail you sent always counts. - Mailing-list mail (
List-Id, orList-UnsubscribewithPrecedence: bulk,listorjunk): the person who wrote it becomes a contact, but not the list or its other readers. - The address is stored in lower case;
domainis the part after the @. - The name comes from the sender’s own display name, or the name you give a recipient when you write. Names others put in To or Cc are not used. The name is replaced when a real one arrives while the stored one is empty, the address, its first part or a no-reply name. A name you set with
PATCH(name_source: "manual") is never replaced; clearing it lets mail fill it again. message_countcounts the messages involving the address: from it, or to, Cc or Bcc it.last_subjectis the subject of the newest message with the contact, withoutRe:orFwd:. A cheap memory of what you last talked about.- Every message has a
contact_id: the sender’s contact for inbound mail, the first recipient’s for outbound mail. It isnullonce that contact is deleted. GET /v1/contacts/:idincludesrecent_threads: up to 10 threads with this contact, across all your inboxes.
#Kinds: people first
Each contact has a kind: person, automated (notifications, verification codes), sales (sales and newsletters) or unknown (not sorted yet). A new sender starts as unknown. Writing to an address, or an answer to mail you sent, makes it a person. Otherwise, when AI processing reads its first mail, the category sorts it: sales and newsletters make it sales, notifications and verification codes automated, everything else person. An uncertain category, or a message that looks like phishing, leaves it unknown. Once sorted, later mail never changes the kind, so one invoice from a person you know does not turn them into a robot.
Set the kind yourself with PATCH { kind } (or update_contact): kind_source becomes manual and later mail never changes it. GET /v1/contacts?kind=person lists people only; the dashboard shows people by default.
#Cleaning up and forgetting
DELETE /v1/contacts/:id deletes one contact; POST /v1/contacts/delete deletes every contact of some kinds and/or a domain, for example { "kind": ["automated", "sales"] }. Try it with "dry_run": true first: it only counts. The mail stays either way.
With forget (?forget=true, or "forget": true in the bulk delete), the contact is removed and stops coming back: mail from the address, or to or Cc’ing it, no longer creates it, also with a +tag (forgetting a@x covers a+news@x). Agentboxd keeps only a keyed hash of the address, nothing else. The mail itself stays in your inboxes: forgetting is not an erasure of the messages. Sending to the address yourself brings it back as a new contact. The MCP tool delete_contact never forgets.
#By organisation
GET /v1/contacts/domains counts your contacts per domain, so you can see which companies you talk to; free-mail providers like Gmail or Outlook are left out. GET /v1/contacts?domain=acme.com lists the people at one of them. The dashboard has the same view.
#Notes, labels and metadata
Notes are free text, up to 10,000 characters: what your agent should remember, like preferences, open issues or what not to do. Labels work like message labels, up to 100 per contact: a change that would leave more answers 400 too_many_labels and changes nothing. Removing labels always works. Metadata holds your own key/value pairs, typically ids from your CRM or shop; see Metadata.
// Who is this? Name, notes, metadata and labels, created from mail.
const c = await mr.contacts.byAddress('priya.n@gmail.com');
console.log(c.notes, c.metadata.crm_id); // "Wholesale…" "hs_48213"
// Link it to your CRM and remember something. Metadata merges; null deletes a key.
await mr.contacts.update(c.id, {
notes: `${c.notes ?? ''}
Asked about order 1042 on 25 Sep.`,
metadata: { crm_id: 'hs_48213', plan: 'wholesale' },
add_labels: ['customer'],
});
// Find contacts by your own id.
const { data } = await mr.contacts.list({ metadata: { crm_id: 'hs_48213' } });c = mr.contacts.by_address("priya.n@gmail.com")
print(c["notes"], c["metadata"].get("crm_id"))
mr.contacts.update(
c["id"],
notes=(c["notes"] or "") + "
Asked about order 1042 on 25 Sep.",
metadata={"crm_id": "hs_48213", "plan": "wholesale"},
add_labels=["customer"],
)#Routes
| Method & path | Notes |
|---|---|
GET /v1/contacts | ?q= (address or name), ?label=, ?kind= (comma-separated), ?domain=, ?metadata.<key>=<value>, cursor, limit. Newest last_seen_at first. |
GET /v1/contacts/domains | Contacts per domain, free mail left out. ?kind=, ?limit=. |
GET /v1/contacts/:id | The contact and its recent_threads. |
GET /v1/contacts/by-address/:address | 404 if unknown. |
PATCH /v1/contacts/:id | { name?, notes?, metadata?, add_labels?, remove_labels?, kind? }. |
DELETE /v1/contacts/:id | Deletes the contact with its name, notes, labels and metadata → 204. Messages stay. The address becomes a contact again if you exchange mail with it later, unless you add ?forget=true. Workspace-wide keys only. |
POST /v1/contacts/delete | { kind?, domain?, forget?, dry_run? }: bulk delete, up to 1,000 per call. Workspace-wide keys only. |
#From an MCP client
get_contact takes an id or an address; update_contact changes notes, metadata, labels or the kind; delete_contact deletes one (only when you ask it to, and without forgetting the address). The dashboard has the same under Contacts, with a notes editor, a metadata editor, a people/automated/sales filter, a by-organisation view and a clean-up button.