AWS CDK constructs for deploying a self-hosted Lightning Talks booking application. Teams sign up to give short talks, organizers schedule sessions, and attendees receive magic-link email invitations.
- Passwordless auth — magic-link sign-in via Amazon Cognito and SES
- Multi-org — multiple independent organisations, each with their own series
- Series and sessions — recurring weekly/biweekly/monthly talk series with bookable slots
- Microsoft Teams integration — webhook notifications, bot, Entra ID SSO, Graph calendar sync, live meeting panel
- Reactions and discussions — emoji reactions and threaded comments on talk slots
- File uploads — speakers can attach slides or supporting materials
- Statistics — aggregated usage reports stored as JSON in S3
- Serverless — API Gateway HTTP API + Lambda, CloudFront + S3, DynamoDB, all managed by CDK
Route 53 (DnsStack)
└── ACM Certificate + WAF WebACL (CertificateStack, us-east-1)
└── CloudFront Distribution ──── S3 (React frontend)
└── /auth/*, /series/*, /features ──── API Gateway HTTP API
└── Lambda (Node.js 22)
└── DynamoDB
└── Cognito
└── SES
└── S3 (uploads)
- Node.js 22+
- AWS CLI configured (
aws configure) - AWS CDK CLI:
npm install -g aws-cdk - AWS account bootstrapped:
cdk bootstrap
npm install @yourorg/lightning-talks aws-cdk-lib constructsCreate your deployment config:
// bin/my-app.ts
import { App, RemovalPolicy } from "aws-cdk-lib";
import {
LightningTalksDataStack,
DnsStack,
CertificateStack,
LightningTalksStack,
type EnvironmentConfig,
} from "@yourorg/lightning-talks";
const app = new App();
const config: EnvironmentConfig = {
envName: "prod",
appUrl: "https://talks.example.com",
appDomain: "talks.example.com",
parentZoneId: "Z0123456789",
parentZoneName: "example.com",
ses: { managed: true, sesAlertsEmail: "alerts@example.com" },
allowedEmailDomains: "example.com",
allowedOrigins: ["https://talks.example.com"],
removalPolicy: RemovalPolicy.RETAIN,
features: {
reactions: true,
discussions: true,
statistics: true,
uploads: { enabled: true, maxFileSizeMB: 50, maxFilesPerSlot: 3 },
teams: { enabled: false, webhookUrls: [], botEndpoint: false,
entraFederation: false, graphCalendar: false, meetingPanel: false },
},
};
const dataStack = new LightningTalksDataStack(app, "Data", { config });
const dnsStack = new DnsStack(app, "Dns", { config });
const certStack = new CertificateStack(app, "Cert", {
config, hostedZone: dnsStack.hostedZone, env: { region: "us-east-1" },
});
certStack.addDependency(dnsStack);
const appStack = new LightningTalksStack(app, "App", {
config,
certificate: certStack.certificate,
hostedZone: dnsStack.hostedZone,
webAclArn: certStack.webAclArn,
});
appStack.addDependency(dataStack);
appStack.addDependency(dnsStack);
appStack.addDependency(certStack);cdk deploy --allgit clone https://github.com/yourorg/lightning-talks.git
cd lightning-talks
npm install
cp config/example.ts config/dev.ts
# Edit config/dev.ts with your domain and AWS zone ID
cdk deploy --all --context env=dev| Field | Type | Description |
|---|---|---|
envName |
string |
Environment prefix for resource names (e.g. "dev", "prod") |
appUrl |
string |
Full URL including protocol (e.g. "https://talks.example.com") |
appDomain |
string |
Bare domain (e.g. "talks.example.com") |
parentZoneId |
string |
Route 53 hosted zone ID of the parent zone |
parentZoneName |
string |
Domain name of the parent zone |
alertEmail |
string? |
Email for CloudWatch alarm notifications. Omit to skip alarm creation |
ses |
ManagedSesConfig | ExternalSesConfig |
SES configuration (see below) |
allowedOrigins |
string[] |
CORS allowed origins (must include appUrl) |
allowedEmailDomains |
string? |
Comma-separated allowed email domains. Empty = allow all |
removalPolicy |
RemovalPolicy |
RETAIN for prod, DESTROY for dev |
features |
FeatureFlags |
Feature flags (see below) |
CDK-managed (recommended — CDK creates and verifies the domain identity):
ses: { managed: true, sesAlertsEmail: "alerts@example.com" }Externally managed (bring your own verified SES identity):
ses: {
managed: false,
fromEmail: "noreply@example.com",
identityDomain: "example.com",
configSetName: "my-config-set",
}| Flag | Type | Description |
|---|---|---|
reactions |
boolean |
Emoji reactions on talk slots |
discussions |
boolean |
Threaded comments on talk slots |
statistics |
boolean |
Usage statistics dashboard |
uploads.enabled |
boolean |
File upload support |
uploads.maxFileSizeMB |
number |
Maximum upload size in MB |
uploads.maxFilesPerSlot |
number |
Maximum files per talk slot |
teams.enabled |
boolean |
Microsoft Teams webhook notifications |
teams.botEndpoint |
boolean |
Teams bot messaging endpoint |
teams.entraFederation |
boolean |
Entra ID (Azure AD) federated SSO |
teams.graphCalendar |
boolean |
Microsoft Graph calendar sync |
teams.meetingPanel |
boolean |
Live meeting panel WebSocket |
# Frontend dev server
cd frontend && npm install && npm run dev
# Lambda unit tests
cd lambdas && npm install && npm test
# CDK tests
npm test
# CDK synth (validates your config without deploying)
cdk synth --context env=dev# CDK infrastructure tests
npm test
# Lambda handler tests
cd lambdas && npm test
# Frontend tests
cd frontend && npm testSee CONTRIBUTING.md.
To report a vulnerability, see SECURITY.md.