Back to WritingWeb
Building a Spam-Proof Inquiry System with Laravel and reCAPTCHA
How I delivered a production inquiry system with layered spam defense and automated lead routing.
Context
A Montreal-based travel agency needed a digital inquiry form to replace phone/email inquiries. The form captures flight preferences (origin, destination, dates, passengers), validates in real-time, and routes leads to the right agent. Critically, it had to survive public exposure without drowning in spam.
The Problem
- ▸No structured way to capture flight inquiries — details lost in email threads
- ▸Manual data entry led to miscommunication and booking errors
- ▸Public forms attract spam bots — no existing protection in place
- ▸No reference system for customers to track their inquiries
The Solution
A Laravel 11 single-page inquiry system with three defense layers:
- ▸Frontend: Blade + Vite + vanilla JS — airport autocomplete, country code picker, real-time validation, AJAX submit
- ▸Backend: Laravel 11 — form request validation, reCAPTCHA v3 verification, honeypot check, encrypted PII storage, Brevo SMTP email dispatch
- ▸API: Laravel Sanctum — token-based auth for inquiry CRUD, reference lookup, admin listing
Request Lifecycle
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Submit │───►│ Throttle│───►│ Validate │───►│ Process │───►│ JSON │ │ Inquiry │ │ 10/hr IP │ │+reCAPTCHA│ │ + Emails │ │ Response │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
Key Features
- ▸Single-page flight inquiry form with real-time validation
- ▸Google reCAPTCHA v3 + honeypot field for bot/spam protection
- ▸Automated email notifications via Brevo SMTP (user confirmation + admin alert)
- ▸REST API with Laravel Sanctum authentication for inquiry management
- ▸Auto-generated reference numbers (BT-YYYYMMDD-NNNNNN format)
- ▸Encrypted PII at rest via Laravel encrypted casts (7 sensitive fields)
- ▸Airport autocomplete with static dataset and country code selector
- ▸Custom rate limiting middleware (10 submissions/hr per IP)
- ▸Docker multi-stage build with Nginx + PHP-FPM + Supervisor
Security Implementation
| Concern | Implementation |
|---|---|
| PII at rest | Laravel encrypted casts on 7 sensitive fields (names, email, phone, IP, UA) |
| CSRF | @csrf directive in form, Laravel automatic protection |
| XSS | Blade auto-escaping |
| SQL injection | Eloquent ORM with parameterized queries |
| Rate limiting | Custom middleware: 10/hr per IP (web); Laravel throttle: 10/min (API store) |
| Spam/bot | reCAPTCHA v3 (score thresholds 0.3–0.5) + honeypot field |
Deployment & CI/CD
- ▸Hosting: Render (Docker, free tier, Oregon) + PostgreSQL (bta-db)
- ▸Container: PHP 8.2-fpm-alpine + Nginx + Supervisor multi-stage Docker build
- ▸CI/CD: GitHub Actions — lint, tests, Docker buildx, Render deploy
- ▸Health check: /health.php endpoint for Render monitoring
Technology Stack
| Layer | Technology |
|---|---|
| Backend | Laravel 11 / PHP 8.2 |
| Database | PostgreSQL 16 |
| Frontend | Blade / Vite / Vanilla JS |
| Security | reCAPTCHA v3 / Laravel Encrypted Casts / Sanctum |
| Testing | PHPUnit / SQLite in-memory |
| Container | Docker / Alpine / Nginx / PHP-FPM |
| CI/CD | GitHub Actions / Render |
Notable Design Decisions
- ▸Single table design — inquiries stored in one table with no relationships for simplicity and fast queries
- ▸Encrypted casts for all PII — leverages Laravel's built-in encryption (depends on APP_KEY), keeping sensitive data encrypted at rest
- ▸After-response email dispatch — emails sent after the HTTP response to avoid blocking the user
- ▸ThrottleInquiries middleware — custom rate limiter with configurable limit via INQUIRY_RATE_LIMIT env variable (default 10/hr)
- ▸reCAPTCHA v3 double validation — score threshold of 0.3 in form request (lenient) vs 0.5 in service (strict), with action name verification
- ▸Honeypot field — invisible website field that must remain empty, catching basic bots without user friction
- ▸Auto-generated reference numbers — collision-resistant BT-YYYYMMDD-NNNNNN format for easy customer lookup