Blog
saasflaskpythonauthenticationstripe

What Nobody Tells You About Building a SaaS: Auth, Payments, Emails and Infrastructure

Building a SaaS sounds simple until you hit the boring parts: authentication, payments, emails, edge cases and infrastructure. Here's what nobody tells you.

2026-03-086 min readLaunchStack

Building a SaaS is fun… until you reach the boring parts.

Auth. Payments. Emails. Edge cases. Infrastructure.

These are the things that turn a weekend project into weeks of work. Most founders start a SaaS thinking about the product. But the real complexity often comes from everything around it.

Here's what nobody tells you.


1. Authentication Is Never Just "Login"

Everyone thinks auth is a checkbox. It's not.

You need:

  • Email/password login with proper password hashing (bcrypt, not MD5)
  • OAuth (Google, GitHub) — because users don't want another password
  • JWT tokens with refresh logic — access tokens expire, and you need to handle that gracefully
  • Password reset via email with secure, time-limited tokens
  • Session management — what happens when a user logs in from two devices?
  • Rate limiting on login endpoints to prevent brute force attacks

In Flask, a minimal JWT setup looks like this:

from flask_jwt_extended import JWTManager, create_access_token, jwt_required

jwt = JWTManager(app)

@app.route('/login', methods=['POST'])
def login():
    user = verify_credentials(request.json)
    access_token = create_access_token(identity=user.id)
    return jsonify(access_token=access_token)

@app.route('/protected')
@jwt_required()
def protected():
    return jsonify(message="you're in")

Simple. But that's 5% of the work. The other 95% is the edge cases: expired tokens, invalid tokens, revoked tokens, CORS issues in production, OAuth callback URLs, email verification flows...

Most developers spend 1-2 weeks just on auth. And that's before writing a single line of their actual product.


2. Payments Are Where Things Get Expensive to Get Wrong

Stripe is the standard. But wiring it up correctly takes more time than people expect.

The basics:

  • Create a checkout session
  • Handle the payment success redirect
  • Store the subscription status in your database

The part that actually matters:

  • Webhooks — Stripe sends events asynchronously. If your server is down for 10 minutes, you miss them.
  • Signature verification — anyone can send fake POST requests to your webhook endpoint if you don't verify the Stripe signature
  • Idempotency — the same webhook event can arrive multiple times. Your handler needs to be idempotent.
  • Failed payments — what happens when a user's card is declined? Do you downgrade them immediately? Send an email? Give a grace period?
  • Subscription changes — upgrades, downgrades, cancellations mid-cycle. Proration logic is not fun.

This is the Stripe webhook handler you actually need in production:

@webhooks_bp.route('/stripe', methods=['POST'])
def stripe_webhook():
    payload = request.data
    sig_header = request.headers.get('Stripe-Signature')

    try:
        event = stripe.Webhook.construct_event(
            payload, sig_header, endpoint_secret
        )
    except ValueError:
        return jsonify({'error': 'Invalid payload'}), 400
    except stripe.error.SignatureVerificationError:
        return jsonify({'error': 'Invalid signature'}), 400

    if event['type'] == 'checkout.session.completed':
        handle_checkout_completed(event['data']['object'])
    elif event['type'] == 'customer.subscription.deleted':
        handle_subscription_cancelled(event['data']['object'])

    return jsonify({'status': 'ok'})

Skip the signature verification and you're vulnerable. Skip the idempotency and you'll double-charge users. Skip the failed payment handling and you'll lose revenue silently.

Most developers spend another 1-2 weeks on payments. At minimum.


3. Emails Are a Reliability Problem, Not a Code Problem

Sending an email in Python is three lines of code. Sending emails reliably is a different problem.

You need transactional emails for:

  • Email verification on signup
  • Password reset
  • Payment confirmation
  • Subscription renewal reminder
  • Failed payment notification
  • Account deletion confirmation

The code is simple with a service like Resend:

import resend

resend.api_key = os.getenv("RESEND_API_KEY")

def send_purchase_confirmation(to_email, user_name):
    resend.Emails.send({
        "from": "LaunchStack <noreply@launchstack.space>",
        "to": to_email,
        "subject": "Your purchase is confirmed",
        "html": f"<p>Hey {user_name}, you're all set.</p>"
    })

But the real work is:

  • HTML email templates that look good on all clients (Gmail, Outlook, Apple Mail)
  • Handling bounces and unsubscribes
  • Not ending up in spam
  • Making sure emails actually send when your server is under load

Another few days of work, at minimum.


4. The Edge Cases Nobody Thinks About

These are the things that will break your app at 2am six months after launch:

User deletes their account — do you hard delete or soft delete? What happens to their data? Their subscription? Their invoices?

Refunds — a user wants their money back. How does that flow through your system? Does it automatically cancel their subscription?

Concurrent requests — two requests hit your server at the same time and both try to update the same user record. Race condition.

Database migrations — you need to add a column to a table that has 50,000 rows. Your app can't go down while this runs.

Token expiry edge cases — user has the app open in two tabs. One tab refreshes the token. The other tab tries to use the old token. What happens?

None of these are hard to solve individually. But each one takes time to think through, implement, and test.


5. Infrastructure Is the Tax You Pay to Ship

You have a working local app. Now you need to deploy it.

  • Hosting — Vercel for the Next.js frontend is easy. Flask backend needs a server: Railway, Render, Fly.io, a VPS.
  • Database — local SQLite is fine for development. Production needs PostgreSQL, proper backups, connection pooling.
  • Environment variables — you have 15 env vars. Where do they live? How do you manage them across dev, staging, and production?
  • CORS — your frontend at yourdomain.com can't talk to your backend at api.yourdomain.com until you configure this correctly.
  • SSL — HTTPS everywhere. Let's Encrypt is free but you need to set it up.
  • Monitoring — how do you know when your app is down? When an error happens?

This is the CORS config that actually works in production with Flask:

CORS(app, resources={
    r"/*": {
        "origins": "*",
        "methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
        "allow_headers": ["Content-Type", "Authorization"],
        "supports_credentials": True,
        "max_age": 3600
    }
})

Getting infrastructure right is another week of work. Maybe two.


The Real Timeline

Here's what building a SaaS actually looks like for most developers:

Task Expected Reality
Authentication 2 days 1-2 weeks
Payments 1 day 1-2 weeks
Emails 2 hours 2-3 days
Infrastructure 1 day 1 week
Edge cases Ongoing

That's 4-6 weeks before you've written a single line of your actual product.


Why SaaS Boilerplates Exist

This is exactly why SaaS boilerplates exist.

Instead of spending weeks setting up authentication, payments, and infrastructure, many founders start from a pre-built foundation that already handles all of this correctly.

That's actually why I built LaunchStack — a production-ready Next.js + Flask boilerplate for Python developers. It comes with JWT auth, Google OAuth, Stripe payments with webhook handling, transactional emails via Resend, and a complete deployment setup.

The goal is simple: skip the boring parts. Start with everything already working. Ship the product you actually want to build.

If you're a Python developer who wants to launch a SaaS without spending weeks on setup, check it out.


Built with LaunchStack? I'd love to hear what you're working on — reach out on Twitter.

LaunchStack

Skip the setup.
Ship your product.

Everything in this guide — JWT auth, Stripe webhooks, CORS — pre-built and production-ready. Start with a working foundation on day one.

JWT auth with refresh tokens
Stripe payments + webhooks
Google & GitHub OAuth
CORS configured for Next.js
Email system with templates
Admin dashboard
Get LaunchStack — $99Launches Feb 24 · Early bird pricing