Case study · A&O Relocation · Freelance, solo · 2026

A Telegram feed turned into a listing catalog - built and sold solo

A Valencia real-estate agency published every listing to a Telegram channel. I saw the problem before the client framed it and brought a working service instead of a pitch: a searchable catalog with filters, map and translations that fills itself from the channel.

3 weeksfrom prototype to a paying client
4 languagesru, en, es, uk - with a translation gate
10 miningest cycle from the channel
86test files: unit, parser parity, 13 e2e

Context

A&O Relocation is a real-estate agency in Valencia - rentals and sales. I had been their client, so I knew how the search looked from the other side of the channel. Nobody asked me to build anything: I built a working version on the agency's real listings first and showed it, and the conversation about money started from a running service rather than a deck.

Deal format: a build fee plus a monthly subscription for running and supporting the service.

Problem

For a client, a Telegram channel is a feed that lives for a day. You can't filter by district or price, you can't see listings on a map, you can't send a foreigner a link in their language, and a week-old listing looks exactly like a stale one. Months of accumulated listings were useless for finding a flat.

The constraint: the agency keeps working in Telegram as it always has. No second workflow, no manual data entry.

Approach

  • The channel stays the source of truth. A worker pulls new posts every 10 minutes and rebuilds the catalog from scratch. Raw posts are append-only; the catalog is fully derived, so a parsing mistake is fixed by changing a rule and rebuilding - history never gets corrupted. Only moderator decisions (hide a listing, pin it as current) survive a rebuild.
  • The parser's behavior is the spec. Parsing is deterministic, and every rule change has to pass a parity test against a reference corpus captured from the prototype - or consciously bump the parser version.
  • LLM only at the edges. In parsing, the model is advise-only: disagreements land in a suggestions table, and rules change only through a commit. The one place the model writes into the product is translation into three languages, and it sits behind a gate: numbers must match, the amenity list must match, and a Ukrainian text that stayed Russian is caught. A suspicious translation is flagged and never shown.
  • Deduplication of reposts by shared photos plus a structural fingerprint with an address check; the rejected alternatives are documented in the code, with numbers.

What I built

  • A public catalog for rent and sale: 13 server-side filter groups with live counters and a 4-level location tree down to the barrio, a clustered map, listing pages with a photo and video gallery and one-tap WhatsApp, call or Telegram to the listing's agent; four locales with enforced key parity.
  • An admin panel for the agency: hide or restore listings, rebuild the catalog, review the parser's suggestions, switch the LLM model without a redeploy, and see anonymous contact-click analytics.
  • An ingestion worker: a 6-stage cycle every 10 minutes - Telegram posts, deterministic parsing, geo-binding against 88 committed barrio polygons, repost deduplication, photo and video storage, catalog rebuild, translation stage.
  • One shared definition of what is public and what is stale (30 days since the last repost, unless a moderator pins it) - a contract between the worker and the site, not two copies of the same rule.
  • Operations: CI/CD on every green push, database backups, security headers with a nonce-based CSP, and alerts from hosting, error tracking and an uptime monitor routed into one Telegram chat.
  • Mid-project, a full move from a VPS to serverless Cloudflare (Workers, D1, R2) with a parity report before and after the cutover.
Privacy by construction: the contact-click counter stores only channel, placement, locale and listing id - no IP, no user agent, no cookies - and a test enforces it.

Result

  • A production service for a paying client, built and sold solo in 3 weeks from prototype.
  • A self-filling catalog: the agency keeps posting to Telegram and does nothing extra.
  • Its own deduplication, geo-binding and translation pipeline - not a wrapper around someone else's service.
Honest gap: I don't have business metrics yet. Traffic and inquiries were not measured before, and the contact-click counter has only just started collecting data.

Takeaways

  • Proactive work sells better than a presentation: a service running on the client's own data answers "will it work?" before anyone talks about price.
  • A deterministic core with the LLM at the edges is cheaper and easier to trust than handing parsing to a model. Here the model advises and translates; the code decides.
  • Moving to serverless is not a change of hosting but a change of data and deployment model - the platform's limits have to be known before the cutover, not discovered during it.
← Previous: IInsider Next: PRObankrotstvo →