Add public contact endpoint delivering via Resend - #18
Merged
Conversation
The site's contact form had no backend; this adds POST /contact. - ContactCreate schema with length/email validation and a honeypot field (website) — honeypot submissions return 202 but are dropped - app/email.py sends via the Resend HTTP API: From on the verified sending domain, Reply-To set to the submitter, 10s timeout; transport errors and non-2xx responses raise EmailDeliveryError - Router returns 503 while RESEND_API_KEY/CONTACT_TO_EMAIL are unset, 502 when the provider rejects a send - Rate limited at 3/minute by generalizing the auth rate-limit middleware to a path-keyed table (_PATH_RATE_LIMITS) - Settings: resend_api_key, contact_to_email, contact_from_email - Deploy maps RESEND_API_KEY and CONTACT_TO_EMAIL secrets into Cloud Run (runtime SA granted secretAccessor on both out-of-band) - httpx promoted to a runtime dependency; six new tests cover success, honeypot, unconfigured, provider failure, validation, and rate limit
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The site's contact form had no backend — the frontend faked success. This adds the real endpoint.
POST /contact(public, no auth): validates aContactCreatepayload (name/email/subject/message with length limits) and emails it to the site inbox via the Resend HTTP API. From stays on the verified sending domain; Reply-To is the submitter, so replies go straight back.websitefield humans never see; when non-empty the endpoint returns 202 but drops the message, giving bots no signal.RESEND_API_KEY/CONTACT_TO_EMAILare unconfigured, 502 when the provider rejects a send (transport errors and non-2xx wrapped inEmailDeliveryError, logged with truncated response bodies)./contactcapped at 3/minute per client IP.resend_api_key,contact_to_email,contact_from_email; the deploy workflow maps the two secrets into Cloud Run. The runtime service account was grantedsecretmanager.secretAccessoron both (done out-of-band).httpxpromoted from dev to runtime dependency.Testing
delivered@resend.dev): real send accepted with 202, both from curl and through the site's form in a browser.