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.
[ 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:
.
├── 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.
.
├── 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.
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.
└── 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.
└── 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
:
// 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.
// 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.
- Stripe: fastest path to production; great docs/plugins; global coverage with SCA/PCI handled.
- UniBee: total control, global flexibility — run it where you want, with the payment rails you want; no Stripe bans. Built for deep customization.
- 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:
// main.wasp
action generateCheckoutSession {
fn: import { generateCheckoutSession } from "@src/payment/operations",
entities: [User]
}
Action implementation (src/payment/operations
file)
// src/server/actions.ts
export const generateCheckoutSession = async (paymentPlanId, context) => {
//...
}
Client call
// 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).
// 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.
// 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:
// 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
, jobemailChecker
-
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 itsog:image
/twitter:image
inapp.head
- Edit landing page
landingPage.tsx
- Customize nav, features, testimonials, FAQs in
contentSections.ts
- Replace
logo.webp
andopen-saas-banner.webp
understatic
- 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
insrc/server/workers/calculateDailyStats.ts
for your provider - Change
dailyStatsJob
cron inmain.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