Setting up your project

For the Business website template on Next.js 16 with Resend. Only what your project uses, in the order to do it.

Build this guide for your project

Providers
Next.js

1. Run the project

  1. 1Unzip the download and open a terminal in the folder. Run npm install.
  2. 2Copy .env.example to .env.local and fill in the 3 required values: RESEND_API_KEY, EMAIL_FROM, CONTACT_TO_EMAIL. Each provider section below says where to get them.
  3. 3Run npm run dev and open http://localhost:3000. This is a Next.js 16 project (Turbopack by default); it needs Node 20 or newer.
  4. 4The README.md inside the project repeats this guide under Go live, so you can hand the folder to someone else.

Pages in the Business website template

//products/pricing/about/support/contact/terms/privacy

2. Resend (email)

Your project's contact form and newsletter signup send real email through Resend. You need an API key and two addresses. Until they are set, the forms show a friendly "not configured" message instead of failing.

Values you will paste into .env.local

  • RESEND_API_KEYAPI key from the Resend dashboard → API Keys → Create. Permission: Sending access; you can also restrict it to your sending domain. That is all the site needs.Secret.
  • EMAIL_FROMThe address emails are sent from. Must be on a domain you verified in Resend, e.g. hello@yourdomain.com. While testing you can use onboarding@resend.dev.
  • CONTACT_TO_EMAILWhere contact form messages are delivered. Any inbox you read.
  • RESEND_AUDIENCE_IDResend audience to add newsletter subscribers to (Audiences → your audience → the id in the URL). Only used when Supabase is off; with Supabase on, subscribers are stored in your database.
  • FORMS_SECRETUsed when Supabase is on. A password only your server knows: the database functions that store subscribers and contact messages refuse any call without it, so the public Supabase key alone can never write to your lists. Running supabase/migrations/0002_forms.sql prints the value; paste it here.Secret.

Get an API key

  1. 1Create an account at resend.com. The free tier covers a small site.
  2. 2Open API Keys in the left sidebar and click Create API Key. Name it after your site, give it Sending access, and copy the key. It is shown once.

Verify your domain

  1. 1Open Domains and click Add Domain. Enter the domain your site will live on, for example yourdomain.com.
  2. 2Resend shows DNS records to add (an MX, a TXT for SPF, and a TXT for DKIM). Add them at your DNS provider. If your DNS is on Cloudflare: DNS → Records → Add record, one per row, exactly as shown.
  3. 3Click Verify. It usually takes a few minutes, occasionally up to an hour.
  4. 4Until the domain verifies, you can test with EMAIL_FROM=onboarding@resend.dev, which can only send to the email address of your Resend account.

Paste the values into the env file

  1. 1If you haven't yet, copy .env.example to .env.local.
  2. 2Fill in the Resend section:
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
EMAIL_FROM=hello@yourdomain.com
CONTACT_TO_EMAIL=you@yourdomain.com
# With Supabase on: the value supabase/migrations/0002_forms.sql prints
FORMS_SECRET=
# Without Supabase: newsletter storage in a Resend audience
RESEND_AUDIENCE_ID=

RESEND_API_KEY is a secret: keep it out of git and out of the browser. It is only read on the server.

Newsletter storage

  1. 1With Supabase enabled, subscribers are stored in your database with double opt-in: run supabase/migrations/0002_forms.sql in the SQL editor (after the profiles migration). Contact messages are kept there too.
  2. 2The file's last line prints a forms secret. Copy it into .env.local as FORMS_SECRET. Your site's Supabase key is public (every visitor's browser has it), so the tables are closed to it entirely; the three functions the file creates (subscribe, confirm, store a message) answer only when the server also sends this secret. Nobody can read, edit or add to your lists without it. Running the file again keeps the same secret; to read it later, run select secret from private.forms_secret;.
  3. 3The confirmation link is built from NEXT_PUBLIC_SITE_URL (see the Supabase section), so set that to your domain when you deploy.
  4. 4Unsubscribing: every subscriber row has an unsubscribe_token, and /newsletter/unsubscribe?token=… with it opens a page with an Unsubscribe button (the welcome email already carries the link). When you send a newsletter, put each subscriber's link at the bottom: https://yourdomain.com/newsletter/unsubscribe?token= followed by their token. Your list to send to is select email, unsubscribe_token from subscribers where confirmed;, and unsubscribing clears confirmed, so they drop out of it.
  5. 5Without Supabase, create an Audience in Resend and paste its id into RESEND_AUDIENCE_ID. Subscribers are added directly and receive a welcome email.

Try it

  1. 1Restart npm run dev, open /contact, and send yourself a message. It arrives at CONTACT_TO_EMAIL, and the sender gets an auto-reply.
  2. 2Enter an email in the footer newsletter box. With Supabase on, click the confirmation link; otherwise the welcome email arrives straight away.
  3. 3Use Resend's Emails page to see every message and its delivery status.

Use Resend for Supabase's sign-up emails too

  1. 1Supabase's built-in mailer allows only a few emails per hour and is meant for testing.
  2. 2In Supabase open Authentication → SMTP Settings, enable Custom SMTP, and enter host smtp.resend.com, port 465, username resend, and your Resend API key as the password. Sender email is your EMAIL_FROM.

What was added to your project

  • lib/email/resend.ts, lib/email/templates.ts
  • lib/forms/guard.ts (honeypot + rate limit)
  • lib/safe-next.ts (keeps the redirect back to the form on this site)
  • components/forms/form-status.tsx
  • app/contact/actions.ts, app/newsletter/actions.ts
  • app/newsletter/confirm/route.ts, app/newsletter/unsubscribe/page.tsx and supabase/migrations/0002_forms.sql (when Supabase is on)
  • lib/forms/messages.ts (the copy behind every ?sent= and ?error= code)

If something goes wrong

  • “Email is not configured yet”: One of the three required values is missing from .env.local, or the dev server was started before you saved it. Restart it.
  • Resend returns 403 or “domain is not verified”: EMAIL_FROM must be on a verified domain. Check the DNS records in Resend → Domains, or use onboarding@resend.dev while testing.
  • The message says sent but nothing arrives: Check Resend → Emails for the delivery status, then the spam folder of CONTACT_TO_EMAIL.
  • “Too many messages from this connection”: The rate limit is 5 submissions per 10 minutes per IP. Wait, or raise MAX_PER_WINDOW in lib/forms/guard.ts.
  • “Form storage is not configured yet”: FORMS_SECRET is missing from .env.local, or still the placeholder. Run supabase/migrations/0002_forms.sql in the SQL editor and paste the 64-character value it prints; on a deployed site push it with npm run cf:secrets.
  • “The site's public URL is not set”: With Supabase on, the newsletter confirmation link is built from NEXT_PUBLIC_SITE_URL. On a deployed site it is missing or still http://localhost:3000; set it to your domain in the host's environment (with Cloudflare: in .env.local, then npm run cf:secrets).

Missing something? See the full reference. Back to the builder