contact-capture

Pocket CRM

The app lives here: https://contact-capture.matthias-homan.workers.dev/ (open it in Chrome on the phone; Chrome menu → Install app; the first time open it as https://contact-capture.matthias-homan.workers.dev/#t=<your access token> so the phone learns the token). The address https://mhoman05.github.io/contact-capture/ is only the old GitHub Pages page that shows this README; it is not the app.

(The repository and the Worker are still called contact-capture; only the name you see changed.)

Share a business card photo, a screenshot of an email signature or a LinkedIn profile, or just selected text, from your Android phone. Claude reads the details and files the person in your Notion Work CRM.

Android share sheet → this app → Cloudflare Worker → Claude (reads the image) → Notion People / Companies / Interactions

One Cloudflare Worker does everything: it serves the app (the public/ folder) and it runs the API (worker/). The app and the API share one address, like https://contact-capture.matthias-homan.workers.dev, so there is no GitHub Pages, no cross-site setup, and no clash with other apps on your phone. Cloudflare’s free plan is enough.

What it does with each contact

One-time setup

Five secrets go in the repository’s Settings → Secrets and variables → Actions (the Actions tab, not Dependabot or Codespaces).

Secret Where to get it
NOTION_TOKEN Notion → Developer tools → Connections → New connection → copy the API token. Then open the Work CRM page → ••• → Connections and add that connection, so it can see People, Companies and Interactions.
ANTHROPIC_API_KEY console.anthropic.com → API keys. This is separate from a claude.ai subscription.
CLOUDFLARE_API_TOKEN dash.cloudflare.com → My Profile → API Tokens → Create Token → template Edit Cloudflare Workers.
CLOUDFLARE_ACCOUNT_ID Cloudflare dashboard → Workers & Pages, shown on the right.
CAPTURE_TOKEN A password you invent (letters and numbers only), for example the output of openssl rand -hex 16. Only someone with it can use the service.

Then:

  1. Push to main. The workflow Deploy worker runs the tests and deploys. Open the finished run → deploy → the step Run cloudflare/wrangler-action; its log ends with the address, like https://contact-capture.matthias-homan.workers.dev. (If it asks for a workers.dev subdomain, open Workers & Pages in the Cloudflare dashboard once to create it, then re-run.)
  2. Check it answers: open <that address>/health in a browser. You should see {"ok":true}.
  3. On the Android phone, open <that address>/#t=<CAPTURE_TOKEN> in Chrome. The token is saved on the phone and removed from the address bar.
  4. Chrome menu → Install app, then open the installed app once so it can receive shares.

Changes to the files in public/ alone do not trigger a deploy; after copying new files, open Actions → Deploy worker → Run workflow (branch main) to publish them.

Optional: Save to phone contacts (Google Contacts setup)

One-time, about 15 minutes. It gives the service permission to add contacts to your Google account only (it cannot read other things). Keep every value below in your password manager and never paste them into a chat.

Part A, Google

  1. Open console.cloud.google.com, sign in with the Google account your phone’s Contacts use, accept the terms. Top left, open the project picker → New project → name it Pocket CRM → Create, and make sure it is selected.
  2. Menu (☰) → APIs & Services → Library → search People API → open it → Enable.
  3. Menu → APIs & Services → OAuth consent screen (it may be called Google Auth Platform) → Get started. App name Pocket CRM, support email yours → Next. Audience External → Next. Contact email yours → Next → tick the agreement → Continue → Create.
  4. In the left menu open Branding and fill in Application home page (https://contact-capture.matthias-homan.workers.dev/), Application privacy policy link (https://contact-capture.matthias-homan.workers.dev/privacy.html, a page the app serves) and, if it asks, the authorized domain, then Save. Then open Audience → Publish app → Confirm. (If you skip this the permission stops working after 7 days. If the button is greyed out, hover over it: the tooltip names what is still missing.)
  5. Left menu → Clients → Create client. Type Web application, name Pocket CRM. Under Authorized redirect URIs click Add URI and enter https://developers.google.com/oauthplayground. Create. A box shows the Client ID and Client secret; copy both.
  6. Open developers.google.com/oauthplayground. Click the gear (top right), tick Use your own OAuth credentials, paste the Client ID and Client secret, close the box.
  7. In the left list, scroll to the box Input your own scopes, type https://www.googleapis.com/auth/contacts and click Authorize APIs. Choose your account. On “Google hasn’t verified this app” click Advanced → Go to Pocket CRM (unsafe) (it is your own app) → Continue → Allow.
  8. Back in the Playground, Step 2: click Exchange authorization code for tokens and copy the Refresh token (it starts with 1//).

Part B, Cloudflare

  1. dash.cloudflare.com → Workers & Pages → contact-capture → Settings → Variables and Secrets → Add. Type Secret. Add three, one at a time: GOOGLE_CLIENT_ID (the Client ID), GOOGLE_CLIENT_SECRET (the Client secret), GOOGLE_REFRESH_TOKEN (the refresh token). Click Deploy after the last one.
  2. In the app: People → open a person → 📱 Save to phone contacts. After a moment the person is in your phone’s Contacts. (Android’s Settings → Google → Contacts sync must be on, which it normally is.)

If the permission ever stops working (the app says Google refused it), repeat steps 6 to 9 and replace only GOOGLE_REFRESH_TOKEN.

Using it

The app shrinks photos to 1600 px before sending, so uploads are small even on mobile data.

People: your whole CRM on the phone

The People tab shows everyone in your Notion People database: a list you can search (name, company, role, email, phone, notes, event; accents are ignored), sort (A–Z with letter headings, or Newest first), and filter (To review, Follow up, Duplicates, and by tier).

Tasks: your Notion Task Database on the phone

The Tasks tab is your Notion Task Database. Two lists plus follow-ups:

Connecting Notion to the Task Database (one-time)

Your Task Database lives under a different Notion page (“DATABASES”) than the CRM, so the app’s Notion connection may not see it yet. In the app open ⚙ → Check Notion connections: it names the Notion connection the app uses, lists every database the app needs with ✓ or ✗, and lists every database that connection can see. For each ✗: in Notion open that database (or its parent page), tap ••• at the top right, choose Connections and add the app’s connection (the same one you created for the CRM), then check again. The Meetings database (shown in a person’s history) may need the same.

Companies (inside the People tab)

At the top of the People tab, the switch **People Companies** shows your Notion Companies database.

No signal, undo, phone contacts and duplicates

Next meetings and the briefing

The Add screen starts with Next meetings: the meetings of your Notion Meetings database from now until a week ahead (a meeting stays listed until half an hour after it ends), with time and attendees. Tap one for the briefing: for every attendee their role and company, the History summary, last contact, open tasks, the last interaction that has already happened, and call / WhatsApp / email / LinkedIn buttons; tapping a name opens the person. At the bottom, After the meeting: write what was discussed (it is appended, dated, to the meeting’s Topics Discussed) and optionally set a follow-up date for all attendees. Notes and dates work without signal and are sent later. Attendees come from the meeting’s Attendees field, so the briefing is only as good as that link: a meeting without attendees says so (the scheduled task CRM: link meeting attendees fills it from your calendar). The list is kept on the phone for when there is no signal.

Fingerprint lock

Settings → App lock → Lock People, Tasks and My QR with my fingerprint. Add stays open (to scan cards fast); People, Tasks and My QR show a lock screen until you touch the fingerprint sensor (or give the phone’s PIN when it asks). You choose how long the app stays unlocked after you leave it (at once, 5 minutes, 15 minutes, 1 hour; default 5 minutes); an app that was closed and reopened is always locked; Lock now locks at once. The Next meetings card hides who you are meeting while locked. It uses the phone’s own fingerprint or screen lock (WebAuthn): there is nothing new to remember. It keeps people who pick up your unlocked phone out of your network; it does not encrypt what the browser has stored. If the fingerprint stops working, Can’t unlock? Reset removes the lock and the access token from the phone, so you must enter the token again.

Backup and export

⚙ Settings → Export everything reads all your databases and saves one zip file (pocket-crm-backup-<date>.zip; on phones that allow it, the share sheet opens so you can send it straight to Google Drive, otherwise it lands in Downloads). Inside: spreadsheets (people.csv, companies.csv, tasks.csv, interactions.csv, meetings.csv, events.csv), contacts.vcf (everyone as contact cards, importable into Google Contacts at contacts.google.com → Import), backup.json (everything with ids) and a README. Tasks are exported in full, including old finished ones. Settings shows when the last backup was made, and People shows a note when there has been none for 30 days (24 people or more). The data itself lives in Notion; deleted pages stay in Notion’s trash for 30 days, so this file is your own independent copy. It is generated on the phone from what the service reads from Notion; nothing is stored anywhere else.

My QR card (sharing your own details)

The My QR card tab turns your details into a QR code that the other person scans with their normal phone camera. Their phone offers Add contact, with no app, account or internet needed on their side.

The Worker reads and edits that one database only (NOTION_ME_DB in worker/wrangler.toml); it refuses to edit any other Notion page.

New phone, or lost phone

Nothing is stored only on the phone except the access token and your “send straight away” choice.

  1. Install the app: open the Worker address (https://contact-capture.matthias-homan.workers.dev) in Chrome, then menu → Install app.
  2. Open <that address>/#t=<your CAPTURE_TOKEN> once. The token is saved on the phone and removed from the address bar. (Keep the token in a password manager. It is also in GitHub: repository → Settings → Secrets, though secrets can be replaced but not read; to get a new one, change CAPTURE_TOKEN there and re-run the deploy.)
  3. Open My QR card. Your details come back from Notion.
  4. Nothing to redo for Save to phone contacts: the Google permission lives in Cloudflare (the three GOOGLE_… secrets), not on the phone. Make sure the new phone is signed in to the same Google account.

The app itself is in GitHub and Cloudflare, and your contacts and details are in Notion, so there is nothing else to restore.

Privacy and cost

Development

cd worker
npm ci
npm run typecheck
npm test            # 107 tests: matching rules, Notion writes (against an in-memory Notion), access control, serving the app

Run everything locally with npx wrangler@4 dev, putting the three secrets in worker/.dev.vars (git-ignored), then open http://127.0.0.1:8787/.

The code is tested against a simulated Notion, a stubbed model and the real Cloudflare runtime running locally. The first real run needs checking by hand: share one card and look at the result in Notion.

My sharing details: a row without a Type

Each row in the Notion database My sharing details needs a Type (Phone, Email, Website, Address, Company, Role …) and at least one Group. If a row you added by hand has no Type, the app guesses it from the value (something with an @ is an email, a link is a website, digits are a phone number); if it cannot guess, the row is listed as Not shared with a reminder to set the Type. Only the groups you tick on the My QR tab are shared (by default just Professional).

Contact files: format notes

Contact cards (QR codes, share links, downloaded .vcf files) are vCard 3.0. Types are written the way Apple’s own files do it (TYPE=INTERNET;TYPE=HOME, one parameter each). A comma-joined list (TYPE=HOME,INTERNET, used by versions up to v18) made Apple Contacts on the Mac stop reading the card at the email line, so only the name and first phone number arrived. The share-link service rewrites such lists on the fly, so links made by older versions open fully too. Links and websites carry a label (LinkedIn, Website …).

The tab bar

Add | People | Tasks | My QR and the settings button sit in one bar fixed at the top of the screen. It stays put while you scroll a long list, and the letter headings of the People and Companies lists stick right below it, so one tap switches tabs from anywhere in the list.