Skip to content

fix(#784): /contact/ offers a channel that works without a third-party key - #786

Merged
TortoiseWolfe merged 3 commits into
mainfrom
fix/784-contact-has-a-working-channel
Aug 17, 2026
Merged

fix(#784): /contact/ offers a channel that works without a third-party key#786
TortoiseWolfe merged 3 commits into
mainfrom
fix/784-contact-has-a-working-channel

Conversation

@TortoiseWolfe

Copy link
Copy Markdown
Owner

The defect

Production shipped an empty Web3Forms access key:

$ curl …/_next/static/chunks/app/contact/page-f4f80d62d04f8012.js
accessKey(){return""
accessKey)throw Error("Web3Forms access key is no…

So every submission threw. There was no EmailJS fallback (no service_… id in any of
the 15 chunks on that page) and no mailto: anywhere on /contact/ — the form was
the entire channel, and the form could not send.

Meanwhile Stripe's live business_profile reads:

support_url   : https://scripthammer.com/contact/
support_email : admin@scripthammer.com

Receipts send paying customers to that page. They arrived at a form that errored, on a
page that never showed them the address that actually works.

The fix: offer something that does not depend on a credential

An address works whether or not a third-party key is present. Two placements:

  • "Other Ways to Connect", listed first, above the GitHub links.
  • Inside the form's error alert. That branch renders at the precise moment a
    visitor's message did not get through. "Something went wrong" and nothing else is how
    an enquiry is lost silently.

admin@scripthammer.com was proven to deliver end-to-end today — a message sent through
Resend → Cloudflare Email Routing arrived in the destination inbox — rather than being
assumed from correct-looking DNS.

The default is the load-bearing part

supportEmail reads NEXT_PUBLIC_SUPPORT_EMAIL and defaults to empty; the mailto is
not rendered when unset.

Hardcoding an address would put this maintainer's inbox on every fork's contact page —
the #392 failure, one person's identity shipped to everyone, reappearing on the exact
page customers are directed to.

Three tests assert it, and they were mutation-tested: giving supportEmail a
hardcoded fallback turns all three red. One states the rule as a property
not.toMatch(/@/) — so any future default fails, whichever address someone picks.

Worth recording: the first mutation attempt was a sed whose | delimiter collided
with ||. It silently did not apply and the suite went green — a proof that proved
nothing. Grepping the file afterwards is what caught it. Confirm the mutant is really
in the file before trusting the red.

Notes

Still outstanding on #784

The Web3Forms access key itself — that is the owner's account. This change means the
page has a working channel regardless. When the key is set, deploy.yml also needs its
secrets.NEXT_PUBLIC_WEB3FORMS_ACCESS_KEY reference switched to vars., the same swap
#785 makes for Stripe.

Refs #784

TurtleWolfe and others added 2 commits August 17, 2026 13:32
…y key

Production shipped an EMPTY Web3Forms access key, so `submitToWeb3Forms()` threw
on every submission (`accessKey(){return""` in the deployed chunk). The page had
no `mailto:` and no EmailJS fallback, so `/contact/` offered no working way to
reach anyone at all — while Stripe's `support_url` was pointing paying customers
straight at it.

Two places now render an address, because the address is the part that does not
depend on a credential being present:

- "Other Ways to Connect", listed FIRST, above the GitHub links.
- Inside the form's error alert. That branch renders at the exact moment a
  visitor's message did not get through, and saying "something went wrong" and
  nothing else is how an enquiry is lost silently.

DEFAULTS TO EMPTY, AND THAT IS THE LOAD-BEARING PART. `supportEmail` reads
`NEXT_PUBLIC_SUPPORT_EMAIL` with an empty default, and the mailto is not rendered
when unset. Hardcoding an address would put this maintainer's inbox on every
fork's contact page — the #392 failure, one person's identity shipped to
everyone, reappearing on the page customers are sent to.

Three tests assert that default and were mutation-tested: giving `supportEmail` a
hardcoded fallback turns all three red. One of them states the rule as a property
(`not.toMatch(/@/)`) rather than a value, so ANY future default fails, whichever
address someone picks.

Worth recording: the first attempt at that mutation was a `sed` whose `|`
delimiter collided with `||`. It silently did not apply, and the suite went green
— a "proof" that proved nothing. The grep of the file afterwards is what caught
it. Verify the mutant is really in the file before trusting the red.

`!text-current` on the in-alert link because the alert owns the colour on its own
surface; a link colour of its own measured 1.66:1 there (#459).

The access key itself is still unset — that is the owner's Web3Forms account and
is tracked in #784. This change means the page works anyway.

Refs #784

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…key nobody set

The contact form's only provider was Web3Forms, keyed by
NEXT_PUBLIC_WEB3FORMS_ACCESS_KEY. Production shipped that key EMPTY, so every
submission threw and /contact/ delivered nothing — while Stripe's support_url
pointed paying customers at that page.

Resend is already this project's mail path: domain verified, DKIM and SPF live,
RESEND_API_KEY already an Edge Function secret. Routing contact mail through it
removes a third-party dependency and one more credential nobody was watching.

- supabase/functions/contact-message — sends via Resend. DEPLOYED and exercised
  against the live function before this commit.
- SupabaseResendProvider, registered FIRST. Web3Forms and EmailJS stay behind it
  as genuine failover, so a fork without Supabase still sends. Ordering is the
  only behavioural change to the service.

THE RECIPIENT IS FIXED SERVER-SIDE AND IGNORES THE REQUEST. This is the security
property, not a detail. An endpoint that honours a caller-supplied `to` is an
open relay, and #353 records this project's sign-up form being abused to mail
non-consenting third parties. Verified against the deployed function rather than
asserted: a POST carrying `"to":"victim@example.org"` was delivered to
admin@scripthammer.com, confirmed by reading the received message's headers.

Also verified live: a valid submission delivers; a filled honeypot returns 200
and sends nothing (checked by absence in the mailbox, not by the status code);
invalid input returns 400.

CONTACT_TO and CONTACT_FROM are REQUIRED with no defaults. A fallback to this
maintainer's domain would put upstream's inbox behind every fork's contact form
(#392) and try to send from a domain the fork does not own in Resend. Missing
config returns 500 rather than pretending to have sent.

Six provider tests, weighted toward the negative cases: a non-OK status, a 200
that does not confirm success, and a network failure must all THROW so the
service fails over. A contact provider that resolves while delivering nothing is
worse than one that throws, because it eats enquiries silently.

Still open on #784: no rate limit. The fixed recipient bounds the blast radius to
our own mailbox rather than a stranger's — the difference between nuisance and
the #353 defect — but a per-IP limit still belongs there.

Refs #784

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-working-channel

# Conflicts:
#	.github/workflows/deploy.yml
@TortoiseWolfe
TortoiseWolfe merged commit b8fe294 into main Aug 17, 2026
61 of 65 checks passed
@TortoiseWolfe
TortoiseWolfe deleted the fix/784-contact-has-a-working-channel branch August 17, 2026 16:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants