Case study · 4 min read
OMO AI
An AI marketing assistant for small businesses, built with a team.

- My role
- Contract engineer on a small team: authentication and onboarding, settings, billing and pricing UI, the role-based admin console, the landing page and analytics and integrations screens, and I worked on the AI agent (LangChain and LangGraph) with the lead.
- Timeline
- Jul 2025 - Dec 2025
- Stack
- Next.js, TypeScript, NextAuth, Prisma, PostgreSQL, Stripe, LangChain, LangGraph, GSAP, Docker, Turborepo
- Credit
- Built with Abdulrasheed Akintola, project lead, and the OMO AI team
OMO AI analyses a business's content, ads and audience, then tells its owner what to post, when to post and how to grow. It is a team product. I joined as a contract engineer and worked on accounts and onboarding, settings, billing screens, the role-based admin console, the landing page and analytics screens, and I worked on the AI agent (LangChain and LangGraph) with the project lead.
- 1AI agent with a human approve-or-deny step, which I worked on with the lead
- 5settings areas: general, profile, notifications, security, billing
- 5admin modules: home, users, subscriptions, team management, roles
The problem
Small business owners rarely have time to work out what to post, whether their ads work, or who their audience is. OMO AI puts an assistant in front of that data: ask a question in plain language and get analysis and next steps.
A product like that also needs the unglamorous parts to be solid: accounts, billing, settings, and an internal console for the team running it.
Constraints
- A shared codebase. Three engineers on one monorepo, with a shared database schema that changes often.
- Security. An admin console that can suspend users and change roles has to be locked down properly.
- Moving requirements across a long build, with several rounds of rebasing to keep branches in step.
- Confidentiality. This is a company product, so this page stays at a level that is safe to publish.
Approach
The product is a Turborepo and pnpm monorepo with a Next.js web app and a shared UI package, using server actions, NextAuth for sign-in, Prisma on PostgreSQL, Stripe subscriptions and a docs site. The assistant is a LangGraph agent with tools, memory kept in Postgres and a streaming chat.
I worked on the AI agent alongside the lead, and I built the account, settings, billing, admin and landing areas described below.
A high-level view only; internal details are intentionally left out.
In the app
Key decisions
01Permissions live in the database and are enforced on the server
- Context
- An admin console where roles are hard-coded, or only hidden in the UI, is one edited request away from a breach.
- What I chose
- Roles are stored with a permissions object per module. Every admin server action loads the caller's role and checks the permission before doing anything, a super admin gets all permissions, and users without access land on a no-access page. The console also refuses to load on a phone.
- What I passed on
- Hard-coded roles in the code, or hiding buttons in the interface only.
- Trade-off
- A database lookup on each action, in return for roles that admins can change without a deploy.
02Sign-in that handles the awkward cases
- Context
- Accounts are the front door: sign-up, verification, recovery and third-party sign-in all need to be right.
- What I chose
- Email and phone verification with one-time codes, Google sign-in (added in September 2025), forgot-password and reset flows, and a separate admin sign-in and invited-admin sign-in with their own one-time codes. A location lookup during sign-up fills in country details.
- What I passed on
- Password-only accounts.
- Trade-off
- More flows to build and test, many of which most users never see.
03Settings as a module, not a page
- Context
- Profile, notifications, security and billing grow independently.
- What I chose
- Settings is split into general, profile, notifications, security and billing sections, each with its own route and actions, with theme preference synced to the account. Billing has a pricing page and a subscription model behind it.
- What I passed on
- One long settings page.
- Trade-off
- More routes, but each one stays small and testable.
04A person can approve what the agent wants to do
- Context
- An assistant connected to business accounts should not act on its own where a mistake would matter.
- What I chose
- The agent has approve and deny endpoints, so a tool run can wait for a person, and its memory and checkpoints are kept in Postgres so a conversation can resume. I worked on this with the lead.
- What I passed on
- Let the agent run every tool autonomously.
- Trade-off
- More friction for the user in exchange for control over actions that matter.
The check happens on the server for every action.
Challenges
- Keeping branches in step. A busy three-person repo meant frequent rebases and merge conflicts around a shared Prisma schema.
- Getting permissions right. The admin console went through several passes: fixing role access handling, an access modal and suspended-user flows.
- Scope. Landing page, analytics, integrations and admin work were all moving at the same time.
Results
OMO AI is live. My part shipped the account and onboarding flows, the settings module, billing and pricing screens, the role-based admin console with roles and team management, the animated landing page, and the analytics and integrations screens.
As a company product there are no business metrics I can share, so I will not quote any.
What I would do next
- Add automated tests around the permission checks, which are the highest-risk code in the console.
- Keep an audit trail of admin actions such as suspensions and role changes.