Skip to content / Saltar al contenido
SHIP/002

Agendamelo: a multi-tenant SaaS that indexes without thin content

A mini-site plus online booking that any business in Chile can publish in five minutes.

Type
SaaS Product
Year
2026
Role
Founder / Product Engineer
Stack
Next.js · TypeScript · Supabase · Vercel
Status
Live
agendamelo.cl homepage: mini-site and online booking
(opens in a new tab)

By Published

Summary

I designed Agendamelo, a multi-tenant SaaS for independent professionals in Chile. Each business publishes a mini-site with online booking in five minutes. The platform generates a directory of 19 trades across 69 communes — about 1,400 URLs — with unique copy and live tenant cards, not empty templates. There is no commission per booking.

Problem

In Chile, independent professionals run the calendar inside WhatsApp. A client types. The professional answers. A reschedule reopens the thread. A no-show leaves nothing you can query. The next day, the same loop.

WhatsApp is a transport, not a public object. There are no published hours. There are no prices a crawler can read. There is no URL a client can forward without screenshotting a chat. When a web presence exists, it is an Instagram bio pointing at nothing Google can rank.

The trade widens the gap. A psychopedagogue, a speech therapist, a private tutor, a psychologist — they do not sell one slot. They sell a course of 8 to 12 sessions, usually the same weekday. In WhatsApp that is a thread. In a booking engine that only knows one-off slots, it is a missing table.

I built Agendamelo to take that work out of the inbox and put it on a URL. The tenant lives at agendamelo.cl/tu-negocio. The product H1 is a constraint: site plus online agenda in five minutes. Clients book without a reply. This case is territory three on this site — indexable multi-tenant SaaS — the same shape I sell as digital products. I designed it and I run it as Jorge Cortés, a product engineer in Santiago.

Constraints

The binding constraint is perceived time. If onboarding lasts more than five minutes, the professional stays on WhatsApp. A name, services with prices, and opening hours must be enough to publish. Gallery, reviews, team, FAQs, promotions can wait.

Prices are CLP. The geographic unit is the commune (comuna), not a generic city field. Demand is in Spanish. The live site compares Agendamelo with WhatsApp and Booksy. I am not using that table as sales copy. I read it as the localization list: pesos, named communes, and queries people actually type, such as a barbershop in Providencia.

No payment gateway for the end client. They book online and pay at the venue. The platform charges a subscription. Commission per booking is 0%. That avoids Mercado Pago and a remote deposit. The product answers no-shows with reminders and self-service rescheduling. I do not publish a no-show rate. I do not have one I can stand behind.

A 7-day trial with no card. After that, “Perfil Gratis”: the page stays published and stays in Google and in the directories; the booking engine, reminders, waitlist and recurring sessions switch off. The client writes on WhatsApp. Indexing a tenant that can no longer take a booking is a product decision.

Agendamelo does not sell domains. Web Pro connects a domain the business already owns. Otherwise the slug is the site. That slug has to be first-class, not an ugly fallback.

Architecture I designed

One Next.js app. The tenant is identified by slug. The public site is a tenant-scoped read. The dashboard is an owner-scoped write. The five nodes are the order in which the system produces a public object and then makes it findable.

Tenancy

Postgres on Supabase. Every business, service, hours row, appointment and review carries a tenant_id — or whatever the schema uses. RLS has to stop one project from reading another’s appointments. A leaked agenda between two salons is a product incident.

The public path /tu-negocio is the key the client sees. Behind it there may be a UUID. The directory lists slug, commune, trade, services and prices, not internal ids. The owner signs into the panel. The client does not create an account to book: they leave contact details, get email, and use a unique link to reschedule or cancel.

Onboarding and schema

Business name. Services with prices. Weekly hours. With those three, the public render already has an H1, a price list and a calendar. If any is missing, the page does not publish. Gallery, reviews, team, FAQs, promotions attach later without a migration.

Team — photo, specialty, certifications — ships on Estándar and Web Pro. Pro adds a second professional with their own hours and services. The client picks who they see. That is another row in the professional–service–slot relation, under the same tenant_id.

Recurring sessions belong on day one. The landing page targets weekly patients. The engine models a series: N sessions, a weekday, a time window, and the ability to move one instance without breaking the rest. A single slot cannot represent an 8-to-12-week treatment.

Directory

19 trades × 69 communes ≈ 1,400 URLs. Next.js materializes them with generateStaticParams. The product FAQ claims structured data, a sitemap, and the tags Google expects. The reasonable inference is SSG or ISR over that set. A live business count cannot wait for a deploy.

Trades observed: /barberias, /peluquerias, /psicologos, plus manicurists, aesthetics, tattoo artists, dentists, veterinary clinics, spas and massage. Trade × commune URLs in Title Case (/barberias/Santiago, /barberias/Providencia). Ñuñoa → Nunoa; Peñalolén → Penalolen.

The FAQ says the product works across Chile. The directory covers Santiago plus Viña del Mar, Concepción, Valparaíso, Temuco, Antofagasta and La Serena, and 287 more communes on demand. 19 × 69 is the generated matrix. I am not claiming the extra 287 are already built.

What keeps pages from being thin is composition, not volume. A trade page has trade copy, a Chile price-range FAQ, and live tenant cards with real services and prices. A commune page has a geographic intro that is not reusable — Santiago Centro names Plaza de Armas, La Moneda, the Metro, daytime worker and student traffic — plus filtered tenants and a FAQ with a live count (“Actualmente hay 1 barbería en Santiago Centro”). Shared boilerplate and unique blocks, at once.

An empty commune still needs a useful page: geographic context, how to book, trade FAQ. The live count can be 0. A URL that only says “no results” is thin. A URL that explains the trade in that commune and leaves a path for the first tenant is not.

Reminders

Email is automatic: confirmation on book, 24 hours out, 1 hour out. The owner also gets email on a new booking. Each message carries a unique link to reschedule or cancel. The slot frees immediately.

WhatsApp is sent from the panel, one click. It is on both plans. Web Pro has no monthly cap. Estándar has a cap; the number is not published. The client has to authorize the channel. I am not inventing the BSP, the per-conversation cost, or what happens when Meta does not deliver.

Making WhatsApp automatic on day one would have tied the $12,990 plan’s margin to templates, 24-hour windows and a vendor I still cannot name here. Email carries reliability. WhatsApp carries conversion in a market where the client actually opens the thread.

Stack and why

Next.js for both faces: the tenant site and the directory. App Router, static params, per-URL metadata, sitemap. A page framework is the right place for ~1,400 routes that have to exist as HTML.

TypeScript on the boundary between onboarding and schema. If services, prices and hours are the minimum contract, the type should say so. Loose JSON lets you publish a tenant with no hours.

Supabase because tenancy is a row problem. Postgres, Auth and RLS in one project. At this size, a second runtime only duplicates deploys. Isolation is won in policies.

Vercel for the edge: pull-request previews, TLS, cacheable HTML. The directory is many stable URLs. Reminders sit behind that edge; this case describes behaviour, not an invoice.

Technical decisions

One app and a slug in the path, not per-business subdomains. negocio.agendamelo.cl would have meant DNS, certificates and a longer onboarding. /tu-negocio publishes immediately. Web Pro connects a custom domain later, for people who already own one.

Directory pages as composition, not Mad Libs. A “Best [trade] in [commune]” template across 1,400 combinations is thin content. The price FAQ and live cards sit on the trade page. The geographic intro and the count sit on the commune page. The tenant query is the block that changes most. If it returns zero, the rest of the page still has to work.

Series inside the engine, not a cron that clones slots. Cloning breaks on holidays, on one moved hour, and on cancelling a single instance. Header plus instances belongs in the schema because the landing copy already promises 8-to-12-session treatments.

Email automatic, WhatsApp on a gesture. The panel click keeps cost under the owner’s control. The cap distinguishes Estándar from Web Pro. That is infrastructure pricing.

Free profile after the trial. Turn the engine off, leave the URL on. Google does not lose the page. The reason to pay is booking, not the existence of the site. The directory does not rot on day 8.

Zero commission. Payments would have been a different product. The SaaS charges for the software. The venue charges for the trade. Team (photo, specialty) is on both plans; Pro sells the second agenda and the domain.

What went wrong

1,400 URLs with the same skeleton look the same. If the only difference is the commune name in the H1, the page does not deserve an index. The risk was not failing to generate them. It was generating hollow ones.

Five-minute onboarding produces thin sites on day one. A name, three services and a schedule do not fill a gallery. If the directory only shows “complete” tenants, it has no cards. If it shows every tenant, the cards look poor. You have to decide what is publishable and what is coming soon.

The 19 × 69 matrix does not wait for a barbershop in every cell. A blank page is thin. A page that fakes a listing is worse. The live count has to be allowed to be zero without losing utility.

Series collide with the slot calendar: move Tuesday of week 3, cancel one instance, keep two Web Pro professionals from colliding. The one-off hour that works for a barbershop does not work for a speech therapist. The same engine has to serve both trades.

WhatsApp is the channel the client reads and it is variable cost. Automating it without a confirmed vendor and retries would have been inventing an SLA. Perfil Gratis, meanwhile, leaves indexed pages whose book button no longer books. If the directory treats them as a live engine, SEO works against the product.

How I fixed it

  1. 01/
    I split the directory into blocks with different sources of uniqueness. Trade copy and a price FAQ on the trade page. Geographic intro and a counted FAQ on the commune page. A live tenant query on both. Boilerplate covers navigation, CTAs and schema. It does not cover the body.
  2. 02/
    I dropped the publish threshold to the minimum schema. A tenant with name, services and hours enters the index. Gallery and reviews improve the card; they do not gate it. “Coming soon” is for someone who has not opened booking yet.
  3. 03/
    I treated an empty commune as a local trade page, not as an empty search. A count of 0 is a datum. The first tenant to publish in that cell lights up the card without regenerating 1,400 routes by hand.
  4. 04/
    I put the series in the schema: a treatment header, weekly instances, a unique email link to move or cancel one instance. The slot frees immediately. 24/7 hours and an 8-to-12-session course share the same availability table.
  5. 05/
    I left email on automatic and WhatsApp on a panel click. The expensive channel stays under the owner’s gesture. The WhatsApp cap distinguishes the plans without publishing a number the site does not publish.

Result

Indexable pages
~1,400
Trades × communes
19×69
Commission per booking
0%
Trial, no card
7 days

The product is live. Estándar: $12,990 CLP per month, or $64,000 per year — twelve months for about five. Web Pro: $19,990 CLP per month, two professionals, custom domain, uncapped WhatsApp, priority support with the person who built it, and featured directory placement. Seven days free, no card. After that, Perfil Gratis: the URL stays; paid booking turns off.

Zero commission per booking. The client books online and pays at the venue. I am not publishing paying businesses, revenue or traffic: they are not in the sources for this case. The engineering result is a directory that is not the same sentence with a name swapped, and an onboarding that does not ask for more entities than fit in five minutes.

Evidence

Captures from the live product, 2 September 2026.

agendamelo.cl home with the five-minute site-and-agenda H1 and a 7-day trial with no card
Fig. 01 — agendamelo.cl home. The H1 states five minutes; below it, the no-card trial and Estándar price.
Estándar and Web Pro plans on agendamelo.cl with published CLP prices
Fig. 02 — Published pricing. Pro adds a second professional, custom domain, uncapped WhatsApp and featured placement.
Barberías en Chile directory on agendamelo.cl with tenant cards showing services and prices
Fig. 03 — Trade page /barberias: own copy, live counts, and cards with real prices.

The live product is at agendamelo.cl.

What I'd do differently

I would publish the Estándar WhatsApp cap. Today Pro is “no cap” and Estándar is left with an invisible number: hard to support and hard to cite.

I would split “published” from “booking is live” in the directory. Perfil Gratis is the right product move; as an index it mixes tenants with an engine and without one. A badge and a filter on live agenda would have kept the count honest.

I would treat geographic copy as a product dataset — landmarks, Metro, traffic type — not text stranded on each page. I would measure no-shows with and without email, same trade, and not invent a percentage. I would document RLS and the WhatsApp vendor: they are TODOs here because the site does not name them.

Back to projects