Commercial product

Autovahti

A commercial vehicle-discovery and monitoring product with a publicly available live service. It turns changing listing data into a searchable catalogue and supports saved-search and single-listing watches while its source and production internals remain private.

Role
Full-stack Product Engineering
Period
2025—present
Categories
FrontendBackendInfrastructure
Stack
Next.jsTypeScriptFastifyPlaywrightBullMQPrismaPostgreSQLRedisS3-compatible storageDocker
Autovahti vehicle catalogue with search filters, sorting controls and listing cards.

Key metrics

New listings discovered
≈2,309/day
Average first-seen source listings in the private production aggregate.
Observed over the rolling 12-month window; refreshes and repeat observations are excluded.
Listing images processed
23.6M+
Cumulative completed image-processing records in the production media pipeline.
Rounded down to one decimal million; not labelled as unique source images.
Data processed
4+ TB
Cumulative media volume handled by the production processing pipeline.
Threshold claim; this is processed volume, not the current storage footprint.
Email notifications sent
35K+
Cumulative successfully delivered notifications from the production service.
Rounded down to the nearest thousand; retries and failed attempts are excluded.

Overview

Autovahti is an independently operated service for discovering and monitoring vehicle listings. It aggregates and normalizes listing information into a searchable catalogue, then lets people follow either saved search criteria through Hakuvahti or one specific listing through Ilmoitusvahti.

Autovahti does not publish the original vehicle listing, sell vehicles or participate in a vehicle transaction. The original marketplace listing remains the primary source for current price, availability, seller information and vehicle condition.

The product runs as separate web, API and background-worker processes. The Next.js application serves the public catalogue, a focused Fastify API provides catalogue data, and queued workers handle data collection, updates, reconciliation, image processing and notifications independently.

Challenge

Vehicle listings change or disappear over time. The catalogue needs stable search behaviour and useful history without presenting incomplete or outdated observations as authoritative marketplace data.

Data collection depends on an external browser-driven source where temporary failures can occur. The processing model therefore needs retries, idempotent writes and reconciliation, and it must distinguish a confirmed removal from a temporarily unavailable listing.

Solution

I built a URL-driven Next.js catalogue with server-rendered catalogue and vehicle pages, validated filters, explicit sorting and cursor-based pagination. Search states remain shareable, while the web application reads listing data through a focused Fastify API.

Separate BullMQ flows handle discovery, detail updates and reconciliation. Prisma and PostgreSQL store normalized listing history, Redis coordinates queued work, and listing images pass through a controlled validation and transformation pipeline before delivery from S3-compatible storage.

Hakuvahti monitors saved criteria for newly discovered matches. Ilmoitusvahti monitors one listing for recorded changes such as price updates or confirmed removal and reactivation. Confirmed email delivery is processed separately from catalogue rendering.

Responsibilities

  • Define the commercial product scope and design the catalogue, vehicle-page and watch flows.
  • Build the server-rendered Next.js application and its URL-based search experience.
  • Design the focused Fastify API with validated filters, sorting and cursor-based pagination.
  • Implement browser-based data collection and the queued discovery, update, reconciliation and notification processes.
  • Model normalized listing history and watch state with Prisma, PostgreSQL and Redis.
  • Package and operate the separate web, API, worker, database and media processes with Docker.

Architecture

Interface

  • Next.js catalogue

Services

  • Discovery worker

  • Detail ingestion

  • Fastify API

  • Search + listing alerts

Data

  • PostgreSQL

  • Object storage

Automation

  • Marketplace source

  • BullMQ + Redis

  • Email delivery

Infrastructure

  • Container runtime

  1. Listing source to discovery process

    Marketplace source -> Discovery worker

  2. Discovery schedules queued work

    Discovery worker -> BullMQ + Redis

  3. Queue dispatches listing updates

    BullMQ + Redis -> Detail ingestion

  4. Normalized listing records and history

    Detail ingestion -> PostgreSQL

  5. Controlled image variants

    Detail ingestion -> Object storage

  6. Catalogue data to focused API

    PostgreSQL -> Fastify API

  7. Stored media references

    Object storage -> Fastify API

  8. Validated catalogue requests

    Fastify API -> Next.js catalogue

  9. Saved-search and listing-watch commands

    Next.js catalogue -> Search + listing alerts

  10. Recorded matches and listing changes

    PostgreSQL -> Search + listing alerts

  11. Confirmed notification delivery

    Search + listing alerts -> Email delivery

  12. Web application process

    Next.js catalogue -> Container runtime

  13. API process

    Fastify API -> Container runtime

  14. Background-worker processes

    Detail ingestion -> Container runtime

Technical decisions

Use the URL as catalogue state

Problem
Filters, sorting and pagination could become hidden browser state that is difficult to share, restore or render consistently.
Decision
Validate query parameters at the application boundary and derive the catalogue from the URL, with cursor-based pagination bound to the active filters and sort order.
Reason
A search can be shared, reopened and rendered on the server without duplicating its state between the browser and API.
Trade-off
Changing a filter starts a new result sequence instead of preserving a cursor that no longer describes the same search.

Make collection work safe to retry

Problem
Browser-based collection and external pages can fail after partial work, creating a risk of duplicate updates or false removal events.
Decision
Give queued work stable identities, make persistence idempotent and record removal only after the source confirms that state.
Reason
A retry converges on the same listing record, while a temporary failure preserves the last known observation for later reconciliation.
Trade-off
Background processes need explicit state transitions and error classification rather than a single linear collection loop.

Separate discovery, updates and reconciliation

Problem
One long-running collection pass would couple listing discovery, detail updates and recovery from interruptions.
Decision
Run these workloads through separate queues with their own retry and recovery behaviour.
Reason
Each workload can resume independently without restarting every stage or blocking the public catalogue.
Trade-off
Queue boundaries add operational components that require clear ownership and observability.

Process listing images through a controlled pipeline

Problem
Depending directly on externally hosted listing images would make the catalogue vulnerable to unavailable or unsuitable files.
Decision
Validate and transform listing images before storing controlled display variants in S3-compatible storage.
Reason
The public catalogue receives consistent media references while collection and cleanup remain isolated from page rendering.
Trade-off
Image processing adds storage and background work, including cleanup when a stored variant is replaced.

Media

Gallery

Screenshot

01 / 03

Autovahti vehicle page with an image gallery, normalized specifications, recorded price and listing-watch action.

The vehicle page combines normalized listing information with Ilmoitusvahti while directing transaction details back to the original listing.

Outcomes

  • The commercial product is publicly available at autovahti.net with shareable searches, vehicle pages and layouts adapted for desktop and mobile use.
  • Hakuvahti and Ilmoitusvahti support distinct monitoring needs while notifications remain independent from catalogue rendering.
  • Queued retries, idempotent persistence and reconciliation provide a defined recovery path for temporary collection failures and interrupted background work.

Verified highlights

  • The live commercial service is publicly available at autovahti.net; its source repository and production internals are not public.
  • The web application, API and background workers run as separate processes.
  • Discovery, detail updates, reconciliation and notification delivery use distinct queued flows with retries and idempotent processing.
  • Listing images pass through a controlled validation, transformation and storage pipeline before public delivery.

Learnings

  • In a product built on collected data, the distinction between unknown, temporarily unavailable and confirmed removed is part of the user experience, not only backend error handling.
  • Saved searches and single-listing watches look related in the interface but need different matching, history and notification rules.