Build Folk
KINDAreplaces $30/mosaves $360/yrback to the verdict
A relationship manager built around the fact that the useful context about a person is mostly things they told you, not things you looked up. Manual notes and interaction history are the core, enrichment is a bounded feature that carries an obligation, and every field knows where it came from.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the server and the mail metadata sync.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree locally
Why Fields with sources, identities and suppression records want real constraints and indexes.
Get it Docker or your package manager. open ↗
Verify
psql connects and a non-nullable foreign key can be enforced - accountfree
Why Phase 3 depends on what scopes the provider actually offers, and some only offer broad ones, which changes what you should store.
Get it A test account with the provider your contacts use, and read the scope documentation before building the consent screen.
Verify
A token with the narrowest useful scope returns message metadata - decidefree to draft; an hour of advice if this is commercial
Why Article 14 requires naming the actual source and the basis, and writing that after you have enriched three hundred contacts is writing it too late.
Get it Decide your basis, draft the notification naming each provider you will use, and write down your position on the disproportionate-effort exemption if you intend to rely on it. open ↗
Verify
A written notification text exists naming providers and the lawful basis - have readyfree
Why Duplicate matching, identity normalisation and timeline usefulness only become real at a few hundred contacts with messy overlapping data.
Get it Export from wherever you keep them now, including the duplicates and the entries with only a first name.
Verify
Two hundred contacts import, including at least five genuine duplicates
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- contacts(id, display_name, created_at, notification_due_at, notified_at) - fields(id, contact_id, key, value, source, source_detail, obtained_at) · source is manual, imported, enriched or from_them - identities(contact_id, kind, value_normalised, verified) · emails, handles and phone numbers used for matching - interactions(id, contact_id, kind, at, subject, direction, account_id) · metadata only, never message bodies - notes(id, contact_id, body, at, author) · the part that actually matters - enrichments(id, contact_id, provider, requested_at, fields_json, notification_required) - merges(id, kept_contact_id, merged_contact_id, at, reversible_until) · because merging is destructive and sometimes wrong
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_URLsecret | required | postgres://localhost:5432/folk | Personal data about identified people, most of whom did not give it to you directly. |
ENRICHMENT_PROVIDER | required | none | Named because the notification must name it. None is a valid and simpler configuration. |
ENRICHMENT_API_KEYsecret | optional | a provider key | Only if enrichment is enabled. Absent means the manual path, which has no obligation. |
NOTIFICATION_DUE_DAYS | required | 30 | From obtaining the data, or first contact if sooner. Thirty is the outer limit, not a target. |
MERGE_REVERSAL_DAYS | required | 30 | How long a merge can be undone. A wrong merge is usually noticed weeks later. |
STORE_MESSAGE_BODIES | required | never | Present as a constant so the answer is visible in configuration. There is no other value. |
The build, in order
Contacts, with every field knowing where it came from
You can always answer where a piece of information about someone came from.
More work than columns and the reason the rest of the build is possible.
Files
src/schema.sqlIn the database, not in the application. A field without provenance should not exist.
Files
src/fields.tsWhat someone told you is the valuable context. Enrichment is a garnish.
Files
src/contacts.ts
done when · tick each as it passeswatch out- A contact record as a flat set of columns loses the distinction between what someone told you and what you found out about them, and that distinction is the entire basis for deciding what you are allowed to do with it. Model fields as rows with a source, even though it is more work than columns.
Matching people without merging strangers
Duplicates are found and merged deliberately, never automatically.
Case, dots, plus addressing and phone formats. All of them cause false negatives.
Files
src/identity.tssrc/identity.test.tsPropose at most. Combining two people is not recoverable from notes alone.
Files
src/duplicates.tsBoth records retained. Reversal is the only defence against a wrong merge.
Files
src/merge.ts
done when · tick each as it passeswatch out- Merging two contacts because they share a common name silently combines two different people's notes and history, and there is no way to separate them afterwards because the information about which note belonged to whom is gone. Match on identities, propose on anything weaker, and keep merges reversible.
The timeline, from metadata you were given access to
You can see when you last spoke to someone without reading their mail.
And document why. A broad scope is a liability even when unused.
Files
src/oauth.tsSubject, time, direction. Never bodies, and assert that in the suite.
Files
src/interactions.tstests/no-bodies.test.tsPer account and per contact. Some correspondence should not be in a CRM.
Files
src/exclusions.ts
done when · tick each as it passeswatch out- Requesting a scope that grants full mailbox read access because it is the easiest one to get means your application holds the ability to read every message the user has, which is a liability you did not need and a permission prompt that will lose you users. Request metadata scopes, and if the platform only offers broad ones, store less than you were given and say so.
Enrichment, and the obligation that comes with it
Looking someone up creates a notification you are tracked against.
Naming the actual source is required. A generic phrase is not sufficient.
Files
src/enrich.tsOne month from obtaining, or first contact, whichever comes first.
Files
src/notifications.tsIt creates hundreds of obligations in one click. The absence is the design.
Files
src/enrich.ts
done when · tick each as it passeswatch out- Enriching every contact automatically on import creates hundreds of notification obligations in one action and no realistic way to meet them, which converts a convenience feature into a compliance problem you did not notice creating. Enrich per contact deliberately, and show the count of outstanding notifications so it cannot accumulate invisibly.
Their rights, which are not a feature requestproduct builder
A person asking what you hold about them can be answered in minutes.
Fields with sources, interactions, enrichments. This is what the question actually asks.
Files
src/subject-access.tsKeyed on identity, so a re-import cannot undo it.
Files
src/deletion.tstests/suppression.test.tsWhat changed, when and by whom. A correction is not an edit.
Files
src/corrections.ts
done when · tick each as it passeswatch out- Deleting a contact without recording the deletion means the next import from the same source silently recreates them, which is not deletion and is exactly what the person asked you not to do. Keep a suppression record keyed on the identity so a re-import is blocked rather than welcomed.
That is the whole plan for Folk. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- The enrichment data itself.
- Sales pipeline management with forecasting.
- Bulk outreach and sequences.
- Legal advice.
- collaborative contact enrichment, integrations, messaging, and polished relationship workflows
- automatic email and calendar capture
- data enrichment
- large integration ecosystem
- enterprise reporting
- Reminders to be in touch, which is what a relationship manager is actually for
- Shared contacts between a small team, which adds a permissions model
- A weekly digest of who you have not spoken to, built from the timeline
Need the files? The project pack on the verdict page hands your agent the whole brief · more crm.