← Lab
Showcase · CRM with an AI agent

The CRM I run my practice on.

I took 2nth.ai's contacts out of HubSpot and into a CRM I own: Postgres in South Africa, a Cloudflare Worker in front, and my mailbox, calendar and invoices joined up. Penny, an AI agent, keeps it current. She organises freely, and asks before anything leaves the business.

900+contacts, from HubSpot
24tools Penny can use
15 minmail and calendar sync
0emails sent unapproved
01 · The problem

The CRM only knew what I typed into it.

Contacts lived in HubSpot. The real relationships lived in my inbox and calendar, and what clients owe lived in my invoicing system. Contracts lived in my head. A SaaS CRM charges per seat to hold that data somewhere else. An AI assistant can't help with what it can't see.

So I built the CRM I tell clients to own. One Postgres database holds every source, and an agent works on top of it with tools that have clear limits.

02 · How it fits together

Sources in, an agent on top, a person in charge.

Sources

Everything flows in

  1. Website enquiries, with POPIA consent recorded as given.
  2. Microsoft 365 mail and calendar, read-only, every 15 minutes.
  3. Invoices and payments from my invoicing app, every hour.
  4. The HubSpot history, imported once.
The CRM

One record

  1. Contacts, companies, deals, contracts and tasks.
  2. Every email, meeting, note and change on one timeline.
  3. Matched by email domain and name, with no manual tagging.
Penny

The agent works it

  1. Reads mail, meetings, invoices and the pipeline.
  2. Adds contacts, updates deals, sets next steps and tasks.
  3. Every change is logged on the timeline as hers.
Me

I decide

  1. A morning brief by email: today's meetings, who's waiting, what needs attention.
  2. Anything that would leave the business waits in an outbox for my approval.
  3. Chat with Penny from any record.
As built, October 2026. Drag to pan; click a box for detail.Open full screen ↗
03 · What it does

Things a CRM should notice by itself.

Relationships

Who's waiting, who went quiet

Threads where the other side wrote last. Contacts who emailed often and then stopped. Steady two-way email with no deal yet, offered as a deal suggestion.

Pipeline

Deals with context

Each deal card shows the next meeting or when we last met. It opens to every field, notes, tasks, recent email and the timeline.

Contracts

Checked against invoices

Retainers, projects and licences matched to what was actually invoiced. Flags like "Not invoiced for October yet", overdue amounts, and "Ends in 30 days".

Calendar

Meetings, linked

Meetings with other people are linked to their contacts, companies and deals. Personal and private entries are never stored.

Capture

People from email

Everyone I've written to who isn't in the CRM, ready to add in one click with their past email and meetings attached. Or Bcc a tagged address and everyone on the email is added and tagged.

Enrichment

Filled in from signatures

Job title, phone, LinkedIn and company read from the signature in each person's own email. Empty fields are filled; anything that differs is offered, never overwritten.

Accounts

Primary and key contacts

Each company has one primary contact and a short list of key contacts, with everyone else folded away. Penny writes to the people who matter first.

Websites

Forms without HubSpot

Enquiry forms on four websites post straight into the CRM, checked by Cloudflare Turnstile and a rate limit, with an email alert that links to the new contact.

Consent

POPIA by design

Opt-in only comes from the person, never from me or the agent. Imported contacts start as "unknown". Bulk export is deliberately left out.

04 · Penny

An agent with limits you can read.

Penny works through 24 tools. Twelve can change data, and none can send an email; a draft goes to the outbox for me to approve. She is model-agnostic: calls go through Cloudflare AI Gateway, and an eval harness scores candidate models on the same CRM tasks before I switch.

Penny, the CRM agent

"What meetings do I have tomorrow?" Penny checks the calendar, names who's attending, links the meeting to its deal, and points out an attendee who isn't in the CRM yet.

Morning brief · Thursday Today 14:00 Payment flow review · 3 attendees · linked to an open deal Waiting on you 2 threads where the client wrote last, oldest 3 days Contracts Retainer: not invoiced for October yet Pipeline 1 proposal has had no reply for 9 days. Suggested next step: follow up Friday

Illustrative. Real briefs name real clients, so they stay private.

05 · Where it runs

Data in South Africa, on systems I own.

The database sits on a Google Cloud VM in the South Africa region, reached through a Cloudflare Tunnel. Nothing on it is open to the internet. The app is a Cloudflare Worker with its own passkey sign-in (Face ID or Touch ID, with an emailed code as the fallback), and it installs on a phone home screen. Mailbox sign-in tokens are encrypted at rest, and the mailbox and calendar connection is read-only.

  • Database: PostgreSQL on Google Cloud (africa-south1), reached through Hyperdrive
  • App: Cloudflare Workers (Hono), queues for the agent's longer jobs, cron for syncs
  • Sign-in: passkeys, with a one-time emailed code as the fallback. Only the website form endpoint is public
  • Mail and calendar: Microsoft Graph, delegated read-only access
  • Agent: any model through Cloudflare AI Gateway, with evals before each switch
PostgreSQLGoogle CloudCloudflare WorkersHyperdrivePasskeysTurnstileMicrosoft GraphAI Gatewaylistmonk
06 · Your version

The same build, on the systems you already use.

The CRM core stays the same: Postgres, a Worker, Penny. Each connector in the diagram can be swapped for the one your business runs.

LayerAs builtAlso connects to
Mail and calendarMicrosoft 365 (Graph, read-only)Google Workspace: Gmail and Google Calendar APIs, read-only, with push updates instead of polling
Team sign-inPasskeys, emailed codeMicrosoft Entra ID or Google sign-in for teams, with roles per person
AccountingMy invoicing appXero, Sage Business Cloud, QuickBooks, ERPNext
DatabasePostgreSQL on Google Cloud, South AfricaAzure Database for PostgreSQL (South Africa North), Amazon RDS (Cape Town), or your own server
MessagingEmail alerts (Resend)WhatsApp Business, Microsoft Teams or Slack alerts; WhatsApp threads on the timeline
Newsletterslistmonk, opt-in onlyMailchimp or Brevo, synced with consent kept in the CRM
AI modelsOpen models on Workers AIClaude, GPT or Gemini through the same gateway; Azure OpenAI for Microsoft-only shops
ReportingViews in the appPower BI, Looker Studio or Metabase, straight on Postgres
Coming fromHubSpot, imported onceSalesforce, Pipedrive, Zoho or spreadsheets: contacts, companies, deals and form history
Your version

Own your CRM, and let an agent work it.

A walkthrough of this build takes 30 minutes. A stack audit then maps your sources (Microsoft 365 or Google Workspace, accounting, ERP, support desk) and what moving off HubSpot would take. Keep the data where your compliance needs it, pick the model, and keep a person on every send.