How I built a SaaS backend entirely on n8n (40+ workflows, Google Play Billing, media processing)

I built a posthumous messaging app called LastSend. Users create messages (text, photos, videos, voice recordings, documents) that get delivered to loved ones after they pass away. The Android app is live on Google Play.

The interesting part: the entire backend runs on n8n.

The setup

Self-hosted n8n in queue mode on a Hetzner VPS. Docker Compose with PostgreSQL, Redis, n8n main instance, n8n worker, and Caddy for reverse proxy. Currently running 40+ active workflows.

What n8n handles

  • Payments: Google Play Billing verification and PayPal webhook processing. When a user subscribes on Android, Google sends a webhook to n8n, which verifies the purchase token against the Google Play API, updates the user’s subscription status in Supabase, and triggers the appropriate access changes.

  • Media processing: Users upload photos, videos, voice recordings, and documents. n8n workflows handle the processing pipeline - image optimization with sharp, video processing with ffmpeg, generating thumbnails, and storing everything in Cloudflare R2 via presigned URLs.

  • Push notifications: Firebase FCM integration for Android push notifications. Workflows manage check-in reminders, trusted contact alerts, and system notifications.

  • Subscription lifecycle: The full subscription state machine - trial periods, upgrades, downgrades, cancellations, grace periods, and expired account handling.

  • Trusted contact system: When a user misses their check-ins, n8n triggers an escalation flow. Trusted contacts get notified, and there’s a multi-step verification process before any messages get delivered.

  • Account cleanup: A 15-day automated pipeline for deceased accounts. Handles message delivery scheduling, media access for recipients, memorial page creation, and eventually cleans up storage.

What I learned

  1. Queue mode is essential once you have more than 10-15 workflows. Execution mode was hitting memory limits and dropping webhook events.

  2. Sub-workflows for everything reusable. I have shared sub-workflows for things like “send push notification”, “update subscription status”, “process media file” that multiple parent workflows call.

  3. Error handling can’t be an afterthought. Every workflow that touches external APIs (Google Play, PayPal, Cloudflare, Firebase) has retry logic and error notification sub-workflows. Payment webhooks especially - if those fail silently, users lose access they paid for.

  4. Native nodes first, Code nodes as last resort. I only use Code nodes when the native node genuinely can’t do what I need. Keeps workflows readable and maintainable.

If anyone has questions about running n8n at this scale or specific patterns I used, happy to share more.

1 Like

Congrats and pushing through to a complete SaaS backend, thats pretty impressive. How did you handle error workflows at scale? As each workflow can only have one error workflow.

2 Likes

Thanks! So, for error handling, I use one Universal Error Workflow that’s set as the error workflow for every single workflow in the project. It catches any failure and sends me a Telegram message with the workflow name, node that failed, and the error message. That way I get alerted immediately no matter which workflow breaks.

Beyond that, the real error handling happens inside the workflows themselves. For critical paths (like message delivery or payment webhooks), I use try/catch patterns in Code nodes and IF nodes to handle expected failure cases gracefully without triggering the error workflow at all. For example, if a media processing step fails, the workflow catches it, marks the media as “moderation_failed” in the database, sends a push notification to the user, and continues. The error workflow only fires for truly unexpected failures.

The “one error workflow per workflow” limit hasn’t been a problem because one centralized error workflow that routes to Telegram covers the alerting side. And for business logic errors, you want to handle those inline anyway, not in a separate error workflow.

Hi! I found your post while reading your later one about your platform cloned. I see here an interesting discussion about error handling and I want to add how I handled mine to see what the community think about it.
I have, as you, a Universal Error for every workflow, but I found that the best way for me to handle the errors is to use the setting of each node and set “On Error” to “continue (using error output)”; from there I use the logic specific to the workflow or even the node to decide what to do (i.e. starting a different workflow, looping back to a previous node, adding a waiting period and so on). I found that it gives me much more control. I also use a database to save the errors (sort of a log function outside the “executions” page of n8n) or to change the status on the dashboard to show that there is an error.

1 Like