Skip to content

Route waitlist form through API with Resend email notification#7

Merged
kushal238 merged 2 commits into
mainfrom
feature/waitlist-email-notification
Mar 4, 2026
Merged

Route waitlist form through API with Resend email notification#7
kushal238 merged 2 commits into
mainfrom
feature/waitlist-email-notification

Conversation

@kushal238

Copy link
Copy Markdown
Owner

Submissions now go through /api/waitlist which inserts into Supabase and sends a notification email via Resend. Email failure is non-blocking so signups are never lost.

Submissions now go through /api/waitlist which inserts into Supabase
and sends a notification email via Resend. Email failure is non-blocking
so signups are never lost.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented Feb 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ariamed Ready Ready Preview, Comment Mar 4, 2026 8:02pm

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @kushal238, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the waitlist submission flow by introducing a dedicated API endpoint. This change centralizes form data handling, improves data validation, and integrates a reliable email notification system using Resend, all while ensuring that the core functionality of adding users to the waitlist remains robust even if email notifications encounter issues.

Highlights

  • New API Endpoint: A new API route, /api/waitlist, has been introduced to handle all waitlist form submissions, centralizing the data processing logic.
  • Supabase Integration: Waitlist data is now securely inserted into a Supabase database via the new API endpoint, ensuring persistence of sign-ups.
  • Resend Email Notifications: Upon successful waitlist submission, an email notification is sent using the Resend service. This process is non-blocking, meaning email delivery failures will not prevent a user from successfully joining the waitlist.
  • Client-Side Refactor: The client-side waitlist form (app/waitlist/page.tsx) has been updated to submit data to the new API endpoint, removing direct Supabase client-side interaction.
  • Data Validation: The new API endpoint includes robust data validation using Zod to ensure the integrity and correctness of submitted waitlist information.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • app/api/waitlist/route.ts
    • Added new API route for handling POST requests to /api/waitlist.
    • Implemented Zod schema for request body validation.
    • Integrated Supabase client to insert validated waitlist data.
    • Integrated Resend to send email notifications for new sign-ups.
    • Ensured email sending is non-blocking, allowing waitlist sign-ups to succeed even if email delivery fails.
    • Logic for constructing full phone numbers with country calling codes was added.
  • app/waitlist/page.tsx
    • Removed direct Supabase client import and usage.
    • Updated onSubmit function to make a fetch call to the new /api/waitlist endpoint.
    • Added error handling for API responses to provide user feedback.
  • package-lock.json
    • Updated to reflect new dependencies for Resend, including various email parsing and handling libraries.
    • Added dependencies related to Playwright for testing purposes.
  • package.json
    • Added resend package as a new dependency.
    • Added @playwright/test to devDependencies.
    • Adjusted positions of pixelmatch and pngjs within devDependencies.
Activity
  • A new API route has been created to serve as the central point for waitlist submissions.
  • The client-side form has been updated to communicate with this new API, abstracting backend logic.
  • Supabase is now used on the server-side for database interactions, enhancing security and control.
  • Resend has been integrated to provide email notifications, with a focus on fault tolerance.
  • Dependencies were updated to support the new integrations and testing tools.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request successfully refactors the waitlist form submission to use a new API endpoint. The API handles data validation, insertion into Supabase, and sends an email notification via Resend. The changes are well-structured. I've provided a few suggestions to improve security and performance on the server-side, such as using the Supabase service role key and moving expensive initializations out of the request handler. I've also suggested a minor improvement for code readability.

Comment thread app/api/waitlist/route.ts Outdated
Comment on lines +57 to +67
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;

if (!supabaseUrl || !supabaseAnonKey) {
return NextResponse.json(
{ error: "Server misconfiguration" },
{ status: 500 },
);
}

const supabase = createClient(supabaseUrl, supabaseAnonKey);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

For server-side operations, it's a security best practice to use the Supabase service role key instead of the public anonymous key. The service role key bypasses Row Level Security (RLS) and is meant for trusted server environments. Using the public anon key subjects this endpoint to the same RLS policies as a client-side request.

Please use the service role key stored in a private environment variable (e.g., SUPABASE_SERVICE_KEY, without the NEXT_PUBLIC_ prefix).

Suggested change
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
if (!supabaseUrl || !supabaseAnonKey) {
return NextResponse.json(
{ error: "Server misconfiguration" },
{ status: 500 },
);
}
const supabase = createClient(supabaseUrl, supabaseAnonKey);
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseServiceKey = process.env.SUPABASE_SERVICE_KEY;
if (!supabaseUrl || !supabaseServiceKey) {
return NextResponse.json(
{ error: "Server misconfiguration" },
{ status: 500 },
);
}
const supabase = createClient(supabaseUrl, supabaseServiceKey);

Comment thread app/api/waitlist/route.ts Outdated
Comment on lines +48 to +51
const callingCodes = customList(
"countryCode",
"+{countryCallingCode}",
) as Record<string, string>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve performance, objects that can be reused across requests should be initialized only once at the module level, instead of inside the request handler.

  • The callingCodes object can be created once at the module scope.
  • The resend client (initialized on line 95) can also be initialized once at the module scope.

This avoids redundant computation and object creation on each API call.

Example:

// At module scope
const callingCodes = customList("countryCode", "+{countryCallingCode}") as Record<string, string>;
const resend = process.env.RESEND_API_KEY ? new Resend(process.env.RESEND_API_KEY) : null;

export async function POST(request: Request) {
  // ... remove initializations from here and use the module-scoped variables
}

Comment thread app/api/waitlist/route.ts
Comment on lines +100 to +109
text: [
`New waitlist signup`,
``,
`Name: ${values.name}`,
`Email: ${values.email}`,
`Phone: ${fullPhoneNumber}`,
`User type: ${values.userType}${values.userTypeOther ? ` (${values.userTypeOther})` : ""}`,
`Features: ${values.features.join(", ")}${values.featuresOther ? ` (Other: ${values.featuresOther})` : ""}`,
`Source: ${values.source}${values.sourceOther ? ` (${values.sourceOther})` : ""}`,
].join("\n"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The construction of the email body can be simplified and made more readable by using a template literal instead of joining an array of strings.

        text: `New waitlist signup

Name: ${values.name}
Email: ${values.email}
Phone: ${fullPhoneNumber}
User type: ${values.userType}${values.userTypeOther ? ` (${values.userTypeOther})` : ""}
Features: ${values.features.join(", ")}${values.featuresOther ? ` (Other: ${values.featuresOther})` : ""}
Source: ${values.source}${values.sourceOther ? ` (${values.sourceOther})` : ""}`,

Address PR review: use SUPABASE_SERVICE_KEY instead of public anon
key for server-side inserts, and move callingCodes map and Resend
client to module scope to avoid re-creating per request.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@kushal238

Copy link
Copy Markdown
Owner Author

/gemini review

@kushal238
kushal238 merged commit e74d1e5 into main Mar 4, 2026
3 checks passed

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request routes waitlist form submissions through an API endpoint (/api/waitlist) to insert data into Supabase and send notification emails via Resend, removing direct client-side Supabase integration. A critical security concern has been identified where server-side logs may inadvertently capture full error objects from external services, potentially exposing user PII such as email addresses. It is recommended to sanitize these logs to enhance data privacy. The implementation also includes form validation using Zod, error handling, and non-blocking email notifications.

Comment thread app/api/waitlist/route.ts
].join("\n"),
});
} catch (emailError) {
console.error("Failed to send notification email:", emailError);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

critical: Sensitive information is being logged. The emailError object might contain sensitive information. Ensure that you only log the relevant parts of the error and avoid logging potentially sensitive data.

Comment thread app/api/waitlist/route.ts
Comment on lines +66 to +71
if (!supabaseUrl || !supabaseServiceKey) {
return NextResponse.json(
{ error: "Server misconfiguration" },
{ status: 500 },
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

high: It's good to check for supabaseUrl and supabaseServiceKey, but consider logging a more specific error message indicating which environment variable is missing. This will help in debugging server misconfigurations.

  if (!supabaseUrl) {
    console.error("NEXT_PUBLIC_SUPABASE_URL is missing");
    return NextResponse.json(
      { error: "Server misconfiguration: NEXT_PUBLIC_SUPABASE_URL is missing" },
      { status: 500 },
    );
  }
  if (!supabaseServiceKey) {
    console.error("SUPABASE_SERVICE_KEY is missing");
    return NextResponse.json(
      { error: "Server misconfiguration: SUPABASE_SERVICE_KEY is missing" },
      { status: 500 },
    );
  }

Comment thread app/waitlist/page.tsx
Comment on lines +81 to +82
const data = await res.json().catch(() => null);
throw new Error(data?.error || "Request failed");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

high: The catch block uses .catch(() => null) which can mask errors. It's better to handle the error explicitly to avoid swallowing important information. Consider logging the error or providing a default error message.

        const data = await res.json().catch((error) => {
          console.error("Error parsing JSON response:", error);
          return { error: "Failed to parse server response" }; // Provide a default error message
        });

Comment thread app/api/waitlist/route.ts
].join("\n"),
});
} catch (emailError) {
console.error("Failed to send notification email:", emailError);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

This line logs the full error object from Resend, which can inadvertently expose sensitive user information (PII) like email addresses to log management systems. It is crucial to sanitize logs by only capturing the error message or a generic failure notification to prevent PII leakage. Furthermore, while the email sending is non-blocking, consider implementing retry logic within this catch block to enhance the reliability of email notifications.

Suggested change
console.error("Failed to send notification email:", emailError);
console.error("Failed to send notification email:", emailError instanceof Error ? emailError.message : "Unknown error");

Comment thread app/api/waitlist/route.ts
});

if (dbError) {
console.error("Supabase insert error:", dbError);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The application logs the full error object from Supabase when a database insertion fails. These error objects, particularly for unique constraint violations (e.g., duplicate email), often contain the sensitive data that caused the error, such as the user's email address. Logging this information exposes Personally Identifiable Information (PII) to anyone with access to the server logs or log management systems. It is recommended to log only non-sensitive information, such as the error message or error code.

Suggested change
console.error("Supabase insert error:", dbError);
console.error("Supabase insert error:", dbError.message);

Comment thread app/api/waitlist/route.ts
Comment on lines +7 to +11
// Hoist static objects to module scope to avoid re-creating per request
const callingCodes = customList(
"countryCode",
"+{countryCallingCode}",
) as Record<string, string>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

medium: Consider using a more descriptive name for callingCodes to improve readability. For example, countryCallingCodes or countryCodeMap would be more explicit.

Comment thread app/api/waitlist/route.ts
Comment on lines +13 to +15
const resend = process.env.RESEND_API_KEY
? new Resend(process.env.RESEND_API_KEY)
: null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

medium: It's good to handle the case where RESEND_API_KEY is not defined, but consider adding a log message to indicate when the Resend client is not initialized. This can help with debugging in environments where the email functionality is expected to work.

Suggested change
const resend = process.env.RESEND_API_KEY
? new Resend(process.env.RESEND_API_KEY)
: null;
const resend = process.env.RESEND_API_KEY
? new Resend(process.env.RESEND_API_KEY)
: (console.warn("RESEND_API_KEY is not defined. Email notifications will not be sent."), null);

Comment thread package.json
Comment on lines +86 to 91
"pixelmatch": "^5.3.0",
"pngjs": "^7.0.0",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.17",
"typescript": "^5.8.3",
"typescript-eslint": "^8.38.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

medium: The dependencies @playwright/test, pixelmatch, and pngjs are defined twice in package.json. Remove the duplicate entries to keep the file clean and avoid potential conflicts.

@kushal238
kushal238 deleted the feature/waitlist-email-notification branch March 4, 2026 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant