MDL Serverless Event Registration System (Plugin + AWS)

1. Project Overview
The Marshall District Library (MDL) needed a reliable way to collect and manage registrations for public programs (afterschool programs, workshops, clubs), send confirmations and reminders, and generate printable day-of rosters for staff.
We built a serverless backend on AWS and a custom WordPress plugin that integrates with The Events Calendar and Forminator to transform form submissions into structured registrations with email notifications and easy export for printing.
High-level flow: WordPress form → API Gateway (custom domain) → SQS FIFO → Lambda → DynamoDB → (EventBridge schedules, SES/SMTP emails, SNS admin alerts).
2. Challenge / Problem
- Registration data previously lived in ad-hoc spreadsheets and email inboxes, making it hard to track capacity, waitlists, and attendance.
- Manual reminder emails and roster prep consumed staff time and introduced errors.
- Spikes in submissions around popular events sometimes caused reliability issues for the site’s PHP stack.
Goals
- Centralize registrations in a durable, queryable store.
- Automate confirmations, reminders, and staff notifications.
- Keep the WordPress experience simple while moving compute off the PHP server.
3. Design Decisions
Why serverless?
- Elastic scaling for submission bursts without stressing WordPress.
- Managed ops: no patching servers; pay only for what runs.
- Decoupling: resilience via SQS buffer and idempotent Lambdas.
Core services
- API Gateway (REST): Custom domain with ACM certificate; API key authentication for WordPress plugin endpoints; CORS-enabled for cross-origin requests.
- SQS (3 queues): FIFO queue for registration processing (ordered waitlist), standard queues for email notifications and registration updates; 14-day retention with DLQ.
- Lambda (15+ functions): Stateless processors for webhooks, registration logic, email orchestration, and admin API; 600s timeout for batch operations.
- DynamoDB (3 tables): Pay-per-request billing; registrations with 3 GSIs, email templates with type index, atomic event counters.
- EventBridge (3 schedules): Reminder checks every 30min, sends offset by 15min, daily 2AM cleanup.
- SNS: Admin notifications for cleanup operations and system alerts.
- Route 53 + ACM: Custom domain routing with edge-optimized distribution.
Trade-offs
- DynamoDB over RDS: Simpler scaling, no server patching, ~90% cost reduction; trade-off is more complex query patterns requiring GSIs.
- SMTP via SES: Direct SMTP integration instead of SDK for simpler email templating in WordPress plugin context.
- Multi-environment SAM template: Single template with conditionals for dev/prod; shares infrastructure code while isolating data.
- Separate counter table: Atomic increments for capacity checks avoid race conditions; small performance cost for extra read.
4. Architecture Diagram
5. Implementation Highlights
WordPress Plugin (MDL Registration Manager)
- Exposes a secured JSON endpoint that provides Lambda functions with real-time event data from The Events Calendar (times, location, descriptions, capacity).
- Hooks Forminator submission webhooks to send registration data to API Gateway with HMAC signatures.
- Admin UI panels for per-event capacity, waitlist toggle for moving attendees between lists, and “Print Registrations” button that prints a clean roster of attendees or waitlist for staff in either PDF or hardcopy.
- Registration counts and attendee data live in DynamoDB; WordPress serves as the event metadata source of truth.
Lambda Functions
The system uses 15+ Lambda functions organized by responsibility:
- Webhook handlers: Receive Forminator submissions, validate HMAC signatures
- Registration processors: Check capacity, manage waitlist, write to DynamoDB with idempotency
- Email orchestration: Template rendering, scheduled reminders (EventBridge cron), confirmation sends
- Admin API: WordPress plugin endpoints for capacity management, registration updates, template CRUD
- Maintenance: Nightly cleanup of expired templates and old registration data
DynamoDB Schema (Registrations Table)
RegistrationTable:
AttributeDefinitions:
- event_id (HASH)
- registration_id (RANGE)
GlobalSecondaryIndexes:
- user_email-event_id-index: Look up patron's registrations across events
- event_status-index: Fast queries for registered vs waitlisted
- event_status_created-index: Waitlist ordering by timestamp
Separate tables for email templates and event counters (atomic increment for capacity checks).
SQS Configuration
ProcessRegistrationQueue:
FifoQueue: true
ContentBasedDeduplication: true
VisibilityTimeout: 300
MessageRetentionPeriod: 1209600 # 14 days
FIFO ensures waitlist promotions respect submission order; content-based deduplication prevents double-registration.
Scheduled Reminders (EventBridge)
CheckReminderEmailsSchedule:
Schedule: cron(0,30 * * * ? *) # Every 30 minutes
SendReminderEmailsSchedule:
Schedule: cron(15,45 * * * ? *) # Offset send window
EmailTemplateDBCleanupSchedule:
Schedule: cron(0 6 * * ? *) # Daily 2AM EST cleanup
Two-stage reminder process avoids SES throttling; nightly cleanup removes expired templates and triggers SNS notifications.
Google Sheets Integration (Initial Implementation)
Originally built Lambda functions with Google Service Account authentication to sync registrations to Sheets for staff-friendly roster printing. Configured OAuth2 credentials and Google Sheets API access; Lambda wrote per-event tabs with formatted attendee lists. Later deprecated in favor of direct WordPress admin UI exports, but demonstrated cross-platform API integration and service account credential management in Lambda environment variables.
Reliability & Ops
- SQS redrive with DLQ; Lambda maxReceiveCount = 5.
- Idempotency: conditional writes with
attribute_not_exists
, idempotency token on SES sends. - Observability: CloudWatch metrics/alarms on DLQ depth, Lambda errors, SES bounce rate.
- Security:
- HMAC verification on webhooks; short-lived API keys for admin actions.
- IAM least-privilege per Lambda (DynamoDB item-level actions, SES:SendEmail, SNS:Publish scope).
- PII minimized in logs; structured logging with request IDs.
Cost Posture
- Near-zero idle cost; submissions burst smoothly via SQS.
- DynamoDB on on-demand initially; switch to provisioned + autoscaling when traffic patterns stabilize.
- SES keeps notification costs pennies/month.
6. Results / Impact
- Operational simplicity: Staff manage registrations directly in WordPress admin; eliminated spreadsheet workflows.
- Reliability: Spiky form traffic no longer impacts WordPress performance.
- Patron experience: Automatic confirmations + day-before reminders reduce no-shows and confusion.
- Staff time saved: Program leads get a consistent printable roster and optional same-day attendance sheet.
(Metrics collection is ongoing; we’ll add concrete numbers for staff time saved and email engagement in the next revision.)
7. Reflection
Serverless let us separate the patron-facing experience from the operational backbone without retraining staff. The trickiest parts were idempotency around duplicate submissions and capacity/waitlist rules that play nicely with real-world edge cases (family group sizes, last-minute cancellations).
Next steps: add an admin dashboard backed by DynamoDB queries (attendance trends, capacity utilization) and optional ICS attachments in confirmation emails for better calendar adoption.
Appendix — IAM Least-Privilege Example
LambdaExecutionRole:
Policies:
- PolicyName: DynamoDBAccess
Statement:
- Effect: Allow
Action:
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:Query
Resource:
- arn:aws:dynamodb:REGION:ACCOUNT:table/mdl_event_registrations-ENV
- arn:aws:dynamodb:REGION:ACCOUNT:table/mdl_event_registrations-ENV/index/*
- arn:aws:dynamodb:REGION:ACCOUNT:table/mdl_event_email_templates-ENV
- arn:aws:dynamodb:REGION:ACCOUNT:table/event-counter-mdl-reg-manager-ENV
- PolicyName: SQSAccess
Statement:
- Effect: Allow
Action:
- sqs:SendMessage
- sqs:ReceiveMessage
- sqs:DeleteMessage
Resource:
- arn:aws:sqs:REGION:ACCOUNT:process-registration-queue-ENV.fifo
- arn:aws:sqs:REGION:ACCOUNT:mdl-email-notifications-ENV
Each Lambda gets only the table/queue access it needs; no wildcard permissions