Lubili
Built by Lubili

AI content pipeline with human approval

Scheduled research and drafting with AI, where nothing is published until a person approves it. One Next.js application runs the public site, a private CMS, a Telegram review bot and the scheduled jobs.

Built by Lubili
Type
Automated content platform
Role
Software engineer, designer and builder of the full system
Stack
Next.js 15, TypeScript, Supabase (Postgres, Auth, Storage), Google Gemini (Flash model, configurable), Zod, Telegram Bot API, Vercel and Vercel Cron
Content lifecycle overviewResearch with Gemini, validate with Zod, de-duplicate at Jaccard 0.6, queue by priority, draft with AI, then stop at pending_review. A person approves, edits, regenerates or rejects. Only then is the article published and the cache revalidated.ResearchGeminiValidateZodDedupeJaccard 0.6Queuepriority 1-10DraftAI first passpending_reviewHuman gateApprove, EditRegenerate, RejectPublishone tapRevalidatecache
AI does the first pass. Nothing goes live until a person approves it.Scroll sideways or pinch to zoom

Context

The problem: AI drafts need a human check

Writing useful articles takes research and drafting time. Publishing AI text without review is a risk to quality and reputation.

The system had to automate the routine steps, such as finding topics, avoiding repeats and drafting, and leave the final decision to a person, reachable from both a CMS and a phone.

Why review had to stay fast

Most AI content tools either publish without control or save little time.

The useful middle is automation that does the first pass reliably and makes review fast.

What we built

Inside the AI content pipeline

  • One Next.js application, two products

    The public site and blog, and a private CMS on its own subdomain, separated by host-based routing in middleware. The CMS is never indexed.

  • Topic research

    A weekly scheduled job asks Gemini for candidate topics as structured JSON. Each candidate is validated with Zod, then de-duplicated by exact slug match and by Jaccard similarity of significant words (threshold 0.6), against existing topics, every past article and the rest of the same batch.

  • A topic queue

    Ranked by priority (1 to 10) or a scheduled date, with a cap so the backlog cannot grow without limit.

  • Drafting

    On Monday, Wednesday and Friday, the highest-priority or due topic becomes a draft saved as pending_review. Nothing publishes automatically.

  • Review from Telegram or the CMS

    Every draft arrives in Telegram with Approve, Edit, Regenerate and Reject buttons. The same actions exist in the CMS.

  • One shared core

    The CMS, the Telegram bot and the scheduled jobs all call the same functions (runTopicResearch, generateDraft, publishArticleCore, revalidateArticlePages), so the routes cannot drift apart.

  • Publishing and cache

    Publishing invalidates both Next.js cache layers (the path and the articles tag), so the article, its category and the sitemap update together.

  • Security

    Google OAuth for one authorised admin, server-side authorisation on every action, Postgres row-level security with no public policies (service-role access only), secret-verified Telegram webhooks, bearer-secret scheduled jobs, and constant-time secret comparison.

  • Storage

    Cover images in Supabase Storage, with a public read, a 5 MB limit and PNG, JPEG and WebP only.

Engineering

Key decisions

  1. Structured output, not text parsing.

    Gemini returns JSON that matches a schema, and Zod rejects anything that does not, so a malformed response never reaches the database.

  2. Failure-safe state.

    A topic is marked as used only after its article exists. If generation fails, the topic stays available for the next run and an error message goes to Telegram.

  3. A race closed by design.

    Regenerate discards the old draft before creating the new one, and publishing refuses discarded drafts, so a stale Approve tap can never publish a draft that was replaced.

  4. Re-check before acting.

    The CMS button and the Telegram picker can show an out-of-date topic status, so generation re-checks eligibility at the moment it runs.

Results

Results of the AI content pipeline

  • Topic research and first drafts run on a schedule without anyone starting them.
  • Repeated or near-duplicate topics are filtered out before they reach the queue.
  • Nothing goes live without a person approving it, and approval takes one tap.
  • A failed generation never loses work or needs manual cleanup.
  • The whole system runs on low-cost managed services.

Architecture

The full system

For engineering readers. Everything in the drawing is described in the text above.

Content platform architectureFour kinds of user reach one Next.js 15 codebase on Vercel: visitors over HTTPS at the public site, one admin through Google OAuth at the private CMS on its own subdomain, the admin again through a secret-verified Telegram bot webhook, and Vercel Cron with a bearer secret for scheduled jobs. Host-based routing splits public routes from CMS routes. A shared core of services handles AI calls, the content pipeline, Telegram and scheduling. Google Gemini returns structured JSON. Supabase Postgres stores topics and articles with row-level security on and service-role access only. Supabase Storage holds article covers. Publishing revalidates the cache. The content lifecycle runs research, Zod validation, de-duplication by Jaccard similarity at 0.6, queue and draft, then stops at pending_review for a person, who approves, edits, regenerates or rejects before anything is published and the cache is revalidated.WHO USES ITVisitorsHTTPSAdminGoogle OAuthTelegram adminWebhook, secret verifiedVercel CronBearer secretENTRY POINTSPublic siteMarketing + InsightsPublic, indexedPrivate CMSOwn subdomainnoindexTelegram Bot APICommands/new /research /generate/schedule /queueScheduled jobsUTCResearch: Mon 06:00Drafts: Mon, Wed, Fri 08:00NEXT.JS 15 ON VERCELOne codebase, host-based routingPUBLIC ROUTESCMS ROUTESCORE SERVICESMarketing pagesInsights/insights/[category]/[slug]sitemap.xml, robots.txtSign-inOne adminTopicsQueue, priority, scheduleArticlesEdit, preview, publish, discardMediaCover uploadsTelegram serviceCommandsInline review buttonsChat allow-listSchedulingPicks the next topicDue topic first,then highest priorityContent pipelineShared by every routerunTopicResearchgenerateDraftpublishArticleCorerevalidateArticlePagesAI serviceStructured JSON outputSchema-checked with ZodCacheRevalidationunstable_cachetag: articlesStorageCovers5 MB maxPNG, JPEG, WebPcached readsuploadsSUPABASE POSTGRESRLS on, service-role onlytopicstableid, title, categorystatus: queued, scheduled, used, skippedpriority 1 to 10, scheduled_forarticlestableid, slug, content (Markdown)status: draft, pending_review, approved, published, discardedsource: ai, manualtopic_id references topicsGoogle GeminiFlash (configurable)Returns schema-shaped JSONstructured outputCONTENT LIFECYCLEAI does the work, a person decidesResearchGeminiValidateZodDedupeJaccard 0.6Queuepriority 1-10DraftAI first passpending_reviewHuman gateApprove, EditRegenerate, RejectPublishone tapRevalidatecache
Every route into the system calls the same core functions, and every draft stops at pending_review until a person decides.Scroll sideways or pinch to zoom

One Next.js codebase on Vercel serves the public site and the private CMS. The strip at the bottom is the content lifecycle: AI does the work up to pending_review, and a person decides from there.

Stack

Technology

  • Next.js 15
  • TypeScript
  • Supabase (Postgres, Auth, Storage)
  • Google Gemini (Flash model, configurable)
  • Zod
  • Telegram Bot API
  • Vercel and Vercel Cron

For your business

Similar problems Lubili can solve

Any repetitive drafting, classification or data preparation where AI does the first pass and a person approves: product descriptions, support replies, document and invoice intake, lead qualification.

Designed, built and run by Lubili.

Want AI to do the first pass, with your team still in control?

Book a free consultation, and we will look at where a first pass by AI would save time and where a person should stay in charge.

Free. No obligation. You get a one-page written summary.