NAVANEM
v0.2.0TypeScript · MIT license · jun 21, 2026 · 19:08 utc

Payload Contact: a self-hosted contact form and inbox for Payload 3

A free, open-source contact-form plugin for Payload 3 — a triage inbox, spam protection, runtime settings and optional email notifications. Self-hosted, with no third-party form service.

by Emanuel De Almeida

Payload Contact — open-source contact form, inbox and spam protection plugin for Payload 3View source on GitHub

TL;DR

  • @navanem/payload-contact is a free, open-source contact-form plugin for Payload 3 — a drop-in form, a validated submit endpoint, a triage inbox and runtime settings, with no third-party form service.
  • Visitors submit through a themeable <ContactForm>; every message lands in a contact-messages inbox you read and mark new / read / replied in the Payload admin.
  • Spam is handled with a hidden honeypot field and a per-IP sliding-window rate limit — no CAPTCHA, no tracking pixels, IPs stored only as salted hashes.
  • A contact-settings global opens or closes the form, sets length limits, requires a subject and blocks keywords at runtime — no redeploy.
  • v0.2.0 adds optional email notifications: enter your SMTP details in the admin and get emailed on every new message. Off by default; the credentials never reach the browser.

What is @navanem/payload-contact?

@navanem/payload-contact is a native Payload 3 plugin that gives any Payload application a complete contact channel without reaching for a third-party form service. It registers a contact-messages collection (the inbox), a contact-settings global (runtime configuration), a public submit endpoint with built-in spam protection, an admin inbox dashboard, and a ready-to-use <ContactForm> React component. The messages live in your own database, under your own admin — there is no per-seat pricing and nothing is sent to an external service.

It follows the same vendored model as its sibling plugin, Payload Comments: copy the source into your project so the admin component paths resolve through your import map, or install it from npm.

What is new in v0.2.0?

v0.2.0 adds optional email notifications. Earlier versions stored every submission in the inbox only; now you can open Contact Settings, expand the Email notifications section, and enter your SMTP host, port, username, password, TLS option and a from address. With the toggle on, a fire-and-forget afterChange hook emails you (via nodemailer) on each new message — it never blocks or fails the submission. Email is off by default, and the SMTP credentials live on an admin-read-only global, so they never reach the browser. Messages always land in the inbox whether or not email is configured.

What does the contact form look like for visitors?

The bundled <ContactForm> is a styled, accessible React client component: name, email, an optional subject and a message, with inline validation and a clear success state. It is themeable through --pc-* CSS custom properties, so it inherits your site’s look without you overriding any markup. It posts JSON to the plugin’s endpoint and shows your configured success message on completion. No account, no login, no third-party script — just a form that matches your site.

How do you triage messages in the inbox?

Every submission becomes a row in the contact-messages collection, grouped under a "Contact" section in the admin. Each message carries a status — new, read or replied — so the form doubles as a lightweight ticket queue rather than a black hole. A dedicated Contact Inbox view at /admin/contact-inbox adds at-a-glance KPIs (total, new, read and replied counts), period filters and a recent-messages table. The view is auth-guarded server-side, so message data never leaks into unauthenticated output.

How do the runtime settings work?

A Contact Settings global (in the "Contact" admin group) controls the form without a redeploy. You can turn submissions on or off, set the minimum and maximum message length, require a subject, edit the list of blocked spam keywords and change the success message. The submit endpoint and the page read these settings live; if the global has never been saved, everything falls open to the plugin’s options, so the form works the moment you install it.

How do you turn on email notifications?

Open Contact Settings, expand Email notifications, tick "Send an email on each new message", then fill in the recipient address plus your SMTP host, port, username, password, TLS option and from address. From then on, an afterChange hook sends a notification through nodemailer on each new submission. Because it is fire-and-forget, a slow or misconfigured mail server can never block or break the form — the message is already safely stored in the inbox. Leave the toggle off to keep everything inbox-only.

How do you install and use it?

Vendor the source (recommended, the same way as Payload Comments) or install it from npm, register the plugin in your Payload config, create the database tables with a migration, then drop the form on a page.

shell
# vendor the source so the admin component paths resolve
cp -r node_modules/@navanem/payload-contact/src src/plugins/payload-contact
# (or: npm install @navanem/payload-contact)
bash
import { contactPlugin } from '@navanem/payload-contact'

export default buildConfig({
  plugins: [
    contactPlugin({ notificationEmail: 'you@example.com' }),
  ],
})

Then render the form wherever you want it on the front end:

bash
import { ContactForm } from '@navanem/payload-contact/client'

export default function ContactPage() {
  return <ContactForm successMessage="Thanks — I’ll get back to you by email." />
}

The form posts JSON to POST /api/contact-api/submit with { name, email, subject?, message }. That path lives under /contact-api/* on purpose — not the collection slug — so it never collides with the contact-messages REST namespace.

How does it stay safe and spam-resistant?

Two layers, with no CAPTCHA and no tracking. First, a hidden honeypot field (named company) that real visitors never see — any submission that fills it is silently dropped, giving bots no useful signal. Second, a per-IP sliding-window rate limit, where IP addresses are hashed with a salt rather than stored raw. Every submission is validated server-side against the live settings (length, required subject, blocked keywords) before anything is written, and the public create route on the collection is closed — messages can only arrive through the validated endpoint.

FAQ

Does it require visitors to create an account?

No. The form is completely anonymous — a name, an email and a message. There is no sign-up, no login and no third-party script for the visitor.

Where are the messages stored?

In your own Payload database, in the contact-messages collection. Nothing is sent to an external form service, so you keep full ownership of the data.

Does it send email?

Only if you want it to. Email is off by default and every message lands in the inbox regardless. From v0.2.0 you can configure SMTP in Contact Settings to also be emailed on each new message.

How does it stop spam without a CAPTCHA?

With a hidden honeypot field and a per-IP sliding-window rate limit, plus server-side validation and a blocked-keyword list. There is no CAPTCHA and no tracking pixel, and visitor IPs are stored only as salted hashes.

Which Payload version does it support?

Payload 3.x. It uses only Payload’s own collections, globals, endpoints and admin components, and is built and tested against the same stack that runs navanem.com.

Can I use my own form UI instead of the bundled component?

Yes. The bundled <ContactForm> is optional — you can POST to /api/contact-api/submit from any UI you like, and the honeypot, rate limit and validation still apply.

#Payload CMS#TypeScript#open-source#Contact form#React