Konturas.lt
LTEN
← All work

In-house e-commerce · subscriptions · D2C

Garrdu

A top-tier, current-generation e-commerce build. Comparable Shopify projects run from €600 a month.

2023–2025·Build, design, architecture, upkeep·Iterative (12+ months)·Premium pet food · D2C subscription
Visit garrdu.lt →
Garrdu — screenshot

TL;DR — what's different

  • 3 payment channels (card with 3DS, Apple Pay / Google Pay, Baltic bank links via Makecommerce) in one checkout flow
  • A survey built on the dog's weight, age and activity sets the portion size and delivery schedule
  • Server-side conversion tracking: Meta and Google still get the purchase signal after Safari ITP
  • An in-house email and abandoned-cart engine instead of a Klaviyo profile fee
  • A predictable monthly server bill instead of Shopify's cut plus a stack of SaaS subscriptions

Situation

The problem

A premium dog-food brand needed a direct sales channel in Lithuania. Every dog needs its own portion size, feeding frequency and delivery schedule computed from age, weight, body type and activity level. A typical Shopify plugin doesn't do that; a WooCommerce workaround would mean stacking six or seven SaaS subscriptions that don't talk to each other and get pricier as the business grows.


Solution

How we got there

We started from an empty file. One database, one source of truth for the order. An Express-based backend with Prisma. A Vite + React 18 front-end with hand-rolled SSR and react-helmet-async. Three payment channels behind a single state machine. BullMQ queues for the abandoned-cart and email chains. Conversion tracking from the server, not just the browser. Audit log, consents and admin built in-house.


Result

What changed

The client got an online store that functions as the backbone of a serious D2C business: three payment methods in one flow, a survey-built subscription, an in-house email engine, and server-side conversion tracking that survives Safari ITP and ad blockers. No SaaS subscription fee, no percentage of sales.


Narrative

How it was built

01 / START

Starting point

Garrdu arrived with a brief that templates don't answer: sell premium dog food as a subscription, where every dog's portion size, feeding frequency and cart are set by age, weight, body type and activity — and all of it has to feel equally smooth on a phone at 4am and in an office on Monday morning. You don't get there with Shopify plugins. So we didn't start by picking a theme — we started from an empty file.

02 / POSITION

Why not Shopify

A growing Lithuanian store eventually lands in the same place: a platform fee, a subscription plugin, an email tool, an abandoned-cart tool, an upsell tool, review and loyalty plugins, a survey builder. Each one runs €30–300 a month, each one holds its own island of data, and each one breaks when its neighbour updates. Garrdu skipped that stage entirely: all the logic lives in one system — one database, one source of truth for the order, the customer and the subscription.

03 / SURVEY

A survey that does the salesperson's job

Garrdu's nine-step survey turns what an owner knows about their dog into a specific product in the cart and a subscription delivery schedule.

In the first five steps the owner just clicks answers, typing nothing: the dog's age group (puppy, adult, senior — from 2 months to 12+ years), activity level, digestive quirks, food fussiness and allergy status. A sixth step appears only when the previous answer suggests or confirms allergies — the owner flags the specific proteins (chicken, turkey, beef, pork or venison) the dog can't tolerate. Step seven takes weight in kilograms, step eight the dog's name, step nine an email address. The last two are only asked once the person is already committed.

From the answers, deterministic logic — no LLM guessing involved — picks one of four proteins by a strict priority order: allergies first, then digestion, then activity. A confirmed allergy always rules out that protein; with no allergies, frequent digestive issues lead to venison, high activity with stable digestion to beef, and a dog with no particular issues gets turkey as the everyday default. Once the protein is set, the daily portion in grams is computed by linear interpolation against the product's own weight table (for example, a 10 kg dog gets roughly 100 g a day), which then determines how many packs are needed per month and how often they ship.

The survey's progress is stored on the server, so someone can start on a work computer and finish on their phone that evening — sessions are matched by email even when the two browsers carry different cookies. After an accidental close, the same question the person left off on reopens. Once the survey is done, the product lands in the cart with its portion and delivery frequency already computed.

Garrdu survey step — selecting the dog's parameters
Survey step · server-side progress saving

04 / TRACKING

Attribution that survives Safari ITP

Over the past couple of years, browsers have cut off a large share of the data Shopify-style stores rely on. At first glance ads look like they're converting worse, when in reality they simply stopped being visible. Garrdu's conversion tracking doesn't run in the browser alone: the core event chain — purchase, subscription start, survey completion — is also sent from the server via CAPI, each with a unique event ID, geographic context and deduplication, so Meta and Google count the same event once. A quality monitor sits alongside it, catching a broken event before the ad budget starts burning on bad data.

05 / EMAIL

An in-house engine instead of Klaviyo

Confirmation emails, abandoned-cart reminders, campaigns, unsubscribe links, customer segments — all built in-house. A queue, a scheduler, open- and click-tracking, and a one-click unsubscribe flow that meets GDPR requirements without extra plugins. As the customer base grows, the bill rises far more slowly than a Klaviyo per-profile fee — it scales with server resources, not with customer count.

06 / RECOVERY

The abandoned-cart chain

The abandoned-cart chain sends three emails: a first reminder after an hour, a second the next day, a third with a product summary and a discount — but only when the logic shows the person is still hesitating. If the buyer completes the order or cancels the subscription in the meantime, the chain stops. Once the recovery token expires, its path closes without a final email.

06b / DELIVERY

LP Express parcel lockers — built in-house

A Lithuanian buyer expects to pick up their parcel from the nearest locker. A ready-made LP Express plugin simply doesn't exist on the market — anyone who has needed one in a Lithuanian store has had to build it themselves. For Garrdu we built the parcel-locker picker layer from scratch.

We start from the whole Baltic region: 1,174 terminals in a single search (447 in Lithuania, 539 in Latvia, 188 in Estonia). All of it sits in one CSV data source with coordinates, cities and postal codes, refreshed directly from LP Express. The list is grouped by city, cities are sorted alphabetically, and each city's terminals are sorted by address. Even when a single city holds twenty locations, they stay in a sensible order.

Search isn't a plain "starts with…" filter. Underneath sits a Fuse.js engine with four weighted fields: city, address, terminal name and postal code. The threshold sits between 0.4 and 0.5 — loose enough that typing "Vinius" finds Vilnius locations and "Bazanavič" finds terminals on Basanavičiaus street, but tight enough that results don't fill up with random noise.

The interface switches by device: on phones and touch tablets it opens a full-screen modal with large tap targets, on desktop a compact dropdown sits alongside the other form fields. The chosen terminal's details (name, street, city, postal code) flow into the order, the LP Express label and the confirmation email. For a subscription, the terminal is saved for the next shipment — a repeat order never has to pick it again.

LP Express parcel-locker picker modal with city grouping
LP Express parcel-locker picker · LT / LV / EE · fault-tolerant search

07 / SPEED

Speed

The first thing a visitor sees is assembled server-side via SSR, not loaded in afterward via JavaScript. That's why Google's crawler sees Garrdu's real content immediately, not an empty shell. Images reserve their space in advance with LQIP hints, so nothing jumps around once a visitor starts tapping. The hero zone's gradient is drawn in real time via WebGL — not loaded as a heavy background image. It's invisible work, but it's exactly why Core Web Vitals show green across the board in a Lighthouse audit.

08 / INSIDE

Admin and the GDPR paper trail

Behind the scenes sits an internal admin layer: orders, coupons, campaigns, customers, subscriptions and an audit log where every internal action gets its own line. Customer consents are stored as separate records — when, for what, and under which version of the privacy policy. If a request from the State Data Protection Inspectorate ever comes in, the answer takes minutes, not weeks.

09 / INFRA

Infrastructure and a predictable bill

Garrdu runs on servers we manage: containers via Docker, a reverse-proxy in front, health checks, and a deploy several times a week. No Shopify percentage cut, no pressure to upgrade to a Plus plan as the business grows, no plugin price hikes in January. The monthly bill doesn't depend on whether you sell 50 packages or 5,000 — only on the server-resource ceiling, where capacity needs bumping up.


Expertise

Payments — three channels, one flow

[ Payments + Subscriptions ]

In Lithuania, one buyer pays by card, another confirms with a fingerprint via Apple or Google Pay, a third picks a bank link from their own bank's list. In most stores these are three separate systems that don't talk to each other. In Garrdu's system they're routed through a single state machine, so however the money travels across the three paths, the order, the customer and the subscription end up in the same place.

Card payment

Stripe · 3DS · AVS · vault

  • 3D-Secure flow with a separate retry window
  • AVS-decline retry without re-entering the card
  • Deduplication of Stripe webhook messages via event ID
  • A vaulted token for repeat purchases
  • Stripe Subscription lifecycle tied to the subscription model

Apple Pay / Google Pay

Phone wallet · fingerprint confirmation

  • Offered automatically when the browser and device support it
  • The buyer never enters a card — confirms with Face/Touch ID or a PIN
  • Works for both a one-off purchase and starting a subscription
  • A shorter checkout flow — fewer carts abandoned at the last step

Makecommerce

Baltic bank links · LT / LV / EE

  • The buyer picks their own bank from a list and confirms payment in online banking
  • The callback cycle is synced with Stripe's states — the same success branch
  • The bank list is filtered by the buyer's country (LT / LV / EE)
  • Expiry windows and a short recovery window are handled by separate logic

Once a payment succeeds through any channel, the order, subscription and customer land in the same place. In many online stores one of the channels is bolted on the side — in Garrdu's system all three are woven into the main flow.


Cart

A cart with two modes

In the same session a buyer can choose a one-off purchase or a subscription — the same product, in the same cart, through the same payment method. In many online stores a subscription lives on a separate branch with its own interface, usually via an external tool (ReCharge, Bold, and the like). In Garrdu's system, one flow launches two separate lifecycles from the same cart.

[ Cart · One-time vs Subscription ]
Garrdu cart with a one-off purchase / subscription toggle
Cart · one-off purchase ↔ subscription · one flow

One-off purchase

One card · one charge · done

  • A single Stripe PaymentIntent or Makecommerce request
  • 3DS, AVS or the Apple/Google Pay layer kicks in when needed
  • The order, receipt and LP Express label form immediately after success
  • Card data and the webhook window don't persist
  • Fits a first purchase, a gift, a one-off test

Subscription

Vaulted token · recurring charges · managed from admin

  • A Stripe Subscription off the same cart (card or Apple/Google Pay)
  • The payment method is stored in Stripe's vault, not with us
  • Delivery frequency (weekly or monthly) — set by the survey, or changed manually
  • Webhooks handle renewal, pause, a failed charge and cancellation
  • The customer sees one invoice, one shipment, one frequency control

Same product, same cart, same checkout window — but two different journeys begin right after it. In many online stores a subscription is kept separate and never connected to one-off order logic; Garrdu's system crosses that line inside a single object.


Shipping

LP Express integration from scratch

Partner · LP Express · Lithuanian Post · terminals LT / LV / EE

The delivery step is where Lithuanian online stores often break down: either only door delivery is on offer, or the parcel locker has to be picked from a long, unsorted list. For Garrdu we built a parcel-locker picker layer with no ready-made plugin to lean on — none exists on the market.

  • Every LT, LV and EE terminal in a single search
  • Grouped by city with clearly visible city headers
  • Fault-tolerant search (typing "Vinius" finds "Vilnius pst.")
  • A full-screen modal on mobile; a compact dropdown on desktop
  • Courier delivery offered as an alternative in the same step
  • Terminal details carried through to the order, the label and the confirmation email
  • A repeat order from a subscription: the terminal is remembered from last time

A detail Lithuanians value more than any survey shows: a parcel locker you can find on your phone in seconds, instead of a page you have to pull up on a desktop.

LP Express parcel-locker list with city grouping
Parcel-locker picker modal · LT / LV / EE

Interface

On mobile

Garrdu homepage hero on a mobile device
Home · hero
Garrdu product list on a mobile device
Product list
Garrdu homepage with the cart open
Cart modal
Garrdu checkout step on a mobile device
Checkout · mobile

Tech stack

Architecture

A hand-assembled SSR setup on Vite + React 18. An Express backend with Prisma over PostgreSQL (around 30 models). Redis + BullMQ for four queues (abandoned-cart recovery, transactional email, marketing campaigns, the email processor). An in-house auth layer (JWT, bcrypt, admin sessions, audit events, service API keys). Deployed via Docker + Coolify + nginx + PM2.

Front-endVite · React 18 · hand-rolled SSR · react-helmet-async
Back-endNode.js · Express · TypeScript · Zod
DBPostgreSQL · Prisma (~30 models)
QueuesRedis · BullMQ (4 workers)
PaymentsStripe (card + Apple/Google Pay) · Makecommerce (Baltic banks)
TrackingPostHog (proxied) · Meta CAPI · GeoIP2 · event-ID dedup
EmailIn-house engine · scheduler · campaign chains
Auth / securityJWT · bcrypt · audit log · rate limiting (2 layers)
GDPRConsent events · privacy-policy version records · unsubscribe tokens
OpsDocker · Coolify · nginx · PM2 · Retool dashboards
i18ni18next + http-middleware (LT / EN)
SpeedServer-side SSR · LQIP hints · WebGL gradient
Vite + React 18Hand-rolled SSRExpress + PrismaPostgreSQLRedis + BullMQStripe (card + Apple/Google Pay)Makecommerce (Baltic banks)PostHog proxyMeta CAPIMaxMind GeoIP2Docker · Coolify · nginx · PM2i18next (LT/EN)

AI / Automation

Automation and background processes

Four BullMQ workers handle what the customer never sees: abandoned-cart recovery with recovery tokens, transactional email, marketing campaigns and the email processor. The survey logic is deterministic, not an LLM guess — it computes the portion and the subscription delivery frequency from the answers. The tracking chain between the browser's Meta Pixel and the server-side CAPI deduplicates events via a shared event ID.


Challenges

What had to be solved

01

One source of truth between Stripe and Makecommerce

Two payment providers whose event semantics don't line up. Stripe sends webhooks following a purchase's lifecycle; Makecommerce follows the outcome of short-lived bank redirects. We needed a chain that could fold both sides' outcomes into a single order-and-subscription record with no duplicates.

02

Baltic bank links alongside a global channel

Makecommerce's callback behaves differently from a Stripe webhook: a short expiry window, different retry logic, different fields. Weaving this channel into the same state machine as Stripe took longer than the visual design did.

03

Event deduplication across the whole purchase path

The browser's Meta Pixel and the server-side CAPI need to send the same purchase event, not two. If the event ID, timing or context diverges, Meta optimizes against a distorted picture. We wrote the deduplication as a standalone library with its own tests, so it can change without re-reviewing the whole funnel.

04

A timing map for consent

Pixel and server-side events behave differently depending on the consent banner's state. We had to define precisely which event fires before consent (and in what form), and which one waits for confirmation — otherwise you either breach GDPR or lose a conversion.

05

Abandoned-cart edge cases

A recovery token expires. A customer finishes the purchase between two reminders. Someone cancels a subscription mid-chain. We wrote an explicit exit for every scenario, so a recipient never gets an email about something that's already gone.

06

Survey session handoff across devices

Started on a computer, finished on a phone that evening; different cookies, no shared session. Without a server-side key tied to the email address, that would have been data loss, not a feature.

07

A mix of payment steps under one order

3DS confirmation, an AVS retry, an Apple or Google Pay prompt, Makecommerce's back-and-forth dance with the bank — each has to fit its own provider's rules. A wrong step ends in a decline you see not in the buyer's dialog, but in telemetry.

08

Server-side i18n wiring

Lithuanian meta tags have to land in the server-generated HTML before the first paint, and later hydration can't overwrite them. A small detail, but it's why Google reads Garrdu as a Lithuanian brand rather than an echo of English keywords.

09

A deploy chain with no surprises

Separate Dockerfile variants for production and staging, different nginx configs, one process manager. All of them have to behave identically on both sides — otherwise an error surfaces in front of a buyer that never showed up in any staging run.


Advantages

Where it beats the standard

01All the logic under one roof

Instead of six or seven SaaS subscriptions talking to each other over webhooks, there's one database and one backend. Every feature sees the full context.

02Attribution after Safari ITP

Server-side tracking means Meta and Google still get the purchase signal even when the browser stays quiet. Optimization doesn't run blind.

03A survey instead of a human consultation

Portion and delivery frequency are set from the dog's weight, age, activity and allergy status. A Shopify plugin doesn't do this work.

04Three payment methods, one flow

Card with 3DS, Apple/Google Pay, and Baltic bank links via Makecommerce — all with the same consequences for the order. Fewer carts abandoned at the last step.

05An abandoned-cart recovery chain

Not a single reminder, but a chain that stops itself once an order completes or a subscription is cancelled. Replaces an expensive external tool.

06An in-house email engine

Functionally comparable to Klaviyo, without a fee for every active profile. The marketing bill grows far more slowly than the customer count.

07Speed from day one

Server-first paint, optimized images, space reserved in advance. Core Web Vitals aren't a final checkbox here — they're a starting condition.

08GDPR built into the foundation

Consent events, an audit log, an unsubscribe chain — no extra plugins, no legal-risk patch job.

09Brand character

An animated checkout flow, an in-house hero gradient, custom modals. It's visible to the customer that this isn't another Shopify template store.

10A predictable bill

A fixed monthly server cost, with no percentage of sales and no surprises in January.


Comparison

Garrdu vs. the standard

MetricShopify + pluginsGarrdu
Monthly SaaS subscription totalfrom €600€0 (every piece built custom)
Payment channels at checkout1–23 (card · Apple/Google Pay · bank link via Makecommerce)
Ad tracking after Safari ITPPartial (browser only)Full (server-side + deduplication)
Subscription personalizationA single template for everyoneSet from the survey (portion + frequency)
Abandoned-cart recoveryA plugin with a per-profile feeA custom in-house solution, no monthly fee
First paint on mobileA templated JS shellServer-side SSR
Lithuanian SEO visibilityA plugin plus delayed content after hydrationServer-side SSR with Lithuanian meta tags from the first byte
Ownership of customer dataBelongs to the platform100% belongs to you

See it live — Garrdu store


Numbers

Results

Payment channels

+1–2 channels

1–23 in one flow

Subscription personalization

Individual to each dog

TemplatedSet by survey

Ad visibility after Safari ITP

Attribution no longer runs blind

PartialFull

Monthly SaaS subscriptions

Every piece built custom

from €600€0

First paint

Faster

JS shellServer-side SSR

GDPR audit

Minutes, not weeks

A bundle of pluginsRecorded in the DB

Lithuanian SEO visibility

Full indexing from the first visit

Plugin plus delayed contentSSR plus Lithuanian meta tags from the first byte

Survey step logic

Portion and protein set deterministically

One flow for everyoneBranches by allergy and activity

The specific implementation recipes — the event-ID schema, the Stripe vault-and-state chain, the survey's portion formula, the abandoned-cart timing map, the deploy sequence — aren't shared publicly. Presented separately to a serious client.