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.

Contact
{
  "id": "c1000000-…",
  "address": "priya.n@gmail.com",
  "name": "Priya N",
  "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

  • The address is stored in lower case.
  • The name comes from the From display name the first time one is seen. It is never overwritten after that, so a name you set stays.
  • Every message has a contact_id: the sender’s contact for inbound mail, the first recipient’s for outbound mail.
  • GET /v1/contacts/:id includes recent_threads: up to 10 threads with this contact, across all your inboxes.

#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. Metadata holds your own key/value pairs, typically ids from your CRM or shop; see Metadata.

contacts.ts
// 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' } });
contacts.py
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 & pathNotes
GET /v1/contacts?q= (address or name), ?label=, ?metadata.<key>=<value>, cursor, limit. Newest last_seen_at first.
GET /v1/contacts/:idThe contact and its recent_threads.
GET /v1/contacts/by-address/:address404 if unknown.
PATCH /v1/contacts/:id{ name?, notes?, metadata?, add_labels?, remove_labels? }.

#From an MCP client

get_contact takes an id or an address; update_contact changes notes, metadata or labels. The dashboard has the same under Contacts, with a notes editor and a metadata editor.