Skip to content
GitHub Twitter

Etsy Manager

A while ago I built a small tool to fix a friend's Etsy shipping workflow. She sells handmade things, and the routine was: open Etsy → copy the customer address → log into DHL Versenden → paste each field → download the label PDF → upload the tracking back to Etsy. Repeat for every order. Twenty orders took an hour.

I turned the whole thing into one button.

Receipts

What it does

Pulls receipts from the Etsy API for a date range, shows them in a table, and lets you bulk-select a batch and hit "Magic". Magic validates the addresses against DHL's rules, creates labels through DHL Versenden, stitches the PDFs together into one printable document, and pushes the tracking numbers back to Etsy.

You can also override a shipping address before label creation — useful when a buyer writes a different address in the order notes — and the table shows tracking status with a one-click copy.

Stack

Backend: Spring Boot 3 + Kotlin on Java 21. SQLite for persistence (it's a single-user tool, no need for a real DB server). OAuth2 client for the Etsy API, plus a separate integration for DHL Versenden. Spring WebFlux + Kotlin coroutines for the outbound API calls.

Frontend: Angular 17 + Material. pdf-lib stitches the DHL labels into one print-ready PDF. js-confetti fires when the batch completes, because the friend asked for confetti.

Auth: Three-legged OAuth with Etsy. Tokens are stored in the browser's localStorage and refreshed transparently via an HTTP interceptor.

Things worth pointing out

Bulk action disabling logic. Each action button in the table consults a per-row predicate (status, tracking presence, age). A Create Label button is disabled for a Completed receipt; a Download button is disabled if the order is older than three days (DHL's label retention window); a Sync Tracking button is disabled if the receipt already has shipments. Keeps the UI from offering operations that will just fail.

Address validation pass. Before label creation, the selected addresses are sent to a /shipments/validate endpoint that runs DHL's own address checks. If any address comes back invalid (wrong format postal code, unknown city, missing house number) the UI flags those rows specifically rather than aborting the whole batch.

Custom address override. When the buyer puts "please ship to X instead" in the order notes, you can attach a CustomAddress to the receipt that takes precedence over the Etsy-reported address. Stored separately so it doesn't clobber the Etsy data.

What it isn't

Not deployed publicly — it runs on a private machine behind a self-signed cert. It's also single-tenant; there's no user management, just one Etsy seller account. That kept the scope small and the codebase honest. If I ever wanted to make this a SaaS for German Etsy sellers, the rewrite would look very different (and would probably use Spring Modulith — which is exactly what I'm doing in a separate rewrite branch).