> ## Documentation Index
> Fetch the complete documentation index at: https://docs.picaos.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a Waitlist App with InstantDB

> Build a waitlist app using InstantDB with real-time signups, automatic welcome emails, and admin dashboard in minutes.

<img src="https://mintcdn.com/pica-236d4a1e/IxuRuKmaOMSptW7A/images/tutorials/instantdb-waitlist-app.png?fit=max&auto=format&n=IxuRuKmaOMSptW7A&q=85&s=888a2ea5baa95902161bb4442b668be2" alt="InstantDB Waitlist App" style={{ borderRadius: '5px' }} width="1270" height="760" data-path="images/tutorials/instantdb-waitlist-app.png" />

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**](https://picaos.com/) + [**InstantDB**](https://instantdb.com/) 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.
* **Pica** → **A 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

<Steps>
  <Step title="Create Next.js App (App Router)">
    1. Initialize the app with TypeScript, ESLint, Tailwind, and App Router.

    <Accordion title="Terminal Commands">
      ```bash theme={null}
      npx create-next-app@latest waitlist-app \
        --ts \
        --eslint \
        --app \
        --tailwind \
        --src-dir=false \
        --import-alias "@/*"
      ```
    </Accordion>

    2. Install dependencies and Start the dev server and verify the app loads.

    <Accordion title="Start Development Server">
      ```bash theme={null}
      cd waitlist-app
      npm install @instantdb/react @instantdb/core
      npm run dev
      ```
    </Accordion>
  </Step>

  <Step title="Environment Variables">
    1. Create a `.env.local` with the following keys:
       * `NEXT_PUBLIC_INSTANT_APP_ID=`
       * `PICA_SECRET_KEY=`
       * `PICA_GMAIL_CONNECTION_KEY=`

    <Accordion title="Environment File Setup">
      ```bash theme={null}
      # .env.local
      NEXT_PUBLIC_INSTANT_APP_ID=
      PICA_SECRET_KEY=
      PICA_GMAIL_CONNECTION_KEY=
      ```
    </Accordion>

    2. Get values from:
       * [**Pica API keys**](https://app.picaos.com/settings/api-keys)
       * [**Pica Gmail connection**](https://app.picaos.com/connections)
         * **Instant DB APP ID**: Go to your instant db dashbaord to get this ID.
  </Step>

  <Step title="InstantDB Client and Schema (lib/db.ts)">
    Create the database configuration file and schema.

    📂 `lib/db.ts`

    Paste this prompt into **Cursor** (or your preferred AI-powered IDE) so it creates the client file:

    <Accordion title="InstantDB Setup Prompt">
      ```text theme={null}
      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.
      ```
    </Accordion>
  </Step>

  <Step title="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.ts`

    Paste this prompt into **Cursor** to create the API route that sends email via Pica Gmail passthrough:

    <Accordion title="Email API Route Prompt">
      ```text theme={null}
      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.
      ```
    </Accordion>
  </Step>

  <Step title="Create the UI (app/page.tsx)">
    Create the main signup form component with real-time updates.

    📂 `app/page.tsx`

    Use this prompt in **Cursor** to generate the client form page. It includes loading/success/error states and uses InstantDB transactions correctly.

    <Accordion title="UI Form Prompt">
      ```text theme={null}
      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 })])`
      ```
    </Accordion>
  </Step>

  <Step title="Admin Dashboard (app/admin)">
    Create admin page and dashboard table with filters and actions.

    📂 `app/admin/page.tsx`

    Use this prompt in **Cursor** to generate the admin page and dashboard table with filters and actions.

    <Accordion title="Admin Dashboard Prompt">
      ```text theme={null}
      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.
      ```
    </Accordion>

    <img src="https://mintcdn.com/pica-236d4a1e/IxuRuKmaOMSptW7A/images/tutorials/instantdb-waitlist-app-admin.png?fit=max&auto=format&n=IxuRuKmaOMSptW7A&q=85&s=db6a4f3bfc329666402c61798e2e0cfb" alt="InstantDB Waitlist App Admin" style={{ borderRadius: '5px' }} width="1505" height="631" data-path="images/tutorials/instantdb-waitlist-app-admin.png" />
  </Step>
</Steps>

<Check>Great job! You've built a waitlist app with real-time signups, automatic welcome emails, and an admin dashboard to manage it all.</Check>

<Card title="Source Code" icon="github" href="https://github.com/picahq/awesome-pica/tree/main/instantdb-waitlist-app">
  View the complete source code for this example on GitHub
</Card>

**References:**

* [Pica API Keys](https://app.picaos.com/settings/api-keys)
* [Pica Connections](https://app.picaos.com/connections)
* [Pica Documentation](https://docs.picaos.com)
* [InstantDB Documentation](https://docs.instantdb.com)
