Skip to content
Sign inJoin waitlist

Forms — Tustel Next.js

Tustel has one form, the project enquiry on the Contact page, and it works as soon as you run the site.

Visitors leave their name, e-mail, organization, project type, budget, timeline and a message. The form checks the required fields, shows a spinner while sending, says “Thank you” and clears the fields, keeps what the visitor typed when something fails (“Something went wrong”), and drops spam with a hidden honeypot field.

Out of the box, submissions are written to the server log (FORM_PROVIDER=console). That’s fine while you build, but useless once you publish, so pick a destination before launch.

  1. Copy the example settings:

    Terminal window
    cp .env.example .env.local
  2. Choose one:

    You want Set
    An email for every enquiry FORM_PROVIDER=resend + RESEND_API_KEY, FORM_EMAIL_TO, FORM_EMAIL_FROM
    It in your CRM, Slack, a spreadsheet… FORM_PROVIDER=webhook + FORM_WEBHOOK_URL (Zapier, Make, n8n, Formspree, your own API)
    The sender in a Mailchimp audience FORM_PROVIDER=mailchimp + MAILCHIMP_API_KEY, MAILCHIMP_AUDIENCE_ID

    Several at once work: FORM_PROVIDER=resend,mailchimp.

  3. Restart npm run dev and send a test enquiry.

  4. Put the same variables in your host’s dashboard before deploying. .env.local is not uploaded.

Details for each provider, the webhook payload, Mailchimp consent settings and how to write your own provider are in Forms reference.

Field Name it arrives under Required
Name name yes
Email email yes
Organization organization
Project Type (Photography, Brand Identity, Web Design, Creative Direction, Art Direction, Other) project yes
Estimated Budget (Under $2,000, $2,000 – $5,000, $5,000 – $10,000, $10,000+) budget yes
Timeline timeline
Message message

Each submission carries the title “New project enquiry”, so you can tell it apart in your inbox or tool. Resend puts the sender’s name after it in the email’s subject and sets the reply address to the sender’s e-mail. Mailchimp stores the name as FNAME, tags the contact with the title and keeps the rest as a note. A webhook receives every field, as "" when left empty, with its label.

The labels, placeholders, the choices of the two selects, the note next to the button (“All fields marked with * are required.”), the button texts (“Send message”, “Thank you”, “Something went wrong”) and the title of each submission are content: contactForm in src/content/pages/contact.ts. The note’s asterisk is drawn in orange; keep one in the text.

A field arrives at your provider under its name, so keep the names you already map somewhere.

The form shows the fields of contactForm.fields in the order they are listed: two columns from tablets up (one on phones), with the message across both. submitContact in src/lib/actions.ts sends every field in that list and checks the required ones again on the server, so the list is the only place to edit:

  • To make a field required, set required: true on it. Its label gets an orange asterisk, the browser checks it before sending and the server checks it again.

  • To add a field, add an entry, for example a phone number:

    phone: { name: "phone", label: "Phone", placeholder: "+1 555 0100", type: "tel", autoComplete: "tel" },

    It appears in the form and arrives with the others. The types are text (the default), email, tel, select (with options; the placeholder is its empty first choice) and textarea, which takes the full width.

  • To remove a field, delete its entry.

  • Submit the form on your own site after deploying, with a real address.
  • Check the destination (inbox, spreadsheet, audience) and reply to the test.
  • Try a failure on purpose: set a wrong API key, submit, and confirm the visitor sees “Something went wrong” and their text is still there.

The honeypot stops the usual bots: a bot that fills the hidden field is shown “Thank you” and nothing is sent. If you start getting spam anyway, put a service in front through the webhook provider (Formspree, Web3Forms and Basin all include spam filtering), or add a captcha to the form component.

Submissions go only where you point them; nothing is sent anywhere else. Most countries expect you to say what happens to the data people send you. Tustel has no privacy policy page and no consent checkbox: if you need a policy, add a page for it (Pages and sections) and mention it in the Contact page’s text (contactPage.text).

The form doesn’t ask for marketing consent, so if you send enquiries to Mailchimp, keep MAILCHIMP_STATUS at transactional (the default): those contacts can get your replies, not your campaigns.