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

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
Listing source to discovery process
Marketplace source -> Discovery worker
Discovery schedules queued work
Discovery worker -> BullMQ + Redis
Queue dispatches listing updates
BullMQ + Redis -> Detail ingestion
Normalized listing records and history
Detail ingestion -> PostgreSQL
Controlled image variants
Detail ingestion -> Object storage
Catalogue data to focused API
PostgreSQL -> Fastify API
Stored media references
Object storage -> Fastify API
Validated catalogue requests
Fastify API -> Next.js catalogue
Saved-search and listing-watch commands
Next.js catalogue -> Search + listing alerts
Recorded matches and listing changes
PostgreSQL -> Search + listing alerts
Confirmed notification delivery
Search + listing alerts -> Email delivery
Web application process
Next.js catalogue -> Container runtime
API process
Fastify API -> Container runtime
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
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.
