Giftly · 2025 – present

Giving a one-shot product a memory

Primary author of the calendar, contacts, and gift recommendation systems that turned a one-shot purchase flow into a product with a reason to return.

PublicNamed and linkable, with figures as reported.

The situation

The product had a ceiling. Every gift began with a buyer who already knew what they wanted and who it was for; nothing in the system knew anything about the recipient — so nothing gave a buyer a reason to come back before the next occasion they remembered on their own.

The constraints

  • The people being remembered are mostly not users. A recipient with a birthday, interests and a city usually has no account and never will, so the model had to hold people rather than logins.
  • A reminder is a promise. "Why did this not send" has to be answerable, which constrains how the pipeline is allowed to work.
  • A recommendation has to be explainable to someone who disagrees with it.

The call

Materialise reminder instances instead of computing what is due on the fly. A recurring event with a "3 days before" rule generates real rows on a rolling window, topped up as they are consumed. Recomputation would have been cheaper to write and much harder to answer questions about — with rows, "why did this not send" is a lookup rather than a re-derivation.

Attach requested information to the recipient, not to the request. When someone asks a contact for their birthday, the answer lands on that person's profile — a shadow profile if they have no account — so the next person to ask sees it already filled in. Per-request capture was the obvious model and the wrong one: it would have made the same person re-enter their details for every friend who asked.

Rank recommendations in fixed bands rather than by a blended score. Summing weighted signals let a strong semantic match outrank an explicit tag-and-location match, and the location weight outranked nationwide tag matches outright. Bands make the ordering deterministic and leave similarity as a tiebreak inside a band, which also means the ranking can be explained to someone who disagrees with it.

What happened

The product gained a memory. Calendar events, contacts with interests and locations, and recommendations built on them turned a one-shot purchase flow into something with a reason to return.

An occasion spreads between people instead of sitting inside one account: accepting an invitation is what creates a subscription, and no account is needed to accept one.

They keep calling me back. Four years into the engagement this is the largest workstream yet — which is the clearest verdict on the work I can offer.

What I'd do differently

I wrote the design docs for the calendar and contacts systems well after the code, and the schema shows it. Reminders shipped in October 2025, lost a unique constraint in November, and gained their instance table in December — three passes at one model, with the document that explains it written after all three. On a shared codebase the written spec is not documentation, it is the coordination mechanism, and I kept treating it as something to catch up on rather than the thing that goes first.

Built with

Ruby on Rails · TypeScript · Vue 3 · Inertia · UnoCSS · PostgreSQL · pgvector · Sidekiq · Elasticsearch · Twilio · Claude Code