After the first run

After the first run

After your first run: what to know

Great news — your SaaS app is up and running. Before we go ahead, here are a few essentials about its current setup; we’ll cover the highlights first, and you can dig deeper right after.

Dev email flow (important)

When you sign up a new user, you’ll be told to “check your email.” In development, emails are logged to the server, not sent. Open your server logs and click the verification link.

bash

    [ Server ] ╔═══════════════════════╗
    [ Server ] ║ Dummy email sender ✉️ ║
    [ Server ] ╚═══════════════════════╝
    [ Server ] From:    LaunchPike App 
    [ Server ] To:      [email protected]
    [ Server ] Subject: Verify your email
    [ Server ] ═════════ Text ═════════
    [ Server ] Click the link below to verify your email: Link
    [ Server ] ═════════ HTML ═════════
  

What isn’t configured yet

Payments (UniBee/Stripe/Lemon Squeezy), AWS S3, social Auth, Analytics, etc. need API keys/config. The guides that follow show you how to wire each one.


Codebase Tour

At the root you’ll see:

bash

    .
    ├── app        # your Wasp full-stack app (React + NodeJS + Prisma) incl. main.wasp
    └── e2e-tests  # Playwright end-to-end tests
  
  • app — your Wasp full-stack app (React + NodeJS + Prisma) incl. main.wasp
  • blog — Astro Starlight docs/blog
  • e2e-tests — Playwright end-to-end tests

App file structure (vertical by feature)

Next, we’ll walk through the app/ folder so you know where things are.

Using Wasp v0.13 or below? You might see a slightly different structure; features remain the same.

bash

    .
    ├── main.wasp              # Wasp Config file. You define your app structure here
    ├── .wasp/                 # Output dir for Wasp. DON'T MODIFY THESE FILES
    ├── public/                # Public assets dir, e.g. www.yourdomain.com/favicon.ico
    ├── src/                   # Your code goes here
    │   ├── admin/             # Admin dashboard related pages and components
    │   ├── analytics/         # Logic and background jobs for processing analytics
    │   ├── auth/              # All auth-related pages/components and logic
    │   ├── client/            # Shared components, hooks, landing page, and more
    │   ├── demo-ai-app/       # Logic for the example AI-powered demo app
    │   ├── file-upload/       # Logic for uploading files to S3
    │   ├── landing-page/      # Landing page related code
    │   ├── messages/          # Logic for app user messages
    │   ├── payment/           # Logic for handling payments and webhooks
    │   ├── server/            # Scripts, shared server utils, and other server code
    │   ├── shared/            # Shared constants and util functions
    │   └── user/              # Logic related to users and their accounts
    ├── .env.server            # Dev environment variables for your server
    ├── .env.client            # Dev environment variables for your client
    ├── .prettierrc            # Prettier configuration
    ├── tailwind.config.js     # TailwindCSS configuration
    ├── package.json
    ├── package-lock.json
    └── .wasproot
  

Wasp Config (what it controls)

Wasp compiles your main.wasp into a full client, server, and deploy setup. In this template, main.wasp already defines: Auth, Routes/Pages, Prisma models, Operations (Queries/Actions), Background Jobs, Email sending.

preview

Wasp automates parts of the setup, so a few things you might expect to do or see aren’t visible. That’s normal.

Client

Use src/client for client-side code that isn’t part of a specific feature.

bash

  └── client
      ├── components
      ├── fonts
      ├── hooks
      ├── icons
      ├── static
      ├── App.tsx
      ├── cn.ts
      └── Main.css
  

Server

Use src/server for server-side code that isn’t tied to a single feature.

bash

  └── server
      ├── scripts     # Scripts to run via Wasp, e.g. database scripts
      └── utils.ts
  

Main Features

Auth (ready to use now)

Email + social auth are already defined in main.wasp:

javascript

      // main.wasp
      auth: {
        userEntity: User,
        methods: {
          email: {
            //...
          },
          google: {},
          github: {},
          discord: {}
        },
        onAuthFailedRedirectTo: "/",
      },
  

This wires:

  • Email-verified login + password reset
  • Social login (Google/GitHub)
  • Auth DB entities (credentials, sessions, socials)
  • Generated Auth UI (login/signup/reset)
  • Hooks to fetch user data

This starter already includes Wasp auth via email, Google, and GitHub — all stable and production-ready. You can begin development right now using the email flow.

Dev email sender

To make email sign-in work, the app needs an email sender (configured at app.emailSender in main.wasp) for verification and password resets. During development, the built-in Dummy sender takes over: it prints the verification token/link to your terminal instead of sending mail. Use that link to verify and continue the flow.

javascript

      // main.wasp
      emailSender: {
        provider: Dummy, // logs all email verification links/tokens to the terminal
        defaultFrom: {
          name: "LaunchPike App",
          email: "[email protected]"
        },
      },
  

You’ll integrate real email providers in the Authentication Guide.

Payments (Stripe/Unibee/Lemon Squeezy)

If you need subscription payments, the template’s ready — Stripe/Unibee/Lemon Squeezy are preconfigured.

  1. Stripe: fastest path to production; great docs/plugins; global coverage with SCA/PCI handled.
  2. UniBee: total control, global flexibility — run it where you want, with the payment rails you want; no Stripe bans. Built for deep customization.
  3. Lemon Squeezy: merchant-of-record handles VAT/GST & compliance; simple checkout.

Flow overview

User clicks BUY: → server creates a Checkout session → user lands on Checkout and enters payment info → user returns to the app (session completes) → Stripe/Unibee/Lemon Squeezy sends a webhook → app’s webhook handler updates the user’s subscription status

Key files:

  • Processor selection/logic: src/payment/paymentProcessor.ts
  • Checkout creation (Action): src/payment/operation.ts
  • Webhook handler: src/payment/webhook.ts

Action definition (Wasp)

Checkout session creation is in src/payment/operation.ts.

This is an Action — a server-side Operation used to write or update database state. After you declare the action in main.wasp, you can easily call them on the client-side:

javascript

      // main.wasp
      action generateCheckoutSession {
        fn: import { generateCheckoutSession } from "@src/payment/operations",
        entities: [User]
      }
  

Action implementation (src/payment/operations file)

javascript

      // src/server/actions.ts
      export const generateCheckoutSession = async (paymentPlanId, context) => {
        //...
      }
  

Client call

javascript

      // src/client/app/SubscriptionPage.tsx
      import { generateCheckoutSession } from "wasp/client/operations";
  const handleBuyClick = async (paymentPlanId) => {
    const checkoutSession = await generateCheckoutSession(paymentPlanId);
  };

Webhook endpoint

Your webhook code goes in src/payment/webhook.ts; make it reachable by Stripe by registering an API endpoint in main.wasp (since Actions/Queries aren’t public).

javascript

    // main.wasp
    api paymentsWebhook {
      fn: import { paymentsWebhook } from "@src/payment/webhook",
      httpRoute: (POST, "/payments-webhook")
      entities: [User],
    }
  

The handler processes Payment Processor events to tie a successful payment to a user, followed by a DB update of that user’s subscription state. For step-by-step configuration, check the Payments guide.


Analytics + Admin Dashboard

Metrics matter for every SaaS, so there’s an admin dashboard that shows stats, users, and revenue in one place. We use Jobs to run a daily cron that aggregates metrics; page views and sources come from Plausible or Google Analytics — create a project with your provider and import the prebuilt helpers.

javascript

    // main.wasp
    job dailyStatsJob {
      executor: PgBoss,
      perform: {
        fn: import { calculateDailyStats } from "@src/analytics/stats"
      },
      schedule: {
        cron: "0 * * * *" // runs every hour
      },
      entities: [User, DailyStats, Logs, PageViewSource]
    }
  

See the Analytics [link] guide for wiring Plausible/GA and the dashboard.


Customize it - quick checklist

You’ll see the app running, yet third-party features stay disabled until keys are set.

Services that need your keys:

  • Auth providers (Google, GitHub)
  • Payments (Stripe or Lemon Squeezy)
  • OpenAI
  • Email (SendGrid) — required if using email Auth
  • Analytics (Plausible or Google Analytics)
  • File uploads (AWS S3)

main.wasp basics

Change app name/title:

javascript

      // main.wasp
      app YourAppName {
      wasp: {
        version: "^0.13.2"
      },
      title: "Your App Name",
  
⚠️

Restart required after renaming:
Run wasp db start, wasp db migrate-dev, and wasp start.

Also:

  • Update meta tags in app.head (set future domain even if not live yet).

  • Update app.emailSender.defaultFrom.name (what users see in inbox).

  • Remove features you don’t need:

    • Auth methods: app.auth.methods

    • If not using email auth: remove routes/pages RequestPasswordReset, PasswordReset, EmailVerification

    • Email sending: app.emailSender, job emailChecker

    • Plausible in app.head

    • File uploading: entity File, route FileUploadRoute, action createFile, queries getAllFilesByUser, getDownloadFileSignedURL

  • Rename Entities, Routes/Pages, Operations as you wish.

Visual Customization

  • Update public/favicon.ico
  • Replace public/public-banner.webp and its og:image / twitter:image in app.head
  • Edit landing page landingPage.tsx
  • Customize nav, features, testimonials, FAQs in contentSections.ts
  • Replace logo.webp and open-saas-banner.webp under static
  • Global styles: tailwind.config.cjs (note: the existing global custom styles are used mainly in the Admin Dashboard.)

Admin & Analytics Dashboard

  • If using Plausible, set your domain in app.head
  • Adjust calculateDailyStats in src/server/workers/calculateDailyStats.ts for your provider
  • Change dailyStatsJob cron in main.wasp
  • Edit Admin components to show only the stats you need

Env files

  • After following the Guides, add keys to .env.server / .env.client
  • Remove unused vars from .env.* and .env.*.example

Other useful setup

  • Create your GitHub repo
  • Deploy to a host
  • Buy a domain and connect it
  • Read e2e-tests README and adapt tests

What’s next

Proceed to the Guides to wire Payments & Webhooks, Auth, Email, Analytics, S3, and more.

👾

If this starter saves you time, consider ⭐ the repo to keep it thriving. LaunchPike Repo