{"id":49280185,"url":"https://github.com/hatefrad/serverless-contact-form","last_synced_at":"2026-04-25T18:06:33.985Z","repository":{"id":89772661,"uuid":"199134607","full_name":"hatefrad/serverless-contact-form","owner":"hatefrad","description":"A modern, secure, and scalable contact form API built with TypeScript, AWS Lambda, and AWS SES, offering comprehensive validation, security features, and error handling.","archived":false,"fork":false,"pushed_at":"2026-03-29T21:11:00.000Z","size":1217,"stargazers_count":1,"open_issues_count":6,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-29T21:32:27.409Z","etag":null,"topics":["contact-form","lambda","lambda-functions","serverless","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hatefrad.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2019-07-27T07:54:58.000Z","updated_at":"2026-03-29T21:11:04.000Z","dependencies_parsed_at":null,"dependency_job_id":"610d8ad4-3150-415a-a181-aa46f79aea14","html_url":"https://github.com/hatefrad/serverless-contact-form","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hatefrad/serverless-contact-form","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hatefrad%2Fserverless-contact-form","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hatefrad%2Fserverless-contact-form/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hatefrad%2Fserverless-contact-form/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hatefrad%2Fserverless-contact-form/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hatefrad","download_url":"https://codeload.github.com/hatefrad/serverless-contact-form/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hatefrad%2Fserverless-contact-form/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32271285,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-25T09:15:33.318Z","status":"ssl_error","status_checked_at":"2026-04-25T09:15:31.997Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["contact-form","lambda","lambda-functions","serverless","typescript"],"created_at":"2026-04-25T18:06:30.896Z","updated_at":"2026-04-25T18:06:33.973Z","avatar_url":"https://github.com/hatefrad.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Serverless Contact Form API\n\nProduction-ready contact form backend using TypeScript, AWS Lambda, API Gateway,\nand AWS SES.\n\nIt is designed for secure public form submission with strong abuse controls,\npredictable behavior under retries, and flexible deployment configuration.\n\n## Highlights\n\n- Strong input validation with Zod\n- SES email delivery with typed AWS SDK v3\n- CORS allow-list support with wildcard subdomains and multi-origin config\n- Rate limiting in-memory by default, optional distributed mode via DynamoDB\n- Idempotency-key deduplication to prevent duplicate sends\n- Optional CAPTCHA verification for high-volume abuse\n- Honeypot trap for low-cost bot filtering\n- Structured error responses and explicit HTTP status handling\n- Comprehensive automated tests with Vitest\n\n## Architecture\n\n- Runtime: Node.js 20 on AWS Lambda\n- Entry point: POST /contact\n- Email transport: AWS SES\n- Optional data stores:\n  - Distributed rate limit table (DynamoDB)\n  - Idempotency table (DynamoDB)\n\n## Quick Start\n\n### Prerequisites\n\n- Node.js 20+\n- AWS CLI configured\n- AWS SES identity verified for sender email\n\n### Install\n\n```bash\nnpm install\n```\n\n### Configure\n\nCreate `secrets.json` from your sample and set values similar to:\n\n```json\n{\n  \"EMAIL\": \"your-verified-email@example.com\",\n  \"DOMAIN\": \"https://yourwebsite.com\",\n  \"AWS_REGION\": \"us-east-1\",\n  \"SES_IDENTITY_ARN\": \"arn:aws:ses:us-east-1:123456789012:identity/your-verified-email@example.com\",\n  \"RATE_LIMIT_MAX_REQUESTS\": \"5\",\n  \"RATE_LIMIT_WINDOW_MS\": \"60000\",\n  \"RATE_LIMIT_TABLE\": \"contact-form-rate-limit\",\n  \"RATE_LIMIT_PARTITION_KEY\": \"id\",\n  \"RATE_LIMIT_FAIL_OPEN\": \"true\",\n  \"IDEMPOTENCY_TTL_MS\": \"600000\",\n  \"IDEMPOTENCY_TABLE\": \"contact-form-idempotency\",\n  \"IDEMPOTENCY_PARTITION_KEY\": \"id\",\n  \"IDEMPOTENCY_FAIL_OPEN\": \"true\",\n  \"CAPTCHA_SECRET\": \"optional-provider-secret\",\n  \"CAPTCHA_VERIFY_URL\": \"https://challenges.cloudflare.com/turnstile/v0/siteverify\",\n  \"CAPTCHA_TOKEN_HEADER\": \"x-captcha-token\",\n  \"CAPTCHA_FAIL_OPEN\": \"false\"\n}\n```\n\n### Run Locally\n\n```bash\nnpm run offline\n```\n\n### Deploy\n\n```bash\nnpm run deploy\n```\n\n## API Contract\n\n### Endpoint\n\n- `POST /contact`\n\n### Request Body\n\n```json\n{\n  \"name\": \"John Doe\",\n  \"email\": \"john@example.com\",\n  \"content\": \"Hello, I would like to get in touch.\",\n  \"subject\": \"Website Contact\"\n}\n```\n\n### Optional Headers\n\n- `Idempotency-Key` or `X-Idempotency-Key`\n- `X-Captcha-Token` (or custom header via `CAPTCHA_TOKEN_HEADER`)\n\n### Success Response\n\n```json\n{\n  \"success\": true,\n  \"message\": \"Your message has been sent successfully!\",\n  \"messageId\": \"ses-message-id\"\n}\n```\n\nFor duplicate idempotency submissions, returns `200` with header\n`Idempotency-Replayed: true` and does not send another email.\n\n### Error Response\n\n```json\n{\n  \"success\": false,\n  \"error\": \"Validation failed\",\n  \"details\": \"Name must be at least 2 characters long\"\n}\n```\n\n### Status Codes\n\n- `200` success (or replay acknowledged)\n- `400` validation/captcha request errors\n- `403` forbidden (origin/captcha verification failure)\n- `405` method not allowed\n- `429` rate limit exceeded\n- `500` internal server error\n- `503` captcha provider unavailable when fail-closed\n\n## Security Model\n\n### Validation and Sanitization\n\n- Strict Zod schema validation\n- Character and length constraints\n- HTML entity sanitization for user-provided fields\n- Suspicious payload pattern detection\n\n### Origin and CORS\n\n- Exact origin allow-list support\n- Wildcard subdomains (`*.example.com`)\n- Comma-separated origin configuration\n- Proper preflight validation\n\n### Abuse Controls\n\n- Honeypot field (`_honeypot`) for naive bot detection\n- Rate limiting with configurable window and threshold\n- Optional distributed rate limiting in DynamoDB\n- Optional CAPTCHA challenge verification\n\n### Duplicate Submission Protection\n\n- Optional idempotency-key handling\n- Prevents retries/double-click duplicate sends\n- In-memory or DynamoDB-backed dedupe store\n\n## Configuration Reference\n\nRequired:\n\n- `EMAIL` verified SES sender\n- `DOMAIN` allowed origin(s) or `*`\n- `AWS_REGION` AWS region\n\nRecommended:\n\n- `SES_IDENTITY_ARN` scope SES IAM permissions to identity ARN\n\nRate limiting:\n\n- `RATE_LIMIT_MAX_REQUESTS` default `5`\n- `RATE_LIMIT_WINDOW_MS` default `60000`\n- `RATE_LIMIT_TABLE` optional DynamoDB table\n- `RATE_LIMIT_PARTITION_KEY` default `id`\n- `RATE_LIMIT_FAIL_OPEN` default `true`\n\nIdempotency:\n\n- `IDEMPOTENCY_TTL_MS` default `600000`\n- `IDEMPOTENCY_TABLE` optional DynamoDB table\n- `IDEMPOTENCY_PARTITION_KEY` default `id`\n- `IDEMPOTENCY_FAIL_OPEN` default `true`\n\nCAPTCHA:\n\n- `CAPTCHA_SECRET` enables verification when set\n- `CAPTCHA_VERIFY_URL` provider endpoint\n- `CAPTCHA_TOKEN_HEADER` default `x-captcha-token`\n- `CAPTCHA_FAIL_OPEN` default `false`\n\n## DynamoDB Table Notes\n\nDistributed rate limit table:\n\n- Partition key: String (`id` by default)\n- TTL attribute: Number `expiresAt` (recommended)\n\nIdempotency table:\n\n- Partition key: String (`id` by default)\n- TTL attribute: Number `expiresAt` (recommended)\n\n## Development\n\n### Scripts\n\n```bash\nnpm run build\nnpm run deploy\nnpm run deploy:dev\nnpm run deploy:prod\nnpm run offline\nnpm run lint\nnpm run lint:fix\nnpm run format\nnpm run type-check\nnpm run validate\nnpm test\nnpm run test:watch\nnpm run test:ui\nnpm run test:coverage\n```\n\n### Project Structure\n\n```text\nsrc/\n  handler.ts\n  security.ts\n  validation.ts\n  errors.ts\n  types.ts\ntests/\nexamples/\nserverless.yml\n```\n\n## Testing\n\nThe test suite covers:\n\n- Handler behavior and response contracts\n- Validation rules and edge cases\n- Security utilities (origin checks, rate limiting, sanitizer)\n- Distributed rate limit behavior and fail-open/fail-closed semantics\n- Idempotency and CAPTCHA flow behavior\n\nRun:\n\n```bash\nnpm test\n```\n\n## Operations and Troubleshooting\n\nCheck first:\n\n- CloudWatch logs for request and error context\n- SES sending status and identity verification\n- CORS origin configuration (`DOMAIN`)\n- CAPTCHA provider health and token header wiring\n- DynamoDB table names, IAM access, and TTL settings\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhatefrad%2Fserverless-contact-form","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhatefrad%2Fserverless-contact-form","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhatefrad%2Fserverless-contact-form/lists"}