Senior full-stack developerVästra Götaland, Sweden

Based in Sweden, with 15 years in software. I’ve shipped a Mac app, a form builder and a mobile game.
ScrollMac app, form builder, mobile game, smart home, open source
Work
What I’ve been working on
Three products I designed, built and shipped myself, and the open source I maintain.

The Unload film. 15 seconds, drawn in code.The Unload film. Drawn in code, and so is its music.
Project 1 of 5Mac app2026
Unload
Copies every photo and video off an iPhone, checks each copy, then frees up the phone. A paid Mac app, sold direct.
- Role
- Design, engineering, licensing and release
- Runs on
- macOS 15 and later
- Built with
- Swift 6SwiftUIAppKitImageCaptureCoreCryptoKitSparkleAstroConvexStripe
- 465tests in the Swift core
- 181more for the site and the license service
- 100,000+items in a library, by design
Try it
The demo from unload.to, running in this page.


How it’s built
A copy counts only when its bytes match
Every chunk is hashed with SHA-256 as it arrives from the phone. The written file is then read back through a fresh, uncached file descriptor and compared. A mismatch is retried once, then that item fails. The hash goes into a manifest that travels with the archive.
Nothing is deleted on old evidence
Right before deleting, Unload reads the phone again and checks every archived file against the manifest. It deletes 3 items first, then 250 at a time, re-reading the library after each batch and stopping if anything else disappears. With iCloud Photos on, it refuses to delete at all.
Reading the phone properly
Apple’s ImageCaptureCore leaves things out: the video half of a recently taken Live Photo, most frames of a burst. Unload reads the originals over AFC and uses the phone’s own Photos database as its catalog, so it counts items the way the Photos app does.
Interruptions are the normal case
Each item is copied to a temporary folder on the destination disk, verified, then moved into place, with the manifest written last. A crash, a full disk or a pulled cable never leaves half an item in the archive, and the next run carries on from the manifest. The engine is designed for libraries of 100,000+ items, and the deletion matching is tested at that size.
Licenses that work offline
A license is an Ed25519-signed payload bound to a hashed Mac ID, so the app can verify it without a network call. Purchases go through Stripe to a small license service on the site, with Convex as its database, which issues the key and emails it. Updates ship through Sparkle with a signed feed, in a notarized build.
Tested like it holds someone’s photos
The core is a Swift package with 465 tests across the copy engine, deletion, the archive and licensing. The website and license service have another 181.
Project 2 of 5Web app2026
Polyform
A free form builder with an AI agent that builds the form for you, live collaboration and response analytics.
- Role
- Product, design and engineering
- Runs on
- Web
- Built with
- Next.js 16React 19TypeScriptConvexAI SDKClerkTailwind CSSTurborepo

Polly, the AI agent
You describe the form. Polly writes a plan, then builds it page by page.

Themes
Built-in themes, or your own colors, fonts and corners.

Logic
A rule decides which page an answer leads to.

Analytics
Views, completion and drop-off, counted as responses arrive.
- 19question types
- 21built-in themes
- 25+languages Polly writes forms in
How it’s built
Polly is an agent with real tools
Polly works in two phases. It writes a plan you can read before anything runs, then carries it out page by page through structured tool calls that edit the same draft and undo history as the editor. When it has finished a request it runs a logic check that catches a branch pointing at a page that is gone. It also takes voice input and writes forms in 25+ languages.
Live by default
The backend is Convex, so every query is a subscription. Edits, presence and incoming responses reach everyone looking at the form without polling or a refresh.
One monorepo, four apps
A Turborepo holds the dashboard, the public answer app, the marketing site and a small service that serves country flag icons, on a shared UI package and form renderer. Each app has its own preview and production pipeline in GitHub Actions.
Analytics from every response
Each response records its path through the form, which is where the drop-off figures come from. Response and event counts are kept with Convex aggregates instead of being recounted, so the headline numbers stay fast as responses grow.
Run like a product
Custom domains, webhooks, Zapier and Slack integrations, email notifications and file uploads. The data was migrated from the US to the EU. An automated abuse sweep, and the ability to suspend a form or a whole team, keep a free service usable.
Project 3 of 5
iOS and Android game2026
FlagTap
A geography game: see a flag, tap its country on the map. On the App Store and Google Play.
- Role
- Design and engineering
- Runs on
- iOS and Android
- Built with
- Next.jsReactCapacitorMapLibre GLConvexTailwind CSS



- No. 1paid trivia game on the App Store in Sweden, April 2026
- 100points for a correct tap
- 7modes, each with a leaderboard
How it’s built
One codebase, two stores
The game is a Next.js app exported as static files and wrapped with Capacitor for iOS and Android. The same project also builds the landing page at flagtap.cc. Haptics, the splash screen and the status bar go through Capacitor plugins.
A map that needs no map service
The map is MapLibre GL with its tiles, style and fonts shipped inside the app. It replaced Google Maps. A tap is matched to a country on the device, with a point-in-polygon test against bundled borders, so a round never waits on a network call.
Small countries are still tappable
Island nations like Kiribati, Cape Verde and the Seychelles got their own hit targets so they can be tapped at all. Territories such as Åland, Hong Kong and Macao are told apart from the countries around them.
Plays offline
A score earned offline is kept on the device. When the network is back, the best one is checked against the leaderboard, and if it makes the top three you’re asked for a name. The leaderboards live in Convex.
Leaving the app ends the round
Switching away during a round ends the game, so nobody looks up the answer. There is a three second grace period for things like a notification.
Project 4 of 5Smart home apps2020-now
Homey integrations
Pax Calima2,200 installs
Ebeco Connect1,200 installs
LK Systems700 installs
Network UPS Tools600 installs
IVT Anywhere500 installs
- 5apps in the Homey App Store
- 120+releases between them
Project 5 of 5Open source package2026
Sweego for Convex
A Convex component for sending transactional email and SMS through Sweego, with durable delivery and webhook tracking.
- Role
- Author and maintainer
- Runs on
- Convex
- Built with
- TypeScriptConvex componentsWorkpoolWeb CryptoREST APIs
$ pnpm add @christian-ek/sweego
import { Sweego } from "@christian-ek/sweego";
import { components } from "./_generated/api";
const sweego = new Sweego(components.sweego, {});
await sweego.sendSms(ctx, {
to: "+33600000000",
region: "FR",
campaignType: "transac",
text: "Your code is 123456",
});How it’s built
Sends that survive failures
Every send runs in a workpool and is retried automatically through transient failures: rate limits, server errors, network blips. A permanent failure is recorded without futile retries.
Delivery, tracked per recipient
The component verifies the HMAC-SHA256 signature on Sweego’s webhooks and keeps each recipient’s state: delivered, bounced, opened, clicked, complained or unsubscribed. A mutation of your own can run whenever an event arrives.
No SDK underneath
There is no Sweego SDK and no svix dependency. The component calls the REST API with fetch and verifies signatures with the Web Crypto API.
The full send surface
Email, SMS and personalized bulk email, with templates, attachments, custom headers, List-Unsubscribe, expiry and campaign metadata. Status, cancellation and retention cleanup come with it.
Experience
15 years across telecom, defense, health tech and SaaS.
Jul 2025 - now
Veyt
Senior full-stack developer, Remote
Build user-facing features in Svelte and SvelteKit. Designed and implemented a dual-mode text-to-speech system that makes 7,700+ documents available as audio, and built and documented a shared UI component library in Storybook.
Apr 2022 - Jul 2025
Norse Feedback
Senior full-stack developer, Remote
Digital tools for mental health treatment. Full-stack development in Java Spring Boot and Vaadin, improving the UI and building secure, scalable systems for health tech.
Mar 2018 - Apr 2022
Leissner Data
Full-stack developer, Trollhättan
Built REST APIs in Java Spring Boot for a microservice platform and front ends in Nuxt and Vue for telecom operators. The APIs handled 100,000+ requests a day as part of mobile core operations.
Sep 2016 - Mar 2018
Combitech
Software consultant, Trollhättan
Consultant at Saab Electronic Defense Systems, developing and optimizing embedded Linux software for defense applications. Also taught a Python course for colleagues.
Feb 2014 - Sep 2016
Ericsson
Software developer, Gothenburg
Built virtualization infrastructure that took test capacity from 5 physical hosts to 200+ virtual machines, and designed the booking system core network teams used to share them.
Sep 2011 - Jan 2014
Alten
Software consultant, Trollhättan
Consultant in Ericsson’s infrastructure team. Automated the setup and management of virtual hosts for core network testing with Bash and VirtualBox.
About
I’ve been writing software for a living since 2011: telecom, defense, health tech and now SaaS. Today I’m a senior developer at Veyt.
Alongside the day job I build my own products and take them all the way: the idea, the design, both ends of the code, the release.
I live in Västra Götaland, on the west coast of Sweden, and work remotely.
- Based in
- Västra Götaland, Sweden
- Education
- B.Sc. in Computer Engineering, University West, 2008-2011
Stack
- Frontend
- React
- Next.js
- Svelte and SvelteKit
- TypeScript
- Tailwind CSS
- Vue and Nuxt
- Astro
- Backend
- Node.js
- Convex
- Java and Spring Boot
- Python
- REST APIs
- Stripe
- Webhooks and OAuth
- Native and mobile
- Swift 6
- SwiftUI and AppKit
- Capacitor
- MapLibre GL
- Bluetooth LE
- Homey SDK
- Shipping
- Vercel
- GitHub Actions
- Turborepo
- Docker
- Linux and Bash
- Sentry and PostHog
- Storybook
Contact
Get in touch
Want to talk? Send me an email or find me on LinkedIn, and I’ll get back to you.























