Home

GetSellers

Year
2025–2026
Type
Work project · Tres Astronautas
Status
In production
Website
getsellers.io
Stack
  • Node.js
  • Express
  • TypeScript
  • MongoDB and Mongoose
  • React and Vite
  • TanStack Query
  • Zustand
  • Tailwind CSS and Radix UI
  • Stripe
  • Socket.IO
  • Twilio
  • Turborepo

Summary

GetSellers is a marketplace for US real-estate investors. Homeowners who want to sell fill in a form in a marketing campaign, their data arrives as a lead, and each lead goes automatically to one investor subscribed to that county, which the product calls a market.

Investors pick their markets, load a prepaid wallet and receive leads in the app, by email and by SMS. They work each lead through a pipeline, from new lead to closed or cold, and can ask for a refund on a bad one. The company's own team runs everything else from an admin site: users, markets, sources and prices, refunds, broadcasts and reports.

The product is made of three parts:

  • An investor app for the people who buy leads.
  • An admin site for the team that runs the marketplace, built from scratch during the project.
  • An API behind both, which also receives the leads from the campaigns.

The engagement was the handover and modernization of a product that was already live. I worked on it at Tres Astronautas for a client, as a Senior Software Engineer, in charge of the whole backend. This case study shows the product through its flows and diagrams.

17
capabilities
45
features
289
backend test files

Context & role

  • Client: A US real-estate lead marketplace
  • Company: Tres Astronautas
  • Product: An investor app, an admin site and the API behind both
  • My role: Senior Software Engineer
  • Team: 1 product owner, 2 software engineers, 1 product designer and 1 QA
  • What I built: The whole backend: the restructure of the API, lead intake and assignment, the wallet and its payments, refunds, referrals and the scheduled work
  • Engagement: Handover and modernization of a live product, from 2025 to 2026

Problem

The marketplace was already running when the project started, and the outgoing team had left a technical document behind. Taking it over safely meant changing nothing until its state was understood, so the first sprint was spent reviewing that document, running a gap analysis and measuring how the live product performed. That review named the critical areas: the lead feed, the counties, billing and the setup wizard.

The pains it found were of three kinds:

  • For investors, lead delivery depended on billing. A failed charge could stop leads from arriving, even for an investor with another card saved.
  • For the team that runs the marketplace, there were no tools of their own for refunds, broadcasts, reports, source prices or referrals.
  • For the product itself, the backend's structure made each use case hard to test on its own, and lead data carried sensitive and duplicated information that should never reach the browser.

The investor carries most of that weight. Leads are what they pay for, and every problem in billing or delivery reaches them as a lead that did not arrive, so most of the backend work was designed around keeping leads flowing.

Users & context of use

Four profiles shape the product, but only two of them sign in. The homeowner is the reason the leads exist and never opens the app. The sales representative is a role that admins manage, not a user with screens of its own.

Real-estate investor

Subscribes to counties, receives leads, works them through the pipeline and manages the wallet, the cards, the referral code and refunds.

Investor app

Homeowner

Someone who wants to sell. Fills in a form in a marketing campaign, with their consent. Their personal data stays hidden until the lead belongs to an investor.

Marketing campaigns

Platform administrator

Manages users, partners, sources and prices, markets, refunds, broadcasts, reports and webhooks, and gets an alert when a lead cannot be assigned.

Admin site

Sales representative

A role that admins assign and list under users. Referral commissions belong to the investor who referred, not to this role.

Admin site, as a record

The moments that shaped the design

  • A lead arrives from a campaign at any hour. It has to reach the right investor in seconds, without anyone on the team stepping in.
  • An investor's card is declined in the middle of the night. Their leads should keep coming if another card can pay.
  • An investor opens the feed and sees leads that are not theirs yet. They should see enough to decide, but never the homeowner's personal data.
  • An admin reviews a refund and needs to know that completing it credits the wallet and sends the lead to someone else, in one step.

Goals, success metrics & constraints

Goals

  1. Keep the live product working while it is modernized.
  2. Never let billing block the delivery of a lead.
  3. Give the team that runs the marketplace its own tools.
  4. Keep personal data inside the backend until an investor owns the lead.
  5. Make each use case testable on its own.

How success would be measured

No success metrics were agreed with the client during the engagement. These are the ones I would propose, each tied to one of the goals:

GoalMetricWhat it tells us
Delivery never blocked by billingLeads left unassigned for lack of funds, and recharges saved by a backup cardWhether the wallet keeps leads flowing.
Fast routingTime from the webhook to an assigned leadWhether a lead reaches an investor while it is still fresh.
Tools for operationsRefunds resolved per week, and time to resolve oneWhether the admin site replaced the manual work.
Fair distributionSpread of leads among the subscribers of each marketWhether taking turns works as intended.
Testable backendCoverage of the use cases, and defects found after releaseWhether the restructure paid off.

Constraints

  • A live product. Every change had to land without stopping lead delivery for the investors already using it.
  • Two engineers. I owned the backend and a teammate owned the web apps, so the API contract between us had to stay clear and stable.
  • An integration that never closed. The refund flow was planned against the client's own backend, but it could not be completed because of connectivity issues, which is why operations got an admin site of their own.
  • Many lead sources. Each campaign names the same fields differently, and the intake had to accept all of them.

Process & prioritization

From problem to scope

The work was organized in sprints 0 to 6. Sprint 0 was the review of the live product, Sprint 1 restructured the backend and set up the monorepo, and the following sprints moved payments to a new provider, built the admin site and reworked the language of the product. The whole scope was consolidated into 17 capabilities and 45 features.

How the team worked

The two engineers split the product along the API: I owned the backend, and my teammate owned the investor app and the admin site.

What the platform covers

AreaWhat it enables
Lead intakeA webhook for every campaign, one adapter that normalizes the fields, and enrichment with property data and a price.
AssignmentCounty, then state, then nationwide, taking turns inside each market, with budget and balance checks.
Lead feed and pipelineA live feed, lead detail and a pipeline from new lead to closed or cold, with personal data masked until the lead is assigned.
MarketsSubscriptions to US counties, each one open, secured, held, full or released.
Wallet and paymentsSaved cards, a backup card, auto-recharge, a monthly budget with a monthly reset, receipts and history.
RefundsUp to two requests per lead, one open at a time, with the lead reassigned when a refund is completed.
NotificationsIn the app in real time, by email and by SMS, according to each investor's settings.
ReferralsReferral codes, settings and commissions, processed every day.
Admin siteUsers, partners, sources and prices, markets, refunds, broadcasts, reports and webhooks.

What waited

  • The refund integration with the client's own backend was never completed, so refunds live in the admin site instead.
  • Part of the old payment provider is still in the repository, from the migration.

Design decisions

The decisions that most shaped the product, made by the team and designed by the product designer of the project, part of the Tres Astronautas team. Each one is shown as a flow or a diagram.

Ready to receive leads in one sitting

Problem. An investor who signs up but never adds a card or a budget is an investor who never receives a lead.

Decision. A setup wizard asks for the three things a lead needs: the markets, a card to pay with and a budget.

Trade-off. A longer first session.

In the product. Each sign-in is confirmed with a one-time code by email, and a second card is encouraged with a one-time discount on the next lead.

From sign-up to the first leadThe investor's path, and the two moments where the platform acts.

One lead, one investor, taking turns

Problem. Leads are exclusive. Sending each one to every subscriber would make it worthless, and always picking the same investor would be unfair.

Decision. A lead looks for subscribers in its county, then its state, then nationwide. Inside each scope, the investor who waited longest gets it, as long as they are under their monthly budget and have the balance to pay.

Trade-off. A lead can end up unassigned when nobody in its scope can pay.

In the product. An investor short on balance has their subscription held and the next one is tried, and when nobody qualifies the admins get an alert right away.

From a homeowner's form to one investorIntake, scope, turns and the checks before a lead is assigned.

Billing never blocks a lead

Problem. Charging a card at the moment of each assignment meant that a declined card stopped the lead, even when the investor had another card saved.

Decision. Leads are paid from a prepaid balance. When the balance drops under the investor's threshold, recharge events are created and charged in the background, trying the other saved cards when one is declined.

Trade-off. More billing states to model and test, and an investor with no funds is skipped until the wallet is recharged.

In the product. Each charge has its own key, so a retry never charges twice, and the investor gets an email only when every card has failed.

A wallet that refills itselfRecharge events, retries with other cards and the monthly cap.

A refund that puts the lead back to work

Problem. A refund that only returns money leaves the lead idle, even though another investor in the market could still use it.

Decision. Completing a refund credits the wallet, unassigns the lead and offers it to the next investor in the same market, in one step.

Trade-off. Refunds touch billing, assignment and notifications at once.

In the product. Each lead allows two requests at most and only one open at a time, a rejected request can be sent again with the form already filled in, and either side can cancel while it is in progress.

A refund on a bad leadThe investor asks, an admin decides, and the lead moves on.

Personal data stays in the backend

Problem. The feed shows leads to investors before they own them. Sending the homeowner's full data to the browser and hiding it there would leave it one inspection away.

Decision. The API masks the last name, email, phone, address and location of every lead in the feed, and returns the full data only for leads the investor owns.

Trade-off. An extra mapping on every lead response.

In the product. The map shows an approximate location by design, and sensitive or duplicated fields were removed from the data model altogether.

Design system

The investor app and the admin site share one design system, created by the product designer assigned to the project at Tres Astronautas.

Foundations

  • Two typefaces. Poppins for titles and Mona Sans for the interface, tables and data.
  • A dark interface. A deep navy page with darker cards, and soft blue-gray tones for text and icons.
  • One violet for action. A violet ramp where one tone marks the main action, with fixed tones for hover, selected and disabled.
  • Status colors with fixed meanings. Success, danger and warning, each with its own ramp.
  • Radius and spacing as tokens. A short scale of radius and spacing values shared by every component.
FoundationsTypography, color, status colors, radius and spacing of the investor app.

From design to code

As a Senior Software Engineer, I owned the backend: its restructure, its use cases and everything that runs behind the two web apps. This section explains how it is organized, in terms of what each part does.

Architecture

The repository is a monorepo with the API, the investor app, the admin site and shared packages. I moved the backend from a layered structure to a hexagonal one: a request goes from a thin route and controller to a use case that does one thing, the domain holds the interfaces and rules, and the adapters talk to the database and the outside services.

The dependencies are wired in one place, so the tests replace them with mocks and each use case can be tested on its own. Scheduled jobs and the real-time notifications run in the same process as the API.

ArchitectureWho calls the API, its hexagonal layers, and the data and services behind it.

Leads, refunds and markets

A lead in the investor's pipeline can move to any status, from new lead to closed or cold. A refund starts in progress and ends completed, rejected or cancelled. A market shows each investor where they stand: open, secured, held when the balance runs short, full, or released.

Life cyclesThe pipeline of a lead, the states of a refund and a market as one investor sees it.

Scheduled work

Four jobs run in the background: one cleans expired one-time codes every day, one charges the pending recharge events every two minutes, one processes referral commissions every morning, and one resets the monthly budgets on the first day of the month.

Quality

The backend has 289 test files, 270 unit and 19 integration, with an in-memory database and a coverage threshold of 60%. Because each use case can be tested on its own, most of that suite is unit tests.

Delivery

Releases are tag-driven and per app: the tag says which part ships and to which environment, so the API, the investor app and the admin site can be released on their own.

Technical decisions

DecisionWhyTrade-off
Layered to hexagonal backendEach use case can be tested alone, with less coupling.A large restructure up front, paid back by the test suite.
One payment providerThe wallet and the investor app share one provider and one model of cards and charges.A migration, with part of the old provider still in the repository.
Prepaid wallet with auto-rechargeA lead is paid from the balance, and the wallet is refilled later with retries.More billing states to model and test.
Taking turns inside each marketLeads are exclusive, and turns spread them fairly.A lead can stay unassigned when nobody can pay.
One adapter for the lead webhookSources name fields differently, so one adapter maps them all.A source with a new shape means extending the adapter.
Masking lead data in the APIPersonal data does not leave the backend until it is needed.Extra mapping on every lead response.
Tag-driven, per-app releasesEach part of the product ships on its own.Releases depend on tagging discipline.

Results

The modernized product is in production, and the client kept a live marketplace the whole way through. Investors sign up, pick their markets, load a wallet that refills itself and receive leads in the app, by email and by SMS, then work each lead through the pipeline and ask for a refund on a bad one. The team that runs the marketplace handles refunds, broadcasts, reports, source prices and referrals from its own admin site.

The refund integration with the client's own backend was never completed, so that part still has to be closed. No revenue or conversion figures were shared for this engagement.

Learnings & next steps

Next steps

  • Close the refund integration with the client's own backend once the connection is possible.
  • Remove what is left of the old payment provider.
  • Measure the time from webhook to assignment, and the leads saved by backup cards, as the first baseline.

What I learned

  • Read before you touch. Spending the first sprint only reviewing a live product felt slow. It is what let the rest of the project change billing and assignment without breaking delivery for the investors already using it.
  • Money flows deserve their own states. Moving from "charge now" to a wallet with recharge events added states, but each one made a failure visible and recoverable instead of silent.
  • The API is where a two-person team meets. With one engineer on each side of it, keeping the API stable and predictable mattered more than any shared code.