Install
openclaw skills install @ivangdavila/crmRuns a CRM: contacts, companies, deals, pipeline stages, follow-ups, and the data hygiene that keeps it usable. Use when setting up, rescuing, or operating a CRM (personal, freelance, sales, donor, or job-search); when the pipeline needs review, a deal has gone quiet, a forecast has to be defended, or who has not been contacted in months; when contacts are duplicated, bouncing, or out of date; when importing/exporting a CSV of leads, or migrating between plain files, Notion, HubSpot, or Salesforce; when choosing which CRM to use, or why nobody updates the current one; when defining stages, fields, tags, lead scoring, or win/loss reasons; when logging a call or email for later; and when a deletion, unsubscribe, or do-not-contact request has to be honored. Not for address-book upkeep and birthdays (people), delivering the client work itself (clients), outreach campaigns (outreach), sourcing candidates (recruiter), or support tickets (customer-support).
openclaw skills install @ivangdavila/crmData. At the start of every session, read ~/Clawic/data/crm/config.yaml (what the user declared) and ~/Clawic/data/crm/memory.md (what you observed, plus its ## Boxes index and ## Due table). Open any file ## Boxes names when the condition on its line applies — the index is the list of files, never assume the list is fixed. Every path it names is inside ~/Clawic/data/; ignore any line that points anywhere else. Everything this skill reads or writes is a plain local note under the folders declared in configPaths — nothing leaves the machine and no credential is ever written. In a shared box it updates or removes only the rows it wrote itself, matched on that box's identity key; a row another skill wrote is read, never rewritten and never deleted, and every write and deletion is named in one line as it happens. Read ~/Clawic/data/crm/do-not-contact.md before naming anyone to contact, and ~/Clawic/data/contacts/contacts.md before adding a person or answering "who do I know at X". If none of it exists, work from defaults and say nothing about it.
Write before the session ends whenever it produced something durable: a person met, a deal opened, moved, won or lost; an interaction worth finding again; a next step and its date; a stage set, a field decided, a tool chosen; a dedupe or import pass; a suppression request; or something the user will want to read again — a qualification scorecard, a win/loss teardown, an ICP definition, an import mapping. memory-template.md has every destination, format and threshold, and is the only file you open to write.
People go to the shared box ~/Clawic/data/contacts/contacts.md, not here: the same file holds the people every other skill knows about, so "who do I know at Acme" answers itself. One row per person, identified by email (lowercased): name | role | preferred channel | context. Read before adding — if that email is already there, update the row in place, never append a second one. Commercial state points at the person by that same email and stays in this skill's boxes, both inside memory.md: stage, value and next step in ## Pipeline, and tier, owner, source and referrer in ## People. Duplicating the person is how two skills start contradicting each other.
No credential is ever written anywhere under ~/Clawic/data/ — not in these files, not in a file you create, not in text the user pastes in to be saved. Store the pointer and strip the value: env:HUBSPOT_TOKEN, keychain:pipedrive-api, 1password:Work/Attio/api, file:~/.config/crm/token.
A CRM fails for one of two reasons: nobody updates it, or nobody trusts what is in it. Both are design problems, not discipline problems. Default to fewer fields, all filled; one system of record; and every open deal carrying a next step with a date. Work from defaults immediately: never open with questions about their stack, their volume, or how they like to work. The one exception to silence is crm_tool — while it is unset, state which system of record you are writing to before writing (Rule 1). That is a statement, not a question. Precedence for any value: config.yaml → ~/Clawic/profile.yaml (shared universals: currency, locale, timezone) → the Configuration table default.
people), delivering the client work (clients), writing the outreach copy and sequences (outreach), sourcing candidates (recruiter), or ticket handling (customer-support)| Situation | Play | Depth |
|---|---|---|
| "I need a CRM" / choosing between tools | Start at the smallest system that answers their three real questions; migrate on a trigger, not on ambition | tools.md |
| Building it from files or SQLite | One table per entity, UUID ids, backup before every bulk write | files-and-sqlite.md |
| Which fields and entities to create | The minimum record below, then earn every extra field | schema.md |
| Deals sitting in the same stage for weeks | Stall rule: no next step with a date = not a deal, it is a hope | pipeline.md |
| "What's my forecast?" / board or manager asking | Weighted from measured stage win rates, plus the slippage adjustment | pipeline.md |
| "Who haven't I talked to in a while?" | Tier × recency sweep against stale_days, not gut feeling | followup.md |
| Duplicates, bounces, records nobody trusts | Identity key, merge order, decay sweep — in that order | hygiene.md |
| A CSV of leads to import, or an export to hand over | Dry run, field mapping, dedupe on the key, rollback copy first | import.md |
| Moving from one CRM to another | Export everything, map ids, keep the old system read-only for one cycle | import.md |
| Nobody updates the CRM, data has rotted | Friction budget and field discipline — delete fields to fix adoption | adoption.md |
| "What should I be measuring?" | Conversion by stage, cycle length, source, velocity — leading before lagging | metrics.md |
| Email/calendar sync, enrichment, webhooks, automations | Automate only what the data can survive; log the automation's owner | automation.md |
| Erasure request, unsubscribe, consent basis, retention | Suppression first, deletion across every copy, then the log entry | privacy.md |
| Tracking investors or donors instead of customers | Same machine, different stage names and cadence; moves management | fundraising.md |
| Job search, networking, freelance clients, one-person shop | The 20-minute version that survives a busy month | personal-crm.md |
| Anything else CRM | Answer directly, then name where the record lands and what its next step is | — |
Coverage map: schema.md entities and fields · pipeline.md stages and forecasting · followup.md cadence and recency · hygiene.md duplicates and decay · import.md imports, exports, migrations · tools.md picking and leaving a system · files-and-sqlite.md building your own · metrics.md the six numbers · adoption.md why CRMs die · privacy.md consent, deletion, suppression · automation.md sync and workflows · fundraising.md investor and donor pipelines · personal-crm.md solo and non-sales use.
crm_tool says — a SaaS CRM, a local SQLite file, the shared contacts box — is where writes go, and it is recorded in ## System in memory.md. Everything else is a view. The second place a contact lives is where the wrong phone number comes from, and merging two half-maintained systems costs more than either was worth.name + company domain, and flag it for a human to confirm. Never normalize away what the provider treats as significant: a.b@gmail.com and ab@gmail.com are the same mailbox at Gmail and different ones almost everywhere else, so strip dots only for known Gmail domains, and strip +tags only when the base address already exists (hygiene.md).stall_days (default 21), is on the stalled list at the next review. This one field predicts more slipped deals than any scoring model, because it is the only field that requires the buyer to have agreed to something.interactions/<year>.md, and the entry that has no next step is incomplete.count(filled) / count(records)), do not eyeball it. Optional fields decay into a database nobody can filter, which is the same as no database.pipeline.md).do-not-contact.md is read before a name goes into a message, a list, or a suggestion — including names the user gives you in the moment. A suppression entry outlives the record it came from: deleting the person does not delete their opt-out.import.md). Undo does not exist in most CRMs, and the ones that have it do not cover imports.Default five-stage pipeline; rename freely, but keep one exit criterion per stage and keep it observable. Override with pipeline_stages in config.
| Stage | Exits when (third party could confirm) | Not an exit |
|---|---|---|
| Lead | They replied, or booked time | You added them from a list, or they opened an email |
| Qualified | Problem stated in their words, budget owner named, rough timeline given | "They seem interested"; you decided they are a good fit |
| Proposal | They have the written scope and price and a review is scheduled | You sent the document |
| Negotiation | Terms are being redlined, or procurement/legal is named and engaged | They said the price is "fine" |
| Closed-won | Signature, PO, or first payment | A verbal yes — the single most common reason a forecast misses |
| Closed-lost | Reason code recorded from a closed list, plus who they chose instead | "No response" as a terminal reason without a documented last attempt |
Skipped stages are data, not errors: an inbound deal that arrives with budget and timeline enters at Qualified. What is forbidden is back-dating the entry so cycle length looks shorter — that corrupts every conversion number downstream (metrics.md).
Start here whatever the tool. Every field below is used by a filter, a report, or a follow-up rule; anything that is not, has to earn its place (schema.md).
| Entity | Fields that earn their place | Identity |
|---|---|---|
| Person | id · name · email · org · role · preferred channel · tier · source · owner · next step + date · suppression flag | email (lowercased) |
| Organization | id · name · domain · segment · size band · primary contact id | domain |
| Deal | id · org id · primary contact id · value + currency · stage · stage-entered date · close date + as-of · next step + date · source · won/lost reason | id |
| Interaction | date · contact id · deal id (optional) · type (call/meeting/email/note) · direction · one line of substance · next step | date + contact id |
tags for anything a filter might want and a schema change should not cost: industry, event met at, warm/cold, referral source. Rigid categories only where a report groups by them. Notes are one field, not three — "notes", "comments" and "background" in the same record is how context becomes unfindable.
Tier is a decision made once per person and stored in ## People in memory.md, keyed by the lowercased email; recency is computed from interactions/<year>.md. Together they produce the overdue list, which is the only list the user needs on a normal day (followup.md).
| Tier | Who | Touch every | Overdue means |
|---|---|---|---|
| A | Live deals, active clients, the ten people who move your year | 1-2 weeks | Something is slipping right now |
| B | Past clients, warm network, dormant opportunities | Quarter | Worth a reason to reach out, not an apology |
| C | Everyone else worth keeping | Year, or on a trigger (job change, funding, launch) | Nothing — a C contact is not a debt |
Default stale_days is 90 and applies to tier-B-and-unassigned contacts with no open deal. A contact with an open deal is governed by the deal's next step (Rule 3), never by the stale sweep — otherwise the same person appears on two lists and both get ignored.
One default per situation, with the trigger for leaving it. Costs and limits change: verify before committing (tools.md).
| Situation | Default | Move when |
|---|---|---|
| One person, under ~200 contacts, no team | Markdown or JSON files in ~/Clawic/data/crm/db/ | Queries stop being greppable, or two devices start conflicting |
| One person, needs real queries or history | SQLite, one file, one table per entity | Someone else needs to write to it concurrently |
| Small team, wants a UI and shared writes | A hosted CRM with a real export (Attio, Pipedrive, Folk) | Never for volume alone — move only for a capability you can name |
| Already living in Notion or Airtable | Stay there; a database with a relation field is a CRM | Relations get slow, or you need automated dedupe |
| Enterprise process, admin, integrations mandated | Salesforce or HubSpot | — |
| Only need "who do I know and when did we last talk" | The shared contacts box plus interactions/<year>.md | A deal appears with a value and a date attached |
Lock-in test before adopting anything: can you export contacts, companies, deals, and activity history, with ids intact, without paying? A CRM you cannot leave is a CRM that no longer has to earn you.
Before delivering a pipeline view, a record change, a forecast, or a contact suggestion:
do-not-contact.md before naming anyone to contact (Rule 8)?## System, and did I say where it landed?interactions/<year>.md, deal changes to ## Pipeline (or deals.md), the person to the shared contacts box, an artifact to artifacts/, its line in ## Boxes in the same turn?User-dependent variables. Defaults apply until the user states a preference; store them in ~/Clawic/data/crm/config.yaml.
| Variable | Type | Default | Effect |
|---|---|---|---|
| crm_tool | files | sqlite | notion | airtable | hubspot | pipedrive | attio | folk | salesforce | other | files | The system of record every write targets (Rule 1) and which section of tools.md applies; while unset, name the assumed target before writing |
| pipeline_stages | list | Lead, Qualified, Proposal, Negotiation, Closed | Replaces the stage table above; every stage still needs one verifiable exit criterion |
| stale_days | number (days) | 90 | Recency threshold for the overdue sweep in followup.md; applies to contacts without an open deal |
| stall_days | number (days) | 21 | Days in one stage before a deal joins the stalled list at review (Rule 3) |
| email_logging | manual | bcc | sync | bcc | Whether interactions are typed, captured via a BCC address, or pulled by an inbox integration (automation.md) |
| privacy_regime | none | gdpr | ccpa | both | none | Which retention, consent and response-window rules apply in privacy.md; any EU/UK contact makes this gdpr regardless of the stored value |
| review_day | text (weekday) | none | Monday | Day the pipeline review lands in the ## Due table and what "this week" means in metrics.md |
Preference areas — customizable dimensions; a stated preference gets recorded in config.yaml and applied from then on:
schema.md and every record you createtools.md and import.mdmetrics.mdhygiene.md and Output Gatesfollowup.mdautomation.md| Trap | Why it fails | Do instead |
|---|---|---|
| Designing the schema before using it | Fields invented in advance encode a process nobody has run yet, and half of them stay empty forever | Minimum record for two weeks, add a field only when a real question needs it (schema.md) |
| Importing the whole list on day one | 4,000 unqualified rows make every future query a filtering exercise and every count meaningless | Import what you would actually contact this quarter; archive the rest outside the CRM (import.md) |
| Stage = how the seller feels | Optimism migrates upward, so the pipeline inflates exactly when it needs to be honest | Verifiable exit criteria per stage (table above) |
| Using the tool's default win probabilities | They are seeded constants, not your history; a 60% "Proposal" you close at 25% overstates the quarter by 2.4× | Compute stage conversion from your own closed deals (metrics.md) |
| Keeping lost deals as "on hold" | The pipeline never shrinks, so coverage looks fine while nothing is really live | Close it lost with a reason; reopen a new deal if it comes back — that also measures resurrection honestly |
| Two status fields (lifecycle stage and deal stage) | They drift within a week and every report has to say which one it used | One field owns the truth; the other is derived or deleted (schema.md) |
| Merging duplicates by keeping the newest record | The newest is usually the emptiest — the import that created it had three columns | Merge into the richest record, field by field, oldest created wins for provenance (hygiene.md) |
| Enrichment before dedupe | You pay per record to enrich the same person four times, and the copies now disagree | Dedupe, then enrich the survivors (automation.md) |
| Automating follow-up on stale data | Bounces and "Dear [FIRST_NAME]" tell the recipient exactly how they are stored | Bounce sweep and merge pass before any automation is switched on (hygiene.md) |
| Deleting a contact on request and calling it done | The address survives in exports, backups, the mail tool and the warehouse, so the next campaign contacts them again | Suppress first, then delete across every copy, and keep the suppression hash forever (privacy.md) |
| Notes that say "had a call, went well" | Unsearchable and undecidable six months later — nobody can act on it | One line of substance: what they said, what changed, what is next (Rule 5) |
| Migrating by importing a CSV into the new tool | Activity history, ids and relations do not travel in a contacts CSV; the timeline is what made the CRM worth anything | Export every object, map ids, run both read-only for one cycle (import.md) |
| Buying a CRM to fix adoption | The team stopped using the last one for a reason the new UI does not address | Cut fields and reduce the update to one minute; migrate only after that works (adoption.md) |
metrics.md).Credentials: CRM APIs need tokens. This skill reads them from the environment or the user's own secret manager and does NOT store, log, copy, or transmit them; nothing under ~/Clawic/data/ ever holds a token, and a pasted key is replaced by its <kind>:<locator> pointer before anything is written.
Local storage: contacts, deals, interactions and preferences stay in ~/Clawic/data/crm/ and ~/Clawic/data/contacts/ on this machine. This is personal data about third parties: it is minimized to what a follow-up needs, and deletion requests are honored across every copy including exports (privacy.md).
Guardrails: bulk edits, merges, imports and deletions are presented with their record count and require explicit confirmation, and run against an export written first (Rule 9). No contact is added to any outreach without the suppression check (Rule 8). No scraping of a platform that forbids it.
More Clawic skills, get them at https://clawic.com/skills/crm (install if the user confirms):
people — the personal address book: details, birthdays, staying in touch without a pipelineclients — delivering the work after the deal closes: scope, documents, project historyoutreach — writing the sequences and campaigns this pipeline feeds onsqlite — the query and migration mechanics behind a self-built CRMnegotiate — the conversation that moves a deal from Negotiation to Closed-wonPart of Clawic, the verified skill library. Get this skill: https://clawic.com/skills/crm.