Skip to content
Irvine "Irvs" Clark B. EbajanIrvs
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

ConcernImplementation
PII at restLaravel encrypted casts on 7 sensitive fields (names, email, phone, IP, UA)
CSRF@csrf directive in form, Laravel automatic protection
XSSBlade auto-escaping
SQL injectionEloquent ORM with parameterized queries
Rate limitingCustom middleware: 10/hr per IP (web); Laravel throttle: 10/min (API store)
Spam/botreCAPTCHA 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

LayerTechnology
BackendLaravel 11 / PHP 8.2
DatabasePostgreSQL 16
FrontendBlade / Vite / Vanilla JS
SecurityreCAPTCHA v3 / Laravel Encrypted Casts / Sanctum
TestingPHPUnit / SQLite in-memory
ContainerDocker / Alpine / Nginx / PHP-FPM
CI/CDGitHub 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