Chiedza answered 3,572 customer messages in one month.
Chiedza is an AI sales assistant for a solar company. From 9 September to 8 October 2026 it handled 3,572 WhatsApp messages and logged 361 leads straight onto the sales board.
3,572
messages handled
361
leads created
30
days
Chenai does the same job for Slab Sales Africa, and can find and share any of 3,559 past social posts.
02 · Infrastructure
One server I run myself, under every bot.
An Ubuntu server with a firewall, reverse proxy and automatic TLS. It runs self-hosted n8n, Supabase and the WhatsApp gateway for several client numbers, plus Docker staging copies of client stores.
03 · Websites
20+ client projects, built for the client's own hosting.
From a Next.js storefront for TV Sales & Home with about 2,000 products, to Triangle Country Club, live at trianglecountryclub.com. Fast on a phone, and easy for the client's team to keep up to date.
Systems that do real work
The jobs I enjoy most sit behind the website: stock, orders, approvals, customer messages and the records that prove who did what.
In build, Oct 2026 to Mar 2027
TV Sales & Home e-commerce rebuild
A new storefront for TV Sales & Home, a furniture and appliance retailer in the Axia group. Next.js on Vercel in front of WooCommerce, with categories kept in step with the company's OpenBravo ERP.
~2,000 products
1,755 live URLs mapped so search rankings carry over
Unit and end-to-end tests, security scans, and a feature, develop, staging, production release path
feature
develop
staging
main
Live
Chiedza, an AI sales assistant for solar
Answers enquiries on WhatsApp for a solar company and logs each lead straight onto their monday.com sales board.
3,572 messages and 361 leads from 9 Sep to 8 Oct 2026.
Live
Chenai, a WhatsApp sales assistant
Answers product questions for Slab Sales Africa, shares links to any of 3,559 past social posts, and hands warm leads to a sales rep. A second model checks every hand-off before it goes through.
Replaced a 161-node workflow with one path. Social posts now go out only after the owner approves them on WhatsApp.
Planned, docs complete
ZimTickets
Event ticketing for Zimbabwe on web, Android and iOS. Organisers list events, buyers pay by EcoCash, OneMoney, InnBucks, ZimSwitch or card, and tickets scan at the door.
Deployed
Team task portal with WhatsApp reminders
Workspaces, projects, tasks and assignees on PostgreSQL. The database itself sends reminders to people's WhatsApp when work is due.
Assistant live, site in final review
Masvingo Chicks: site, agent network and poultry assistant
React site with a 3D map of sales agents, a PHP and MySQL agent application system, and a WhatsApp assistant in TypeScript on Claude that answers poultry and price questions, behind a send guard.
Websites
Built for the client's hosting, fast on a phone, and easy for their team to keep up to date. Hover a site to see it on mobile.
Redesign for a fertiliser manufacturer, written with no frameworks at all.
Also
AfriPFC, Penhalonga Energy, SEP's solar store, Webtex, and the Tandamanzi careers page.
Some of this work was delivered with Single Thorn Studio and Rhino Creative. KW Enterprises design by Samuel Smith.
The side of code
Short pieces from real projects, to show how I think about the parts that can go wrong.
The client's rule was that no customer ever gets a message they did not ask for. So every send passes one gate, and there is no override flag for a bug or a bad prompt to flip.
// guard.ts (Masvingo Chicks WhatsApp assistant)
async function dmDecision(redis: Redis, number: string): Promise<SendDecision> {
if (!/^\d{8,15}$/.test(number)) {
return { allowed: false, reason: "not a personal number" };
}
if (isStaff(number)) return { allowed: true, reason: "staff" };
const seen = await redis.get(inboundKey(number));
if (!seen) {
// The branch that makes an unsolicited customer message impossible.
log.warn("blocked outbound to a number with no open inbound window", { number });
return { allowed: false, reason: "no inbound message inside the reply window" };
}
if (expired(seen)) return { allowed: false, reason: "reply window expired" };
return { allowed: true, reason: "reply" };
}
The WhatsApp bot reads a customer's message and fills in the details. It never writes a price. Every figure comes from one pure function that the website also uses, with its own tests.
// engine.js (The Draught Man): no I/O, runs in the browser and in the bot
export function planKegs({ guests, occasionId, beersPerGuestOverride, kegsOverride }) {
const occasion = OCCASION_BY_ID[occasionId];
const perKeg = servingsPerKeg();
const recommendedPerGuest =
beersPerGuestOverride != null ? beersPerGuestOverride
: occasion ? occasion.recommendedBeersPerGuest : 4;
const recommendedKegs = Math.max(1, Math.ceil((guests * recommendedPerGuest) / perKeg));
const kegs = kegsOverride != null ? Math.max(1, Math.floor(kegsOverride)) : recommendedKegs;
return { perKeg, recommendedKegs, kegs, servingsSupplied: kegs * perKeg };
}
Customers started receiving each other's quotes. The database lookup combined its two conditions with OR by default, so every contact matched the same open conversation. The fix was one setting, applied to every filtered lookup so it cannot come back.
// Find Conversation, simplified. Before: the default, invisible in the export
{ "conditions": ["contact_id = :contact", "is_active = true"] }
// matched: contact_id = :contact OR is_active = true// after
{ "conditions": ["contact_id = :contact", "is_active = true"],
"matchType": "allFilters" }
// matched: contact_id = :contact AND is_active = true
I tested whether copying top crypto traders could pay after fees. The answer was no, so I switched it off and wrote down why, before any real money went in.
# Research summary (copy-trading study, Sept to Oct 2026)
wallets on the leaderboard 47,095
wallets with enough history 5,636
survived multiple-testing check 11
basket paper-traded for 2 weeks 10
entry batches 35
mean result per trade, after fees -1.25%# Verdict: their edge is real, but not catchable an hour late. Stopped.
Automations and the servers under them
Automations
Daily post approval
Picks the next product photo, writes a caption, frames it, and posts only after the owner taps approve on WhatsApp.
Video posting
Overlays a branded frame on product clips with FFmpeg and publishes to Facebook and Instagram Reels.
Guest reviews import
Moves reviews from a team spreadsheet onto a WordPress site every hour, with no copy and paste.
WhatsApp to monday.com
An AI assistant for a solar company that logs enquiries straight onto their sales board.
Agency job intake
Slack commands that turn a client brief into a job card and route it to design, film or edit.
What I run myself
Ubuntu server with a firewall, reverse proxy and automatic TLS
cPanel hosting, PHP and MySQL for sites that need to live there
Tools I use most
TypeScript
React
Next.js
Node
PHP
PostgreSQL
MySQL
WordPress
WooCommerce
n8n
Docker
Python
Playwright
Claude and Gemini APIs
How I work
Your rules go in the code
If something must never happen, like a message to a customer who did not ask or a price the AI made up, I build it so it cannot happen, not just so it usually does not.
Every change has a trail
Work moves through version control and a staging copy before it goes live. You can see what changed, when, and who changed it.
Customer data stays put
Staging uses product data only. Real customer records stay where they belong and are never copied to a test server.
Plain updates, written down
Scope, decisions and open questions live in a document you can read, so nothing depends on someone's memory of a call.
Have something that needs building?
Tell me what the business does and where it hurts.