Scofti
← Board
SC-18DonePhase One · Launch

Connect the contact form

The form is built and live; it needs a mailbox and a key before it can deliver.

The form at /contact is the first thing on this site that receives anything. Everything else here only hands pages out.

It is built and deployed. It does not deliver yet, because the mailbox and the API key do not exist. Until they do it returns a plain "this form is not connected yet" — deliberately, because a form that thanks someone for a message nobody receives is worse than one that admits it is not ready.

Still to do, and it is not code

  1. Create prefect@scofti.com as a forwarding address at Porkbun.
  2. Open a free account at Resend.
  3. Add scofti.com as a sending domain and put Resend's DKIM and SPF records into Porkbun DNS. Two things to watch: leave the existing MX records (fwd1/fwd2.porkbun.com) alone — that is the forwarding, and Resend does not touch it — and if Porkbun already publishes an SPF record, merge Resend's into it rather than adding a second. Two SPF records break both.
  4. Create an API key.
  5. Put RESEND_API_KEY and CONTACT_TO in .env.local, and add both in Vercel → Settings → Environment Variables.

The address is never rendered on any page — it lives only in CONTACT_TO — so it is not there to be scraped.

How it is built

The message passes through and is not stored: read off the request, handed to Resend, dropped. Nothing logs a name, an address or a message body, because an error log full of people's messages is a store by another name.

Spam defence is a honeypot field and one message per address per minute, held in memory. No reCAPTCHA — it is Google tracking, and it would sit one click from a page promising the opposite. The rate limit resets on redeploy and is not shared between instances, which makes it a speed bump rather than a guarantee. That trade is deliberate: the alternative is keeping a store of visitor IP addresses, which is a worse thing to own than the spam it would stop.

The privacy page already describes this flow, so the two have to stay true to each other. Changing what this route does means changing that page in the same commit.

Verify the mail actually authenticates

Do this last, after the key is in and a message has been sent. A form that delivers to the spam folder has not shipped.

Two records already exist and both change what the setup steps mean:

scofti.com        TXT  "v=spf1 include:_spf.porkbun.com ~all"
_dmarc.scofti.com TXT  "v=DMARC1; p=none;"

There is already an SPF record. This is exactly the case that breaks: Resend's include has to be merged into the line above, so it stays one record —

v=spf1 include:_spf.porkbun.com include:amazonses.com ~all

— and not published as a second v=spf1 line. Two SPF records is a permanent fail, not a warning, and it would take the existing Porkbun forwarding down with it. Confirm the exact include Resend asks for rather than assuming this one.

The DMARC record is a placeholder doing nothing. p=none with no rua= means no enforcement and no reports, so a failure is invisible. Worth fixing while in there: add a reporting address, and only tighten the policy once the reports come back clean.

v=DMARC1; p=none; rua=mailto:prefect@scofti.com; fo=1;

Leave p=none until reports show SPF and DKIM passing for real mail. Moving straight to p=quarantine or p=reject before that can bounce legitimate mail, including the forwarding.

The check itself

  1. Send a real message through /contact to an address you can read raw — a Gmail account is easiest.
  2. Open Show original. It has to say SPF: PASS, DKIM: PASS and DMARC: PASS. Two out of three is not enough; DKIM is the one that survives forwarding.
  3. Confirm Reply addresses the person who wrote, not the site. The route sets reply_to to their address — if that is wrong, every reply goes nowhere and nobody finds out.
  4. Check it landed in the inbox rather than spam.

Only when all four hold is the form actually connected.

Connected

The form delivers on scofti.com. Michael sent one through the live page and it arrived; Resend reports delivered on every send so far, and the reply address is the person who wrote, not the site.

What actually held it up was not the code. Local worked from the moment the key was in place, because .env.local is gitignored and never leaves the machine — so Vercel had genuinely never seen those values, and a passing local test said nothing at all about production. Both variables had to be added in Vercel and the project redeployed, since environment variables are injected at deploy time and do nothing to a build that already exists.

The verification section above — SPF, DKIM, DMARC, and whether mail lands in the inbox rather than spam — moves to SC-19. It is deliberately not done here. Resend saying delivered means the receiving server accepted the message, which is not the same as a person finding it.