MDL Serverless Event Registration System (Plugin + AWS)

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).
MDL Event Registration Manager


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

Event Registration – Serverless Backend


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