Skip to content

feat: implement email contact form with Postmark API (#14)#76

Merged
taearls merged 7 commits intomainfrom
feature/contact-form-postmark-integration
Dec 13, 2025
Merged

feat: implement email contact form with Postmark API (#14)#76
taearls merged 7 commits intomainfrom
feature/contact-form-postmark-integration

Conversation

@taearls
Copy link
Copy Markdown
Owner

@taearls taearls commented Dec 7, 2025

Summary

Implements a complete, production-ready email contact form with enterprise-grade spam protection.

Backend (Cloudflare Worker - packages/contact-form/)

  • 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 - src/components/ContactEmailForm.tsx)

  • 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 attributes, screen reader announcements, role="alert")

Testing

  • 22 unit tests for ContactEmailForm (Vitest)
  • Integration tests with Cypress
  • All 182 tests passing

Files Created

  • packages/contact-form/ - Complete Cloudflare Worker package
  • tests/unit/components/ContactEmailForm.test.tsx - Unit tests
  • tests/integration/contact-form.cy.ts - Integration tests

Files Modified

  • src/components/ContactEmailForm.tsx - Complete rewrite with full functionality
  • src/util/constants.ts - Added API_URIS.CONTACT, TURNSTILE_SITE_KEY
  • src/vite-env.d.ts - Added new environment variable types
  • .env.development, .env.production - Contact form URLs
  • package.json - Added workspace, scripts, dependencies
  • ROADMAP.md - Marked Add Working Email Contact Form #14 complete with detailed changelog

Deployment Requirements

  1. Create Cloudflare Turnstile site (get Site Key and Secret Key)
  2. Create KV namespace for rate limiting
  3. Set Worker secrets: POSTMARK_SERVER_TOKEN, TURNSTILE_SECRET_KEY
  4. Deploy Worker: npm run deploy:contact
  5. Enable feature flag: email-contact-form.enabled = true

Test plan

  • All 182 unit tests pass
  • TypeScript build succeeds
  • Vite production build succeeds
  • ContactEmailForm renders correctly
  • Email validation works on blur
  • Form submission handles success/error states
  • Turnstile widget integrates correctly
  • Accessibility attributes present (ARIA)

Closes #14

🤖 Generated with Claude Code

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Dec 7, 2025

Deploying portfolio with  Cloudflare Pages  Cloudflare Pages

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

View logs

Copy link
Copy Markdown
Owner Author

@taearls taearls left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.tsx
    • tests/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

  1. 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:fix and amend the commit

🔵 Minor Issues / Suggestions

  1. Email regex differs between frontend and backend

    • Files: src/util/constants.ts:64-67 vs packages/contact-form/src/index.ts:50-51
    • Suggestion: Consider sharing the regex via @portfolio/shared-types for consistency
  2. 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

✅ Positive Observations

  1. Excellent security implementation - Triple-layer spam protection, HTML escaping, CORS, input validation
  2. Strong accessibility support - ARIA attributes, screen reader announcements, keyboard accessible
  3. Comprehensive test coverage - 22 unit tests covering all scenarios
  4. Well-structured Worker code - Clean separation, proper error handling, TypeScript interfaces
  5. Thorough documentation - ROADMAP updated, deployment requirements documented

Approval Status

⚠️ Fix Required: Prettier formatting must pass before merge.

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>
@taearls taearls force-pushed the feature/contact-form-postmark-integration branch from d915545 to 18f7ca2 Compare December 7, 2025 18:22
taearls and others added 3 commits December 13, 2025 11:33
- 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>
@taearls taearls enabled auto-merge (squash) December 13, 2025 21:05
taearls and others added 3 commits December 13, 2025 15:08
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>
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Dec 13, 2025

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
portfolio-feature-flags 5d3f333 Dec 13 2025, 09:41 PM

@taearls taearls merged commit f6621ee into main Dec 13, 2025
4 of 5 checks passed
@taearls taearls deleted the feature/contact-form-postmark-integration branch December 13, 2025 21: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.

Add Working Email Contact Form

1 participant