feat: implement email contact form with Postmark API (#14)#76
Merged
Conversation
Deploying portfolio with
|
| Latest commit: |
5d3f333
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://94131203.portfolio-next.pages.dev |
| Branch Preview URL: | https://feature-contact-form-postmar.portfolio-next.pages.dev |
taearls
commented
Dec 7, 2025
Owner
Author
taearls
left a comment
There was a problem hiding this comment.
Summary
This PR implements a complete email contact form with Postmark API integration and enterprise-grade spam protection (Issue #14).
Changed files: 14 files, +1708 additions, -45 deletions
Impact areas: Contact form UI, Cloudflare Worker backend, email delivery, spam protection
Review depth: Full validation
Quality Checks Results
- ✅ ESLint:
npm run lint:check- Pass - ✅ OxLint:
npm run oxlint:check- Pass (0 warnings, 0 errors) - ❌ Prettier:
npm run format:check- Failed (2 files need formatting)src/components/ContactEmailForm.tsxtests/unit/components/ContactEmailForm.test.tsx
- ✅ Unit tests:
npm run test- Pass (182/182 tests) - ✅ Build:
npm run build- Pass
Code Review Findings
🔴 Critical Issues
None found.
🟡 Major Issues
- Prettier formatting not applied
- Files:
src/components/ContactEmailForm.tsx,tests/unit/components/ContactEmailForm.test.tsx - Issue: These files have formatting inconsistencies that fail the Prettier check
- Fix: Run
npm run format:fixand amend the commit
- Files:
🔵 Minor Issues / Suggestions
-
Email regex differs between frontend and backend
- Files:
src/util/constants.ts:64-67vspackages/contact-form/src/index.ts:50-51 - Suggestion: Consider sharing the regex via
@portfolio/shared-typesfor consistency
- Files:
-
Integration tests document behavior rather than test it
- File:
tests/integration/contact-form.cy.ts:137-177 - Suggestion: Consider adding Turnstile test keys for CI
- File:
✅ Positive Observations
- Excellent security implementation - Triple-layer spam protection, HTML escaping, CORS, input validation
- Strong accessibility support - ARIA attributes, screen reader announcements, keyboard accessible
- Comprehensive test coverage - 22 unit tests covering all scenarios
- Well-structured Worker code - Clean separation, proper error handling, TypeScript interfaces
- Thorough documentation - ROADMAP updated, deployment requirements documented
Approval Status
Overall: Excellent implementation with strong security, accessibility, and testing. Just needs formatting fix.
Reviewed by: Claude Code Agent
Add working email contact form with enterprise-grade spam protection: Backend (Cloudflare Worker): - POST /api/contact endpoint for form submission - Cloudflare Turnstile verification (privacy-friendly CAPTCHA) - Honeypot field for additional bot detection - Rate limiting: 5 requests/hour/IP using Cloudflare KV - Postmark API integration for email delivery - Full input validation with proper error messages - CORS configuration for allowed origins Frontend (React): - Complete form with name, email, message fields - Turnstile widget integration (@marsidev/react-turnstile) - Client-side email validation with inline errors - Loading, success, and error states - Character counter for message field (5000 char limit) - Full accessibility support (ARIA, screen reader announcements) Testing: - 22 unit tests for ContactEmailForm - Integration tests with Cypress - All 182 tests passing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
d915545 to
18f7ca2
Compare
- Move EMAIL_REGEX to @portfolio/shared-types for consistency - Fix dot escaping in regex pattern (. -> \.) - Add shared-types dependency to contact-form Worker - Remove duplicate regex definitions from constants files - Add Cypress helper commands for Turnstile testing - Complete form submission tests (success, error, rate limit) - Add Turnstile widget tests (render, disable, reset) - Add error handling tests (network, validation, retry) - Complete dark mode tests with theme toggle 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The Cypress integration tests were timing out because VITE_TURNSTILE_SITE_KEY was not set in CI. Without it, the Turnstile widget doesn't render and tests wait indefinitely for an iframe that never appears. Added: - VITE_TURNSTILE_SITE_KEY with Cloudflare's "always pass" test key - VITE_CONTACT_FORM_API_URL for consistency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Turnstile mock that injects via onBeforeLoad to bypass CDN - Intercept Turnstile script request to prevent real script loading - Add stubTurnstile() call to all test suites using contact form - Fix rate limit test to match actual error message format - Fix dark mode tests to use direct DOM manipulation - Update Turnstile button test to fill form first (button requires valid fields) - Mock reset() method to trigger new token after form errors 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
portfolio-feature-flags | 5d3f333 | Dec 13 2025, 09:41 PM |
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
Implements a complete, production-ready email contact form with enterprise-grade spam protection.
Backend (Cloudflare Worker -
packages/contact-form/)/api/contactendpoint for form submissionFrontend (React -
src/components/ContactEmailForm.tsx)@marsidev/react-turnstile)Testing
Files Created
packages/contact-form/- Complete Cloudflare Worker packagetests/unit/components/ContactEmailForm.test.tsx- Unit teststests/integration/contact-form.cy.ts- Integration testsFiles Modified
src/components/ContactEmailForm.tsx- Complete rewrite with full functionalitysrc/util/constants.ts- AddedAPI_URIS.CONTACT,TURNSTILE_SITE_KEYsrc/vite-env.d.ts- Added new environment variable types.env.development,.env.production- Contact form URLspackage.json- Added workspace, scripts, dependenciesROADMAP.md- Marked Add Working Email Contact Form #14 complete with detailed changelogDeployment Requirements
POSTMARK_SERVER_TOKEN,TURNSTILE_SECRET_KEYnpm run deploy:contactemail-contact-form.enabled = trueTest plan
Closes #14
🤖 Generated with Claude Code