Dev.to AI πŸ€– Ai πŸ‘ 0 πŸ“– 3 min read

Giving AI a memory of your Copper CRM (that also forgets)

Copper is popular with teams that live in Google Workspace. The data is all there: companies, people, opportunities, and a long log of notes and calls. But a CRM is good at storing things and bad at answering questions a

Copper is popular with teams that live in Google Workspace. The data is all there: companies, people, opportunities, and a long log of notes and calls. But a CRM is good at storing things and bad at answering questions about them.

For Mergetober I built a Copper connector for cognee. cognee builds a knowledge graph and a vector index from your data, so you can ask "Which opportunities over $10k mention a competitor?" and get an answer that pulls from several records at once.

Setup in a few lines

import cognee
from cognee_community_connector_copper import copper_source

await cognee.remember(
    copper_source(
        api_key="...",
        user_email="[email protected]",  # the user who created the key
        resource_name="copper_sales",
    ),
    dataset_name="copper_crm",
    primary_key="id",
    write_disposition="merge",
)

One gotcha that cost me time: Copper checks that the API key and the email belong together. If you send a teammate's email with your key, you get a 401. The connector's error message now says so directly.

How I use it

Account research. "What do we know about Acme?" pulls the company record, its people, its open opportunities, and every note logged on them. Each note names the record it was logged on, which is how the graph connects them.

Weekly review. "Which opportunities in Qualified had a call this month but no next step?" Pipelines, stages, sources, and loss reasons are stored as names, so I can ask the way I'd ask a colleague.

Lost deal patterns. "Why did we lose deals last quarter?" works because loss reasons and the notes around them end up in the same graph.

What gets synced

  • Opportunities: status, pipeline, stage, value, company, primary contact, owner, priority, close date, source, loss reason, tags, custom fields, and details.
  • People and companies: name, title, company, contact type, owner, tags, custom fields, and details.
  • Activities: notes, calls, meetings, and any other activity type your team logs by hand.

System activities like "stage changed" are left out. They're noise for questions, and the current stage is already on the opportunity. Emails, phone numbers, and addresses are left out on purpose too.

Keeping memory honest

The hard part of any sync is deletes. If someone deletes a wrong note in Copper, the AI must stop quoting it. Copper has no feed of deleted records, so the connector works like this:

  1. Each run searches every selected record, 200 per page.
  2. Each record is rendered to text and fingerprinted. Unchanged records are skipped, so cognee only does work for real changes.
  3. After a full pass, any record that wasn't seen is checked again by id.
  4. A 404 means it's gone. The connector sends a delete marker, and cognee removes it from the graph, the vectors, and the database.

Disabling an activity type in Copper also removes those activities from memory on the next run. The same happens if you stop syncing a record type.

Why not filter by modified date?

Copper's search has minimum_modified_date, but it doesn't work for every record type. And renaming an opportunity doesn't change its notes' modified date, even though each note names its opportunity. Comparing rendered text handles both cases.

The cost is one request per 200 records per run. 20,000 records is about 100 requests, which is well under Copper's limit of 180 a minute.

Edge cases I had to handle

  • The 100,000 cap. Copper search stops at 100,000 records. If a record type hits that, the connector syncs what it can but never deletes for that type, because it can't see the rest. It reports failed_cap so you know.
  • Rate limits. Waits are capped at a minute per request and two minutes per run, so a sync never holds cognee's ingest lock for long. If it stops early, the next run picks up on the same page.
  • Odd answers. If a search comes back as an error object instead of a list, the connector raises. Otherwise it would look like an empty account, and everything would be queued for deletion.

Try it

It's in packages/connector/copper in cognee-community. A Copper free trial works: create a key under Settings β†’ Integrations β†’ API Keys, export COPPER_API_KEY and COPPER_USER_EMAIL, and run examples/example.py.

Built for Mergetober with cognee.

πŸ“° Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.