Files
CompanySite/.agents/backend-specialist/FORM_INTEGRATION.md
T
Clintchiz d402256547
Deploy to Production / Build & Verify (push) Failing after 5m56s
Ping Search Engines / Notify Search Engines (push) Successful in 2s
Deploy to Production / Pre-Deploy Tests (push) Has been skipped
Deploy to Production / Deploy to Railway (push) Has been skipped
Deploy to Production / Deploy to Render (push) Has been skipped
Deploy to Production / Deploy to VPS (PM2) (push) Has been skipped
Deploy to Production / Deploy to Fly.io (push) Has been skipped
Deploy to Production / Post-Deploy Verification (push) Has been skipped
Deploy to Production / Notify on Failure (push) Successful in 2s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
E2E Test Suite / Smoke Tests (P0) (push) Failing after 11m26s
E2E Test Suite / Form Interaction Tests (push) Failing after 11m42s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 12m2s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 16m14s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 17m45s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 25m23s
E2E Test Suite / Security Header Tests (push) Failing after 7m55s
E2E Test Suite / Test Report Summary (push) Failing after 20s
E2E Test Suite / Mobile Device Tests (push) Failing after 2h49m9s
Uptime Monitor / Health & Response Time (push) Failing after 2s
Uptime Monitor / SSL Certificate (push) Successful in 2s
Uptime Monitor / Send Alerts (push) Failing after 3s
Uptime Monitor / Record Uptime Success (push) Has been skipped
First Init
2026-03-21 16:46:46 +05:30

8.8 KiB
Raw Blame History

Form Backend Integration

Agent: backend-specialist Date: 2026-03-21 Status: Complete ✓


Overview

Two API endpoints handle form submissions for the WorkRoot site:

Endpoint File Purpose
POST /api/contact src/pages/api/contact.ts Contact form submissions
POST /api/newsletter src/pages/api/newsletter.ts Newsletter subscriptions

Both endpoints include server-side validation, rate limiting, spam protection, and graceful degradation when optional dependencies (SMTP, newsletter providers) are not configured.


Architecture

Contact Form (/api/contact)

Flow:

  1. Client submits form → POST /api/contact (JSON body)
  2. Rate limit check (5 requests / IP / hour, sliding window)
  3. Honeypot field check (silent success if bot detected)
  4. Server-side field validation
  5. Email delivery via SMTP (nodemailer) — logs to console if SMTP not configured
  6. JSON response returned

Fields validated:

  • name — required, 2100 characters
  • email — required, valid format, max 254 chars
  • phone — optional; if present, must match phone regex
  • subject — required, allowlist enum (web-development, mobile-development, cloud-services, ai-ml, consulting, support, other)
  • message — required, 105000 characters
  • website — honeypot, must be empty

Rate limit: 5 requests per IP per hour


Newsletter (/api/newsletter)

Flow:

  1. Client submits email → POST /api/newsletter (JSON body)
  2. Rate limit check (3 requests / IP / hour)
  3. Email format validation
  4. Subscribe via configured provider (Mailchimp → ConvertKit → SMTP notification → log-only)
  5. JSON response returned

Rate limit: 3 requests per IP per hour


Configuration

Set these environment variables (see .env.example):

Email (SMTP) — for contact form delivery and newsletter fallback

SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your_email@example.com
SMTP_PASS=your_app_password
CONTACT_EMAIL=hello@workroot.in

Gmail tip: Enable 2FA, then create an App Password at https://myaccount.google.com/apppasswords

Newsletter — pick ONE provider

Mailchimp (recommended):

MAILCHIMP_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-us1
MAILCHIMP_LIST_ID=abc123def456
MAILCHIMP_DC=us1

Uses double opt-in (status: "pending"). Handles already-subscribed gracefully.

ConvertKit:

CONVERTKIT_API_KEY=your_api_key
CONVERTKIT_FORM_ID=1234567

If no provider is configured, submissions are logged to server console. Safe for development and staging.


Dependencies

nodemailer (installed)

The SMTP functionality requires nodemailer. It is installed as a production dependency:

npm install nodemailer
npm install --save-dev @types/nodemailer

Both are now in package.json. The dynamic import (await import('nodemailer')) in the API routes handles the case where the module is unavailable with graceful degradation.


Graceful Degradation Matrix

Config state Contact form Newsletter
SMTP not configured Logs submission to console, returns 200 Falls through to next provider
SMTP configured, nodemailer unavailable Warns and logs only, returns 200 Warns and logs only, returns 200
No newsletter provider Logs to console, returns 200
SMTP set, no newsletter provider Sends email Falls back to SMTP notification
All configured Sends HTML email Sends to Mailchimp/ConvertKit

This prevents the site from breaking in any environment, including when secrets aren't yet configured.


Security

Spam Protection

  • Honeypot field: website input hidden from users via CSS (position: absolute; left: -9999px). Bots that fill all fields are detected server-side and receive a silent 200 response.
  • Rate limiting: In-memory sliding window per IP address. Configurable constants at the top of each API file:
    • Contact: RATE_LIMIT_MAX_REQUESTS = 5 per hour
    • Newsletter: RATE_LIMIT_MAX_REQUESTS = 3 per hour

Input Validation

  • All input validated server-side regardless of client-side checks
  • String lengths enforced to prevent DoS via large payloads
  • Subject field uses an allowlist (enum) to prevent injection
  • HTML in email templates uses escapeHtml() to prevent XSS in email clients

CORS

  • Production: Access-Control-Allow-Origin: https://workroot.in
  • Development: Access-Control-Allow-Origin: *
  • Preflight OPTIONS handlers implemented on both endpoints

Rate Limit Response Headers

Both endpoints return standard rate limit headers on every response:

X-RateLimit-Limit: 5
X-RateLimit-Remaining: 4
X-RateLimit-Reset: 1742000000

Response Format

All responses follow a consistent structure:

Success (200):

{ "success": true, "message": "Human-readable success message" }

Validation error (422):

{ "success": false, "error": "Specific validation error" }

Rate limited (429):

{ "success": false, "error": "Too many requests. Please try again later." }

Server error (500):

{ "success": false, "error": "Failed to send message. Please try again or email us directly." }

Client Integration

Contact form (src/pages/contact.astro)

Posts JSON to /api/contact. Handles all response states:

  • Success → toast notification + form reset
  • Validation error → toast with specific message
  • Rate limit (429) → error toast
  • Network error → error toast

Newsletter form (src/components/Footer.astro)

Posts JSON to /api/newsletter. Handles all response states:

  • Success → inline green message + form reset
  • Error → inline red message
  • Network error → inline red message
  • Message auto-hides after 5 seconds

Logging & Monitoring

Both endpoints use src/utils/logger.ts for structured logging:

  • Development: colored console output
  • Production: JSON output for log aggregators (Logtail, Datadog, etc.)

Errors are captured via src/utils/sentry.ts (optional — only active when SENTRY_DSN env var is set).

Each API request is logged with:

  • Method, path, status code
  • IP address
  • Duration in milliseconds

Testing

Test the endpoints manually with curl:

# Contact form - success case
curl -X POST http://localhost:4321/api/contact \
  -H "Content-Type: application/json" \
  -d '{"name":"Test User","email":"test@example.com","subject":"consulting","message":"Hello, I need help with a project."}'

# Newsletter - success case
curl -X POST http://localhost:4321/api/newsletter \
  -H "Content-Type: application/json" \
  -d '{"email":"subscriber@example.com"}'

# Test honeypot (should return 200 silently)
curl -X POST http://localhost:4321/api/contact \
  -H "Content-Type: application/json" \
  -d '{"name":"Bot","email":"bot@spam.com","subject":"other","message":"Buy cheap stuff now!","website":"http://spam.com"}'

# Test rate limiting (run 4+ times to trigger 429 on newsletter)
for i in 1 2 3 4; do
  curl -s -o /dev/null -w "%{http_code}\n" \
    -X POST http://localhost:4321/api/newsletter \
    -H "Content-Type: application/json" \
    -d '{"email":"test@example.com"}'
done

Playwright tests are located at:

  • tests/contact-form.spec.ts — contact form UI and API
  • tests/newsletter-subscription.spec.ts — newsletter UI and API
  • tests/api-integration.spec.ts — API endpoint integration tests
  • tests/e2e-form-interactions.spec.ts — form interaction flows

Run all form tests:

npm run test:forms

In-Memory Rate Limiter Notes

The current rate limiter is in-memory (per process). This means:

  • Works correctly for single-instance deployments (VPS, single container)
  • ⚠️ Resets on server restart
  • ⚠️ Not shared across multiple instances (e.g., PM2 cluster mode)

For multi-instance deployments, upgrade to Redis-backed rate limiting (see Future Improvements).


Alternative: FormSpree (No-code option)

If you prefer a managed third-party service without SMTP configuration:

  1. Sign up at https://formspree.io
  2. Create a form and get your form ID (e.g. xpwzgkab)
  3. Replace the fetch URL in contact.astro:
    const response = await fetch('https://formspree.io/f/xpwzgkab', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
      body: JSON.stringify(formData),
    });
    

The existing custom API endpoint can remain for future migration back to self-hosted.


Future Improvements

  • Persist rate limit state in Redis for multi-instance PM2 cluster deployments
  • Add CAPTCHA (hCaptcha or Cloudflare Turnstile) as additional spam layer
  • Store contact submissions in a database for CRM integration
  • Add confirmation/welcome email for newsletter signups (double opt-in flow)
  • Webhook notifications to Slack/Discord on new contact submissions
  • Admin dashboard to view contact form submissions