Puerto Jiménez, Costa Rica

info@gexpsoftware.com

© 2026 Marcelo Retana

All work

How I built Alhena so the AI only drafts and a parent decides

I built a Spanish-language app that turns a parent’s emails into draft tasks. The choices that mattered: what the AI may do, what it may read, who decides.

Product
Alhena
Sector
Family task app
Location
Online
Year
2026
Visit live site(opens in a new tab)
The Alhena home page on a computer: logo, the headline “Clear tasks for school and home, for today”, a short description and a button to try the app.
The public home page at alhena.fun, in Spanish.

I changed one rule on day one

On 6 April 2026 I committed the Gmail sync at 17:46 and the first AI step at 18:01. That first version wrote the model’s output straight onto the board as tasks.

By 20:20 the same evening I had changed it. The commit says it in one line: “intake creates drafts for review instead of auto-creating tasks”. Now only a parent can turn a draft into a task. The cost is one extra step per notice. In exchange, nothing the model reads reaches the board until a person has looked at it.

If you are planning something similar, settle this on day one: who has the last word, the software or a person? That evening it took a drafts table, a review service, three routes and a review queue to make it real.

Parent mode on the Alhena home page: a card listing four tasks waiting for confirmation, all ticked, with an “Approve all” button and a note that only ticked items are approved.
Parent mode on the home page, with sample data: tasks marked done wait here, and only the ticked ones are approved.

Taking the smallest slice of an inbox

The app asks Google for one Gmail permission, read-only, and only when a parent chooses to connect the inbox, as a step separate from signing in. A permission like this is called a scope: the list of things you let an app do. This one lists reading and nothing else, and the code only calls Gmail’s read endpoints.

Then I narrowed what the app keeps. It follows one sender, which a parent can change in Settings, and mail from anyone else is dropped before it is saved. By default the first sync looks back 15 days, and a parent can pick another window. Later syncs ask Gmail only for what is new. Nothing runs in the background: a parent presses a button.

The trade-off is that mail does not arrive by itself. The project rules in the first commit had already ruled out scheduled sync for version one. On 16 April I reworked the landing copy and the parent screens so that Gmail is clearly optional: a family can start with manual tasks and add the inbox later.

The Alhena home page on a phone: logo, headline, short description, a note that linking Gmail is optional and asked for separately, and a button to try the app.
The home page on a phone: Gmail is optional and asked for separately.

Putting limits on the AI

The rules in my first commit already said two things: use structured outputs for email extraction, and never let a model write to the database directly. So I used OpenAI’s gpt-4o-mini with a fixed form instead of free text. That form is called a schema. The API asks the model to follow it, and my code checks the answer against the same schema again before using it.

Two small rules matter more than they look. Links in a draft must come from the original email, so any other link the model returns is thrown away and it cannot invent an address a parent might tap. And every result is stored with the model name and a trace number, so any draft can be traced back to the run that made it.

Dates took many commits. On 8 April, 16 of the 42 commits mention dates, time windows or overdue items. If your product reads dates from free text, budget time for them.

Three feature cards on the Alhena home page. The first says that after syncing Gmail the AI proposes tasks and the parent reviews them before they reach the board.
From the home page: the AI proposes tasks from the email, and the parent reviews them first.

From one household to many families

My first migration created ten tables, and none had a family column. Support for many families landed on 10 April, and it took two steps: a script that moved the existing data into a first family, then a migration that made the family ID mandatory on 14 tables. The order mattered: the migration’s own header says it may run only after the data has been moved.

Today 17 of the 20 tables carry a family ID. What matters more is where that ID comes from. It comes from the signed-in session, not from the request.

If a second customer is even possible, put the customer ID on every table from the first day. I added it on day five, with a backfill script and a careful migration. With other people’s data already inside, that step carries far more risk.

Closing section and footer of the Alhena home page: a “Start with Google” button and a note that data is kept separate for each family, with Google sign-in and no Alhena password.
The footer note: data kept separate for each family, Google sign-in, no separate password.

What I left out of version one

The first commit already carried my project rules: no notifications, no scheduled sync, a single Gmail inbox and a light theme only. Limits written down on day one give every “can we also…” a ready answer. For an owner, the list of what you will not build is as useful as the list of what you will.

What version one does have is a set of automatic checks. Every push to the main branch runs the unit tests (at the time of writing 1,321 pass and 5 that need a live database are skipped), a coverage report that reads 67% of lines, and a production build. A script also fails the build if code that talks to Gmail or OpenAI ends up in code that runs in the browser. The project rules keep both on the server.

The end-to-end tests drive a real browser at desktop and iPad sizes. They are meant to run on a developer’s machine and are skipped in the pipeline. That is a known gap.

What I'd tell an owner

  • Decide early who has the last word, the software or a person, and build the product around that answer.
  • Ask for the smallest permission you can, make it optional, and keep only the data the product needs.
  • If you might ever have a second customer, put the customer ID on every table from the first day.
  • Write down what version one will not do. Scope arguments become a lookup.
  • AI demos are quick. Dates, edge cases and automatic checks need their own time, so plan for them.

Stack

  • Next.js
  • React
  • TypeScript
  • Tailwind CSS
  • Auth.js
  • PostgreSQL
  • Drizzle ORM
  • Gmail API
  • OpenAI API
  • Vitest