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
- Create
prefect@scofti.comas a forwarding address at Porkbun. - Open a free account at Resend.
- 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
MXrecords (fwd1/fwd2.porkbun.com) alone — that is the forwarding, and Resend does not touch it — and if Porkbun already publishes anSPFrecord, merge Resend's into it rather than adding a second. Two SPF records break both. - Create an API key.
- Put
RESEND_API_KEYandCONTACT_TOin.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
- Send a real message through
/contactto an address you can read raw — a Gmail account is easiest. - Open Show original. It has to say
SPF: PASS,DKIM: PASSandDMARC: PASS. Two out of three is not enough; DKIM is the one that survives forwarding. - Confirm Reply addresses the person who wrote, not the site. The route
sets
reply_toto their address — if that is wrong, every reply goes nowhere and nobody finds out. - 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.