Skip to main content
InstantDB Waitlist App Most apps today need two things:
  • A database to store and update data.
  • An integration layer to send emails, connect APIs, or trigger workflows.
Traditionally, that means setting up:
  • SQL databases, migrations, ORMs
  • OAuth flows, SMTP servers, or 3rd-party email APIs
  • Hours of boilerplate before you even ship your MVP
But what if you could skip all that? With Pica + InstantDB you can build full-stack apps in minutes, not days. Recommended Setup:
  • Cursor IDE with any AI model of your choice (Claude 3.5 Sonnet, GPT-4, etc.)
  • Next.js App Router with TypeScript and Tailwind
  • Pica for email integration
  • InstantDB for real-time database

Why Pica + InstantDB?

  • InstantDB → real-time, schema-less database. Add data, query it, and get live updates — no setup required.
  • PicaA unified API platform with hundreds of integrations (Gmail, Slack, Notion, QuickBooks, Shopify, and more). No need for custom OAuth workflows—just one secure API key handles everything.
  • Next.js → modern React-based full-stack framework.
With this stack, you can build:
  • 📧 Mailing lists (what we’ll demo here)
  • 💬 Real-time chat apps
  • ✅ To-do apps with email reminders
  • 📊 Admin dashboards with live updates
👉 In this guide, we’ll build a waiting list app with real-time signups and automatic welcome emails.

What You’ll Get

  • A user signs up → their info appears in your dashboard instantly.
  • At the same time → they get a personalized Gmail welcome email.
  • All this with less than 150 lines of code.

Step-by-step build with prompts

1

Create Next.js App (App Router)

  1. Initialize the app with TypeScript, ESLint, Tailwind, and App Router.
npx create-next-app@latest waitlist-app \
  --ts \
  --eslint \
  --app \
  --tailwind \
  --src-dir=false \
  --import-alias "@/*"
  1. Install dependencies and Start the dev server and verify the app loads.
cd waitlist-app
npm install @instantdb/react @instantdb/core
npm run dev
2

Environment Variables

  1. Create a .env.local with the following keys:
    • NEXT_PUBLIC_INSTANT_APP_ID=
    • PICA_SECRET_KEY=
    • PICA_GMAIL_CONNECTION_KEY=
# .env.local
NEXT_PUBLIC_INSTANT_APP_ID=
PICA_SECRET_KEY=
PICA_GMAIL_CONNECTION_KEY=
  1. Get values from:
3

InstantDB Client and Schema (lib/db.ts)

Create the database configuration file and schema.📂 lib/db.tsPaste this prompt into Cursor (or your preferred AI-powered IDE) so it creates the client file:
Create a TypeScript file at path: lib/db.ts

Requirements:
- Use imports:
  import { init } from '@instantdb/react';
  import { i } from '@instantdb/core';
- Define a schema with one entity: signups
  - Fields:
    - name: string
    - email: string
    - interest: string (optional)
    - status: string // 'pending', 'confirmed', 'notified'
    - welcomeEmailSent: boolean
    - createdAt: date
- Initialize and export an InstantDB client with this schema using NEXT_PUBLIC_INSTANT_APP_ID
- Export the schema type

File contents:
------------------------------------------------
import { init } from '@instantdb/react';
import { i } from '@instantdb/core';

// Define your schema
const schema = i.schema({
  entities: {
    signups: i.entity({
      name: i.string(),
      email: i.string(),
      interest: i.string().optional(),
      status: i.string(), // 'pending', 'confirmed', 'notified'
      welcomeEmailSent: i.boolean(),
      createdAt: i.date(),
    }),
  },
});

export const db = init({
  appId: process.env.NEXT_PUBLIC_INSTANT_APP_ID!,
  schema,
});

export type Schema = typeof schema;
------------------------------------------------

Notes:
- Do not change indentation or spacing.
- Do not add extra comments or code.
4

Email API Route (app/api/send-welcome-email/route.ts)

Create the API endpoint for sending welcome emails via Pica Gmail passthrough.📂 app/api/send-welcome-email/route.tsPaste this prompt into Cursor to create the API route that sends email via Pica Gmail passthrough:
Create a TypeScript file at path: app/api/send-welcome-email/route.ts

Requirements:
- App Router API route (Next.js 13+):
  import { NextRequest, NextResponse } from 'next/server';
- Parse JSON body: { name: string; email: string; signupId: string }
- Construct an HTML email with a styled welcome message (as provided below)
- Build a MIME message with:
  From: your Gmail (placeholder: your-email@gmail.com)
  To, Subject, MIME-Version, Content-Type: text/html; charset=UTF-8
- Base64url-encode the MIME (Gmail requirement)
- Call Pica Gmail passthrough:
  POST https://api.picaos.com/v1/passthrough/users/me/messages
  Headers:
    - x-pica-secret: process.env.PICA_SECRET_KEY!
    - x-pica-connection-key: process.env.PICA_GMAIL_CONNECTION_KEY!
    - x-pica-action-id: 'conn_mod_def::F_JeJ3qaLEg::v9ICSQZxR0un5_ketxbCAQ'
    - Content-Type: application/json
  Body: { raw: <base64url string>, labelIds: ['INBOX','UNREAD'] }
- On success: return { success: true, messageId, message }
- On error: return 500 with { success: false, error, details }

Exact file contents:
------------------------------------------------
import { NextRequest, NextResponse } from 'next/server';

interface WelcomeEmailRequest {
  name: string;
  email: string;
  signupId: string;
}

export async function POST(request: NextRequest) {
  try {
    const { name, email }: WelcomeEmailRequest = await request.json();

    console.log('📧 Sending email to:', email);

    // Get current date for subject
    const currentDate = new Date().toLocaleDateString('en-US', {
      month: 'long',
      day: 'numeric', 
      year: 'numeric'
    });

    const subject = `🎉 Welcome to the Waiting List!`;

    // Create HTML email body
    const htmlBody = `
    <div style="font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; max-width: 600px; margin: 0 auto; background: #ffffff; color: #2c3e50; line-height: 1.7; font-size: 16px; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
      <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 10px 10px 0 0; text-align: center;">
        <h1>Welcome Aboard! 🚀</h1>
      </div>
      <div style="background: #f9f9f9; padding: 30px; border-radius: 0 0 10px 10px;">
        <h2>Hi ${name}!</h2>
        <p>Thank you for joining our exclusive waiting list! You're now part of an amazing community of early adopters.</p>
        
        <p><strong>What happens next?</strong></p>
        <ul style="margin: 15px 0; padding-left: 25px; list-style-type: disc;">
          <li style="margin: 8px 0;">You'll be among the first to know about our launch</li>
          <li style="margin: 8px 0;">Get exclusive early access to new features</li>
          <li style="margin: 8px 0;">Receive special launch pricing</li>
        </ul>
        
        <p>Stay tuned for exciting updates!</p>
        
        <p>Best regards,<br>
        The Team</p>
      </div>
    </div>

    <hr style="border: none; border-top: 1px solid #eee; margin: 30px 0;">
    <p style="color: #666; font-size: 12px; text-align: center;">
      Generated automatically by Waiting List System<br>
      Powered by InstantDB & Pica
    </p>`;

    // Construct proper MIME email
    const mimeEmail = [
      `From: your-email@gmail.com`, // Replace with your actual Gmail
      `To: ${email}`,
      `Subject: ${subject}`,
      'MIME-Version: 1.0',
      'Content-Type: text/html; charset=UTF-8',
      '',
      htmlBody,
    ].join('\r\n');

    console.log('📝 MIME email constructed');

    // Encode to base64url (Gmail API requirement) 
    const encoder = new TextEncoder();
    const data = encoder.encode(mimeEmail);
    const base64 = btoa(String.fromCharCode(...data));
    const encodedEmail = base64
      .replace(/\+/g, '-')
      .replace(/\//g, '_')
      .replace(/=/g, '');

    console.log('🔐 Email encoded to base64url');

    // Send via Pica Gmail API
    const response = await fetch('https://api.picaos.com/v1/passthrough/users/me/messages', {
      method: 'POST',
      headers: {
        'x-pica-secret': process.env.PICA_SECRET_KEY!,
        'x-pica-connection-key': process.env.PICA_GMAIL_CONNECTION_KEY!,
        'x-pica-action-id': 'conn_mod_def::F_JeJ3qaLEg::v9ICSQZxR0un5_ketxbCAQ',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ 
        raw: encodedEmail,
        labelIds: ['INBOX', 'UNREAD']
      })
    });

    console.log(`📡 Pica response status: ${response.status}`, process.env.PICA_GMAIL_CONNECTION_KEY);

    if (!response.ok) {
      const errorText = await response.text();
      console.error('❌ Pica error response:', errorText);
      throw new Error(`Pica API error: ${response.status} - ${errorText}`);
    }

    const result = await response.json();
    console.log('✅ Email sent successfully:', result);
    
    return NextResponse.json({ 
      success: true, 
      messageId: result.id,
      message: 'Welcome email sent successfully!' 
    });

  } catch (error) {
    console.error('💥 Error sending welcome email:', error);
    
    return NextResponse.json(
      { 
        success: false, 
        error: 'Failed to send welcome email',
        details: error instanceof Error ? error.message : 'Unknown error'
      },
      { status: 500 }
    );
  }
}
------------------------------------------------

Environment variables required in .env.local:
- NEXT_PUBLIC_INSTANT_APP_ID=...
- PICA_SECRET_KEY=...
- PICA_GMAIL_CONNECTION_KEY=...

Notes:
- Replace the From address with your connected Gmail.
- Keep the exact indentation and spacing.
5

Create the UI (app/page.tsx)

Create the main signup form component with real-time updates.📂 app/page.tsxUse this prompt in Cursor to generate the client form page. It includes loading/success/error states and uses InstantDB transactions correctly.
Design a beautiful client form in app/page.tsx with fields: name, email, interest (select with early-access, pricing, features, updates). Style with a glassy/gradient card. On submit:
1) Create a `signups` record in InstantDB with status 'pending' and welcomeEmailSent false
2) Call /api/send-welcome-email with { name, email, signupId }
3) On success, set status 'notified' and welcomeEmailSent true
4) Ensure text are visible on light and dark background
Show loading, success, and error states; reset form on success; ensure accessibility.

Important: Use InstantDB transactions exactly as follows so it works on first try:
- Import `id` from `@instantdb/core`
- Generate an id with `const signupId = id()`
- Create: `await db.transact([db.tx.signups[signupId].create({ name, email, interest, status: 'pending', welcomeEmailSent: false, createdAt: new Date() })])`
- Update: `await db.transact([db.tx.signups[signupId].update({ status: 'notified', welcomeEmailSent: true })])`
6

Admin Dashboard (app/admin)

Create admin page and dashboard table with filters and actions.📂 app/admin/page.tsxUse this prompt in Cursor to generate the admin page and dashboard table with filters and actions.
Create an admin page at app/admin/page.tsx and a dashboard component. Display a table of signups with columns: name, email, interest, status, createdAt. Add a status filter (all | pending | notified | confirmed). Provide per-row actions to set status to 'confirmed' or back to 'pending' using InstantDB; implement optimistic updates and graceful error recovery.
InstantDB Waitlist App Admin
Great job! You’ve built a waitlist app with real-time signups, automatic welcome emails, and an admin dashboard to manage it all.

Source Code

View the complete source code for this example on GitHub
References:
I