event-management
Guides event lifecycle implementation including registration, capacity management, waitlists, QR code check-in, virtual/hybrid events, and post-event analytics for NABIP conferences, webinars, workshops, and networking events. Use when building event creation workflows, attendee management, or event performance tracking.
When & Why to Use This Skill
This Claude skill provides a comprehensive framework for implementing the full event management lifecycle within the NABIP ecosystem. It offers standardized TypeScript data models, robust registration workflows, and automated logic for capacity management, QR code check-ins, and virtual/hybrid event integration. By streamlining complex operations from scheduling to post-event analytics, it helps developers build scalable solutions that enhance attendee experience and drive measurable business outcomes.
Use Cases
- Developing automated registration workflows with multi-tier pricing (Early Bird, Member, Non-Member) and eligibility verification.
- Implementing intelligent capacity management and automated waitlist promotion logic to maximize event attendance.
- Building secure QR code-based check-in systems for efficient on-site attendee verification and real-time attendance tracking.
- Configuring virtual and hybrid event support, including automated access link distribution for platforms like Zoom and Teams.
- Creating post-event analytics dashboards to evaluate revenue, attendance rates, and capacity utilization for performance reporting.
| name | event-management |
|---|---|
| description | Guides event lifecycle implementation including registration, capacity management, waitlists, QR code check-in, virtual/hybrid events, and post-event analytics for NABIP conferences, webinars, workshops, and networking events. Use when building event creation workflows, attendee management, or event performance tracking. |
Event Management
Streamline event operations to improve attendee experience and drive measurable outcomes across in-person, virtual, and hybrid NABIP events.
When to Use
Activate this skill when:
- Creating event registration workflows
- Implementing capacity and waitlist management
- Building QR code check-in systems
- Designing virtual/hybrid event support
- Creating multi-tier pricing (early bird, member, non-member)
- Implementing post-event analytics and feedback
- Working on event calendar and scheduling
- Building event communication workflows (confirmations, reminders)
Event Types
NABIP Event Categories
- Conferences: Multi-day, large-scale events (500+ attendees)
- Webinars: Online-only educational sessions (100-300 attendees)
- Workshops: Hands-on training sessions (25-75 attendees)
- Networking: Social and professional connection events (50-200 attendees)
- Chapter Meetings: Local/state gatherings (15-50 attendees)
Event Lifecycle States
draft → published → registration_open → registration_closed → in_progress → completed → archived
State Transition Rules
- draft → published: Event details finalized, ready for announcement
- published → registration_open: Registration portal activated
- registration_open → registration_closed: Registration deadline reached OR capacity filled
- registration_closed → in_progress: Event start date/time reached
- in_progress → completed: Event end date/time reached
- completed → archived: Post-event tasks finished (30 days after completion)
Event Data Model
interface Event {
id: string
title: string
description: string
eventType: "conference" | "webinar" | "workshop" | "networking" | "chapter_meeting"
deliveryMode: "in_person" | "virtual" | "hybrid"
status: "draft" | "published" | "registration_open" | "registration_closed" | "in_progress" | "completed" | "archived"
// Scheduling
startDate: Date
endDate: Date
timezone: string
registrationOpenDate: Date
registrationCloseDate: Date
// Capacity
capacity: number
currentRegistrations: number
waitlistEnabled: boolean
waitlistCapacity?: number
// Location (for in-person/hybrid)
venueName?: string
venueAddress?: string
venueCity?: string
venueState?: string
venueZip?: string
// Virtual (for virtual/hybrid)
virtualPlatform?: "zoom" | "teams" | "webex" | "custom"
virtualMeetingUrl?: string
virtualAccessCode?: string
// Pricing
pricingTiers: EventPricingTier[]
// Organizer
organizerId: string
chapterId: string
// Metadata
createdAt: Date
updatedAt: Date
}
interface EventPricingTier {
id: string
eventId: string
name: string // "Early Bird", "Member", "Non-Member", "Student"
price: number
currency: string
validFrom?: Date
validUntil?: Date
memberTypesEligible?: string[] // ["national", "state", "local"]
maxQuantity?: number
}
interface EventRegistration {
id: string
eventId: string
memberId: string
status: "pending" | "confirmed" | "cancelled" | "waitlisted" | "attended" | "no_show"
pricingTierId: string
amountPaid: number
paymentStatus: "pending" | "completed" | "refunded" | "failed"
registrationDate: Date
cancellationDate?: Date
checkInTime?: Date
checkInMethod?: "qr_code" | "manual" | "self_service"
// Attendee info (for non-members)
guestName?: string
guestEmail?: string
guestPhone?: string
// Dietary/accessibility
dietaryRequirements?: string
accessibilityNeeds?: string
specialRequests?: string
}
Registration Workflow
Event Registration Process
async function registerForEvent(
eventId: string,
memberId: string,
pricingTierId: string,
additionalInfo?: {
dietaryRequirements?: string
accessibilityNeeds?: string
specialRequests?: string
}
) {
// Step 1: Validate event availability
const event = await getEvent(eventId)
if (event.status !== "registration_open") {
throw new Error("Registration is not currently open for this event")
}
// Step 2: Check capacity
if (event.currentRegistrations >= event.capacity) {
if (event.waitlistEnabled && event.waitlistCapacity) {
return addToWaitlist(eventId, memberId)
}
throw new Error("Event is at full capacity")
}
// Step 3: Validate pricing tier eligibility
const member = await getMember(memberId)
const pricingTier = event.pricingTiers.find(pt => pt.id === pricingTierId)
if (!pricingTier) {
throw new Error("Invalid pricing tier")
}
if (pricingTier.memberTypesEligible) {
if (!pricingTier.memberTypesEligible.includes(member.memberType)) {
throw new Error("You are not eligible for this pricing tier")
}
}
// Step 4: Check for duplicate registration
const existingRegistration = await db
.select()
.from(eventRegistrations)
.where(
and(
eq(eventRegistrations.eventId, eventId),
eq(eventRegistrations.memberId, memberId),
ne(eventRegistrations.status, "cancelled")
)
)
.limit(1)
if (existingRegistration.length > 0) {
throw new Error("You are already registered for this event")
}
// Step 5: Create registration record
const registration = await db
.insert(eventRegistrations)
.values({
eventId,
memberId,
pricingTierId,
status: "pending",
amountPaid: pricingTier.price,
paymentStatus: "pending",
registrationDate: new Date(),
...additionalInfo
})
.returning()
// Step 6: Process payment
const payment = await processEventPayment(
registration[0].id,
pricingTier.price,
memberId
)
if (!payment.success) {
// Mark registration as failed
await db
.update(eventRegistrations)
.set({ paymentStatus: "failed" })
.where(eq(eventRegistrations.id, registration[0].id))
throw new Error("Payment processing failed")
}
// Step 7: Confirm registration
await db
.update(eventRegistrations)
.set({
status: "confirmed",
paymentStatus: "completed"
})
.where(eq(eventRegistrations.id, registration[0].id))
// Step 8: Update event capacity
await db
.update(events)
.set({
currentRegistrations: sql`${events.currentRegistrations} + 1`
})
.where(eq(events.id, eventId))
// Step 9: Send confirmation email
await sendEventConfirmationEmail(member.email, {
eventTitle: event.title,
startDate: event.startDate,
location: event.deliveryMode === "in_person" ? event.venueName : "Virtual",
confirmationCode: registration[0].id,
amount: pricingTier.price
})
// Step 10: Add to calendar (generate .ics file)
const calendarInvite = generateCalendarInvite(event, registration[0])
await emailCalendarInvite(member.email, calendarInvite)
return registration[0]
}
Waitlist Management
async function addToWaitlist(eventId: string, memberId: string) {
const event = await getEvent(eventId)
if (!event.waitlistEnabled) {
throw new Error("Waitlist is not available for this event")
}
const waitlistCount = await db
.select({ count: sql`COUNT(*)` })
.from(eventRegistrations)
.where(
and(
eq(eventRegistrations.eventId, eventId),
eq(eventRegistrations.status, "waitlisted")
)
)
if (waitlistCount[0].count >= (event.waitlistCapacity || 0)) {
throw new Error("Waitlist is full")
}
const registration = await db
.insert(eventRegistrations)
.values({
eventId,
memberId,
status: "waitlisted",
registrationDate: new Date()
})
.returning()
await sendWaitlistEmail(memberId, event)
return registration[0]
}
async function promoteFromWaitlist(eventId: string, count: number = 1) {
// Get waitlisted registrations in order
const waitlisted = await db
.select()
.from(eventRegistrations)
.where(
and(
eq(eventRegistrations.eventId, eventId),
eq(eventRegistrations.status, "waitlisted")
)
)
.orderBy(asc(eventRegistrations.registrationDate))
.limit(count)
for (const registration of waitlisted) {
// Update status to pending (requires payment)
await db
.update(eventRegistrations)
.set({ status: "pending" })
.where(eq(eventRegistrations.id, registration.id))
// Send promotion email with payment link
const member = await getMember(registration.memberId)
await sendWaitlistPromotionEmail(member.email, {
eventId,
registrationId: registration.id,
paymentDeadline: new Date(Date.now() + 48 * 60 * 60 * 1000) // 48 hours
})
}
}
QR Code Check-In System
import { QRCodeSVG } from "qrcode.react"
// Generate unique QR code for each registration
function generateCheckInQR(registrationId: string): string {
const checkInData = {
registrationId,
timestamp: Date.now(),
signature: generateHMAC(registrationId) // Security verification
}
return JSON.stringify(checkInData)
}
// Render QR code in confirmation email
export function RegistrationQRCode({ registrationId }: { registrationId: string }) {
const qrData = generateCheckInQR(registrationId)
return (
<div className="flex flex-col items-center gap-4">
<QRCodeSVG
value={qrData}
size={200}
level="H"
includeMargin
/>
<p className="text-sm text-muted-foreground">
Show this QR code at event check-in
</p>
<p className="text-xs text-muted-foreground font-mono">
Confirmation: {registrationId.slice(0, 8).toUpperCase()}
</p>
</div>
)
}
// Check-in scanning endpoint
async function processCheckIn(scannedData: string) {
const checkInData = JSON.parse(scannedData)
// Verify signature
if (!verifyHMAC(checkInData.registrationId, checkInData.signature)) {
throw new Error("Invalid QR code")
}
// Verify registration exists
const registration = await db
.select()
.from(eventRegistrations)
.where(eq(eventRegistrations.id, checkInData.registrationId))
.limit(1)
if (!registration.length) {
throw new Error("Registration not found")
}
if (registration[0].status !== "confirmed") {
throw new Error(`Registration status: ${registration[0].status}`)
}
// Mark as attended
await db
.update(eventRegistrations)
.set({
status: "attended",
checkInTime: new Date(),
checkInMethod: "qr_code"
})
.where(eq(eventRegistrations.id, checkInData.registrationId))
return {
success: true,
attendeeName: registration[0].guestName || (await getMember(registration[0].memberId)).name
}
}
Virtual/Hybrid Event Support
interface VirtualEventSession {
id: string
eventId: string
sessionName: string
startTime: Date
endTime: Date
platform: "zoom" | "teams" | "webex" | "custom"
meetingUrl: string
meetingId?: string
passcode?: string
hostId: string
recordingEnabled: boolean
recordingUrl?: string
}
async function sendVirtualEventAccess(registrationId: string) {
const registration = await getRegistration(registrationId)
const event = await getEvent(registration.eventId)
const member = await getMember(registration.memberId)
if (event.deliveryMode === "in_person") {
throw new Error("This is not a virtual event")
}
// Generate unique access link (with tracking)
const accessToken = generateAccessToken(registrationId)
const trackableUrl = `${event.virtualMeetingUrl}?ref=${accessToken}`
await sendEmail({
to: member.email,
subject: `Virtual Access: ${event.title}`,
template: "virtual-event-access",
data: {
eventTitle: event.title,
startDate: event.startDate,
platform: event.virtualPlatform,
meetingUrl: trackableUrl,
accessCode: event.virtualAccessCode,
instructions: getVirtualPlatformInstructions(event.virtualPlatform)
}
})
// Send reminder 1 hour before
await scheduleReminder(registrationId, {
sendAt: new Date(event.startDate.getTime() - 60 * 60 * 1000),
template: "virtual-event-reminder",
includeAccessLink: true
})
}
Post-Event Analytics
interface EventPerformanceMetrics {
totalRegistrations: number
totalAttendees: number
attendanceRate: number
noShowRate: number
cancellationRate: number
totalRevenue: number
revenueByTier: Record<string, number>
averageTicketPrice: number
capacityUtilization: number
waitlistPromotions: number
checkInMethods: Record<string, number>
feedbackAverageRating?: number
feedbackResponseRate?: number
}
async function calculateEventMetrics(eventId: string): Promise<EventPerformanceMetrics> {
const event = await getEvent(eventId)
const registrations = await db
.select()
.from(eventRegistrations)
.where(eq(eventRegistrations.eventId, eventId))
const totalRegistrations = registrations.length
const totalAttendees = registrations.filter(r => r.status === "attended").length
const noShows = registrations.filter(r => r.status === "no_show").length
const cancelled = registrations.filter(r => r.status === "cancelled").length
const totalRevenue = registrations
.filter(r => r.paymentStatus === "completed")
.reduce((sum, r) => sum + r.amountPaid, 0)
return {
totalRegistrations,
totalAttendees,
attendanceRate: (totalAttendees / totalRegistrations) * 100,
noShowRate: (noShows / totalRegistrations) * 100,
cancellationRate: (cancelled / totalRegistrations) * 100,
totalRevenue,
revenueByTier: calculateRevenueByTier(registrations),
averageTicketPrice: totalRevenue / totalRegistrations,
capacityUtilization: (totalRegistrations / event.capacity) * 100,
waitlistPromotions: registrations.filter(r =>
r.status === "confirmed" && r.promotedFromWaitlist
).length,
checkInMethods: countCheckInMethods(registrations)
}
}
// Automated post-event survey
async function sendPostEventSurvey(eventId: string) {
const attendees = await db
.select()
.from(eventRegistrations)
.where(
and(
eq(eventRegistrations.eventId, eventId),
eq(eventRegistrations.status, "attended")
)
)
for (const attendee of attendees) {
const member = await getMember(attendee.memberId)
const surveyToken = generateSurveyToken(attendee.id)
await sendEmail({
to: member.email,
subject: "How was your experience?",
template: "post-event-survey",
data: {
eventTitle: (await getEvent(eventId)).title,
surveyUrl: `https://ams.nabip.org/surveys/${surveyToken}`
}
})
}
}
Integration with Other Skills
- Use with
supabase-schema-validatorfor event data models - Combine with
component-generatorfor event UI - Works with
analytics-helperfor performance metrics - Supports
member-workflowfor attendee management
Best for: Developers building event management features including registration, check-in, virtual event delivery, and post-event analytics for the NABIP AMS.