You have paying customers. You are not sure if they are happy. You have two options: pay $50-200 a month for a tool like Delighted or Survicate, or build it yourself in a few hours using the database and email infrastructure you already have.
NPS -- Net Promoter Score -- is a single question: "On a scale of 0-10, how likely are you to recommend this to a friend or colleague?" It is the most widely-used satisfaction metric in SaaS because it predicts churn and word-of-mouth growth better than almost anything else you can measure.
This post shows you how to add NPS to a Next.js SaaS boilerplate -- with a Drizzle ORM table, a timing rule that avoids annoying users, and a Resend alert when a score drops.
Responders fall into three groups:
Your NPS = % promoters minus % detractors. Above 30 is good for B2B SaaS. Above 50 is strong.
The number matters less than the comment. A score of 5 with "I can never find the export button" tells you exactly what to fix. A batch of anonymous 9s just confirms you are not actively broken.
The boilerplate already has Drizzle ORM and Neon DB for the table, JWT auth so you know exactly which user is responding, Resend for the alert email, and the Shadcn Dialog component for the modal. You are wiring them together, not building from scratch.
Add this to your modules folder:
export const npsResponseTable = pgTable(nps_responses, {
id: uuid(id).defaultRandom().primaryKey(),
userId: uuid(user_id).notNull().references(() => userTable.id),
score: integer(score).notNull(), // 0-10
comment: text(comment),
createdAt: timestamp(created_at).defaultNow().notNull(),
});
Run npm run db:generate and npm run db:migrate. That is the entire data layer.
Do not show NPS on day one. Users have not had time to form an opinion. A common rule: show it after 14 days, then again no sooner than 90 days after the last response.
Your service layer can enforce this with two checks before returning the survey flag:
function shouldShowNps(user: User, lastResponse: Date | null): boolean {
const msPerDay = 86_400_000;
const daysSinceSignup = (Date.now() - user.createdAt.getTime()) / msPerDay;
if (daysSinceSignup < 14) return false;
if (!lastResponse) return true;
const daysSinceLast = (Date.now() - lastResponse.getTime()) / msPerDay;
return daysSinceLast > 90;
}
Call this in the server component that renders your dashboard. If it returns true, pass a prop down to the client component that mounts the modal.
Use the Shadcn Dialog component. Render 11 buttons labeled 0 through 10. Below the buttons, add an optional textarea for a comment and a submit button.
Keep it short. Every extra field drops your response rate. The goal is one question with an optional comment -- nothing else.
On submit, call your API route which writes the score to nps_responses and, if the score is 6 or below, fires a Resend alert to your support inbox.
This is where the real value lives. When a paying customer gives you a 4, you have a window -- maybe 30-60 days -- to fix the problem before they cancel.
A Resend email that fires on score <= 6 should include:
Your support team can reply personally within an hour. That one conversation is worth more than a month of broadcast emails. For a broader look at using email to fight churn, see How to Reduce SaaS Churn.
Delighted, Survicate, and AskNicely give you beautiful trend dashboards, response segmentation, and NPS benchmarks against your industry. If you want all of that on day one and are willing to pay, use them.
Build it yourself when:
The core is one table, one function that decides when to show the survey, and one modal. You can add a score-over-time chart to your metrics dashboard later -- the data will already be there.
If you are building a SaaS and want auth, payments, email, and Drizzle ORM already wired up, get the Next.js SaaS Boilerplate and add your NPS survey in an afternoon instead of a week. Your first detractor alert email might be the most valuable thing you send all month.