i18n B2B e-commerce platform for waterless urinal products across 32 European countries with automated VAT handling, PDF invoicing, and CRM integration.
International e-commerce platform with 30 locales, product configurators, AI chatbot, and fully automated order flow: Stripe → Zoho CRM → Airtable → Mailgun → PDF.
Key Results

Pikkuna is a Finnish manufacturer of vinyl curtains and PVC products with a loyal local customer base and, at the time we started, zero international presence. The decision to expand internationally made the complexity immediate: 32 countries, a different VAT rate for every EU market, 13 currencies, and customers who expect to read — and be billed — in their own language.
Off-the-shelf platforms were ruled out early. Shopify could not handle the product configurators the business required: custom dimensions, material choices, and mounting options that recalculate price in real time. More critically, every order was being processed by hand. Confirming payment, creating a deal in the CRM, generating an accounting entry, booking a PostNord shipment, producing a PDF invoice and emailing it to the customer — a 30-minute workflow, spread across four tools, repeated for every single order. That was manageable at a dozen orders a week. It would not survive a European launch.
I built the entire platform from scratch: storefront, checkout, product configurators, and every backend integration. The central design decision was to treat the manual order workflow as the primary problem. Everything else — the i18n, the VAT logic, the configurators — was important, but the order flow was the thing that would determine whether the expansion actually worked operationally.
A single Stripe webhook enqueues the order into a BullMQ (Redis-backed) job queue the moment payment is confirmed. A worker processes each order sequentially — Zoho CRM deal, Airtable backup record, PostNord shipment, Netvisor accounting entry, PDF invoice, confirmation email — with automatic retries on failure. From payment to invoice in the customer's inbox: under two minutes, with no one touching it.
The i18n system covers 30 locales. Each country carries its own currency, VAT rate, and shipping zone, all updated automatically before each build from live exchange rate and VAT APIs. Checkout pricing is always accurate, and there are no runtime API calls in the critical purchase path. The product configurators handle custom dimensions and pricing in real time on the client side. A RAG-based AI chatbot handles multilingual support — that system is documented separately.
Processing time dropped from 30 minutes to under 2 minutes per order. For a company expanding to 32 countries without hiring an operations team, that gap was the difference between scaling and drowning.
| Metric | Value |
|---|---|
| Languages | 30 fully localized |
| Markets | 32 countries, 13 currencies |
| Order automation | Fully automated (payment → invoice) |
| Analytics capture | Server-side (GA4 + Meta CAPI) |
| API integrations | 30+ endpoints |
| Regions | 3 (Frankfurt, Stockholm, Cleveland) |
Server-side tracking (GA4 + Meta CAPI) captures the conversions that adblockers and iOS hide from client-side tracking — recovering attribution that would otherwise be invisible.
Selling across 30 languages and 32 countries means SEO is not an afterthought you bolt on later — it has to survive localization, server-side rendering, and a constantly changing product catalogue. I owned the full technical SEO stack for this build:
hreflang across 30 locales — every product, category, and content page declares its language and regional alternates correctly, so Google serves the Estonian buyer the Estonian page and the German buyer the German oneProduct schema — pricing, currency, availability, and reviews are emitted per locale, with the structured data surviving the i18n layer (the most common place schema breaks on multilingual sites)BreadcrumbList and Organization schema site-wide; FAQ pages for the most-searched questions in each marketlastModified driven by product data — no plugin, no external tool, just code that runs at build timeThe same patterns are now my default for every project — covered in the Technical SEO I Build Into Every Project write-up.
For the technically curious, here is how the core pieces are built.
Stripe webhook enqueues the order for sequential processing by a BullMQ worker, which handles retries with exponential backoff (illustrative — simplified from the production implementation):
// src/app/api/stripe-webhook/route.ts
export async function POST(req: Request) {
const event = stripe.webhooks.constructEvent(body, sig, secret);
if (event.type === "checkout.session.completed") {
const session = event.data.object;
// Enqueue for sequential processing — the worker handles
// Zoho, Airtable, PostNord, Netvisor, PDF, and email in order
await orderQueue.add("process-order", { sessionId: session.id });
}
return new Response("Queued", { status: 200 });
}The worker processes each order sequentially — Zoho CRM, Airtable, PostNord, Netvisor, PDF invoice, confirmation email, then server-side analytics (GA4 + Meta CAPI) — retrying failed steps with exponential backoff before falling back to manual inspection in the queue dashboard.
A prebuild pipeline automatically updates VAT rates and exchange rates:
// scripts/update-vat-rates.js
async function updateVatRates() {
const euCountries = ['AT', 'BE', 'BG', ...];
const rates = {};
for (const country of euCountries) {
const res = await fetch(
`https://apilayer.net/api/rate?country_code=${country}`
);
const data = await res.json();
rates[country] = data.standard_rate;
}
await fs.writeFile('vat-rates.json', JSON.stringify(rates));
}
// next-intl.config.js (32 countries with unique settings)
const countries = {
finland: {
locale: 'fi', currency: 'EUR',
vatRate: vatRates.FI, // Auto-updated
exchangeRate: 1,
shippingZone: 'finland',
vatName: 'ALV'
},
germany: {
locale: 'de', currency: 'EUR',
vatRate: vatRates.DE,
exchangeRate: 1,
shippingZone: 'eu',
vatName: 'MwSt'
},
unitedKingdom: {
locale: 'en', currency: 'GBP',
vatRate: 0, // Non-EU
exchangeRate: exchangeRates.GBP,
shippingZone: 'america_asia',
customsUrl: 'https://...' // Customs info
},
// ... 29 other countries
};Chatbot uses Upstash Vector for semantic search over knowledge base:
// src/lib/rag-chat.ts
export async function getRelevantContext(query: string) {
// Generate embedding via OpenAI
const embedding = await openai.embeddings.create({
model: "text-embedding-3-large",
input: query,
});
// Search similar documents in Upstash Vector
const results = await vectorIndex.query({
vector: embedding.data[0].embedding,
topK: 5,
includeMetadata: true,
});
// Return context for prompt
return results.map((r) => r.metadata?.content).join("\n\n");
}
// API route with streaming
export async function POST(req: Request) {
const { messages } = await req.json();
const context = await getRelevantContext(messages.at(-1).content);
const result = streamText({
model: openai("gpt-4o-mini"),
system: `Answer based on this context:\n${context}`,
messages,
});
return result.toDataStreamResponse();
}Using Puppeteer with @sparticuz/chromium for serverless-compatible generation:
// src/lib/generateInvoice.ts
import chromium from '@sparticuz/chromium';
import puppeteer from 'puppeteer-core';
export async function generateInvoicePDF(order: Order) {
const browser = await puppeteer.launch({
args: chromium.args,
executablePath: await chromium.executablePath(),
headless: chromium.headless,
});
const page = await browser.newPage();
// Render React component to HTML
const html = renderToStaticMarkup(
<InvoiceTemplate order={order} />
);
await page.setContent(html);
const pdf = await page.pdf({ format: 'A4' });
await browser.close();
return pdf;
}As COO, I worked closely with Iurii, who led our IT department with rare clarity and ownership.
Andrii 🇫🇮
COO, Suomen Pehmeä Ikkuna Oy (Pikkuna)
I have worked together with Iurii Rogulia in company Suomen Pehmeä Ikkuna Oy, where he conducted IT development and support. Thanks to his expertise, all tasks were solved in time.
Basil A 🇫🇮
Head of Customer Support, Suomen Pehmeä Ikkuna Oy (Pikkuna)
Topics
As Head of Production I had the opportunity to work closely with Iurii during his time with our company.
Before Iurii, our company site was a static brochure nobody had touched in years — wrong prices, an outdated team page, no way for us to publish anything ourselves.
Elina Virtanen 🇫🇮
Marketing Manager, Suomen Pehmeä Ikkuna Oy (Pikkuna)
AvailableNeed something similar?
I build custom solutions — from APIs to full products. Let's talk about your project.
i18n B2B e-commerce platform for waterless urinal products across 32 European countries with automated VAT handling, PDF invoicing, and CRM integration.
RAG system on OpenAI and Upstash Vector with 30 language support. Includes streaming chatbot, hybrid search (semantic + keyword), AI ticket classifier, and
Databases
E-commerce order automation that cut 160+ hours/month of manual work to zero: Stripe webhook to VAT invoice and tracking in under 2 minutes.
Libraries
Databases
40+ point production SaaS checklist: auth, Stripe billing, PostgreSQL, rate limiting, email, monitoring, and security — with honest 8-week build estimates.
Stack
Databases
Topics