All products

Social Marketing AI Agent

In active development

An AI system that learns a brand, plans the work, writes and illustrates the posts, and then waits for a person to approve anything that goes public.

Role
Architect and sole engineer
Live at
smai.angabani.com
  • FastAPI
  • Python
  • React Native
  • Expo
  • TypeScript
  • PostgreSQL
  • pgvector
  • Supabase
  • Gemini
  • Cloudflare Workers AI
  • n8n
  • MCP
  • OpenTelemetry
  • GitHub Actions

Architecture, in one picture

Client, Expo / React Native Web, iOS, Android, one TypeScript codebase typed REST client Backend, FastAPI modular monolith one deployable, explicit module bounds identity brand brain strategy content analytics platform ops publishing approvals is_current_approval() approved, scoped, not expired evaluated live, fails closed PostgreSQL system of record + append-only audit pgvector brand knowledge chunks + embeddings Object storage generated media served as signed URLs Provider ports llm, embeddings, image social, storage swappable Gemini Workers AI FLUX.1 social, mock authorisation path, no provider call happens before this passes Dimmed = contract-faithful mock, not yet a real provider.
Every outward action funnels through one gate. is_current_approval() is computed at call time, not cached from generation time, so revoking an approval or letting one expire stops a publish that was already queued.
Brand context Strategy human approves Weekly plan human approves Copy + image generated, cited Approval inbox risk flags, expiry changes requested, terminal approved Re-run output is immutable Publish attempt gate re-checked here new lineage Metrics ingested per post Insight recommendation feeds the next cycle human gate terminal state human re-runs generation manually every transition writes an AuditEvent
changes_requested is terminal on purpose. Generated content is append-only and versioned, so a reviewer never edits model output in place. They reject it and re-run generation, which produces a new lineage and keeps the audit trail honest about what the system actually produced.

What it does

Social marketing is mostly repetitive work. Someone reads the brand guidelines, decides what to post this week, writes it four different ways for four platforms, finds an image, and schedules it. Every week, forever.

This does that work. It ingests the brand’s own material and keeps it in a vector store, so what it writes comes from real source documents rather than a general impression of the brand. It proposes a plan, drafts the copy, adapts it per platform, generates branded images, and queues everything for review.

Then it stops. Nothing reaches the public until a person approves it.

How it is built

A modular monolith on FastAPI, not microservices and not a chat wrapper. One deployable with clear module boundaries: identity, brand knowledge, strategy, content, approvals, publishing, analytics.

Layer Choice Why
Backend FastAPI, Python One deployable, clear module bounds
Client Expo, React Native, TypeScript Web, iOS and Android from one codebase
Database PostgreSQL via Supabase Transactional source of truth
Vectors pgvector, in the same Postgres Retrieval without another system to run
Text Gemini, with a Cloudflare Workers AI adapter Both behind one llm.generate port
Images Cloudflare Workers AI, FLUX.1 Behind an image.generate port
Tools MCP Scoped access that cannot bypass app authorization
Orchestration n8n, self-hosted Publish and schedule jobs
Delivery GitHub Actions, Render, Cloudflare Pages Lint, types, tests, security scans, eval gates

The design rule behind all of it: every provider is replaceable. Text generation, embeddings, image generation, storage and each social platform sit behind a port. Swapping Gemini for something else is a config change, not a rewrite. Nothing is a permanent bet on one vendor.

The rules it will not break

This is the part I actually care about, and the part most AI products skip.

It fails closed. Publishing, rescheduling and deleting all require a live, scoped, unexpired human approval. When that approval is missing or has expired, the system does nothing. It never decides an action is probably fine.

It does not fabricate success. A provider action is not reported as done until it has been confirmed and reconciled against what the provider actually returned. A queued post that silently failed is worse than one that never sent.

Every claim carries a citation. Brand profile extraction and generated copy trace back to the source chunk they came from, so a person reviewing a draft can check where a claim about the brand actually came from.

Prompt injection is treated as a real threat, not a footnote, anywhere user-supplied or web-scraped content reaches a model call.

Everything is logged. Every state change, approval decision and publish attempt records who did it and when.

CI is a gate, not a formality. Lint, type check, the full test suite and AI eval gates all run before anything merges, including a pass that stops the commit gate itself from failing open.

Where it is now

Built and tested: brand ingestion and RAG, brand profile extraction with citations, strategy and weekly planning, copy generation with platform adaptation, branded image generation, and the approval inbox with risk flags, citations and expiry.

In progress: real social connectors starting with Threads, scheduled publishing, and the n8n orchestration layer, which is architected but not yet integrated.

I am deliberately precise about that split. The interesting claim here is not that it works, it is that it refuses to act when it should not, and that is only worth anything if I am honest about what is finished.

Next

See the rest of the work, or get in touch.