The Challenge: Managing Community Events at Scale
Running a developer community is more than just organizing meetups. As LampungDev grew, we faced a common problem: managing event registrations manually was becoming unsustainable.
Our old process looked like this:
- π Create a Google Form for registration
- π Export responses to a spreadsheet
- βοΈ Manually email confirmations
- π Print attendance lists
- π Repeat for every event
With 100+ registrations per event, this was eating up hours of volunteer time. We needed a proper system.
The Solution: A Custom Event Management System
Instead of using a generic platform like Eventbrite or Meetup (which don't work well in Indonesia), I built a custom event management system directly into our community website.
The PR: Event Management Enhancement #47
Tech Stack
| Component | Technology |
|---|---|
| Framework | Next.js 14 (App Router) |
| Database | PostgreSQL on Supabase |
| ORM | Drizzle ORM |
| UI | Tailwind CSS + Shadcn UI |
| Nodemailer + React Email | |
| Runtime | Bun |
Features I Built
1. Event Type Management
Different events need different treatments. A workshop isn't the same as a casual meetup.
// Event types with custom colors for visual distinction
const eventTypes = [
{ name: "Workshop", color: "#3B82F6" }, // Blue
{ name: "Meetup", color: "#10B981" }, // Green
{ name: "Hackathon", color: "#F59E0B" }, // Orange
{ name: "Conference", color: "#8B5CF6" }, // Purple
];
Each event type gets its own color coding, making it easy for members to browse and filter.
2. Smart Registration System
The registration flow handles the messy edge cases that trip up simple systems:
// Simplified registration logic
async function registerForEvent(eventId: string, userId: string) {
const event = await getEvent(eventId);
const currentCount = await getRegistrationCount(eventId);
if (currentCount >= event.maxCapacity) {
// Add to waiting list instead of rejecting
return createRegistration({
eventId,
userId,
status: "WAITING_LIST",
registeredAt: new Date(),
});
}
return createRegistration({
eventId,
userId,
status: "REGISTERED",
registeredAt: new Date(),
});
}
Key features:
- β Automatic capacity checking
- β Fair waiting list (FIFO queue)
- β Automatic promotion when spots open
- β Cancellation handling
3. QR Code Tickets via Email
Every confirmed registration receives an email with a QR code ticket. On event day, organizers scan codes for instant check-in.
// Generate QR code for ticket
import QRCode from 'qrcode';
async function generateTicketQR(registrationId: string) {
const ticketUrl = `${BASE_URL}/verify/${registrationId}`;
const qrDataUrl = await QRCode.toDataURL(ticketUrl);
return qrDataUrl;
}
The QR code links to a verification page that shows:
- Participant name
- Event details
- Registration status
- Check-in button for admins
4. Automated Email Notifications
I implemented four types of automated emails:
| Email Type | Trigger |
|---|---|
| Registration Confirmation | User signs up for event |
| Waiting List Notice | Event is full, user added to queue |
| Promotion Notice | Spot opens, user promoted from waiting list |
| Event Reminder | 24 hours before event (H-1) |
Each email is built with React Email for type-safe, maintainable templates:
// React Email template example
export function RegistrationConfirmation({
userName,
eventTitle,
eventDate,
qrCode
}: Props) {
return (
<Html>
<Body style={main}>
<Container>
<Heading>You're In! π</Heading>
<Text>Hi {userName},</Text>
<Text>
Your registration for {eventTitle} is confirmed.
</Text>
<Text>π
{formatDate(eventDate)}</Text>
<Img src={qrCode} alt="Your ticket QR code" />
<Text>Show this QR code at the venue for check-in.</Text>
</Container>
</Body>
</Html>
);
}
5. Admin Dashboard
Organizers need control. The admin panel provides:
- π Registration statistics (registered vs. waiting list vs. cancelled)
- π₯ Participant list with search and filters
- βοΈ Manual status updates (promote, cancel, mark attended)
- π§ Bulk email capabilities
- π Attendance tracking with timestamps
Database Design
I added three new tables using Drizzle ORM:
// schema.ts
export const eventType = pgTable("event_type", {
id: uuid("id").primaryKey().defaultRandom(),
name: varchar("name", { length: 100 }).notNull(),
description: text("description"),
color: varchar("color", { length: 7 }), // hex color
createdAt: timestamp("created_at").defaultNow(),
});
export const event = pgTable("event", {
id: uuid("id").primaryKey().defaultRandom(),
title: varchar("title", { length: 200 }).notNull(),
slug: varchar("slug", { length: 250 }).notNull().unique(),
description: text("description"),
eventTypeId: uuid("event_type_id").references(() => eventType.id),
location: varchar("location", { length: 300 }),
locationMapUrl: text("location_map_url"),
imageUrl: text("image_url"),
eventDate: timestamp("event_date").notNull(),
maxCapacity: integer("max_capacity").notNull(),
registrationStatus: registrationStatusEnum("registration_status")
.default("OPEN"),
createdAt: timestamp("created_at").defaultNow(),
});
export const eventRegistration = pgTable("event_registration", {
id: uuid("id").primaryKey().defaultRandom(),
eventId: uuid("event_id").references(() => event.id).notNull(),
userId: uuid("user_id").references(() => user.id).notNull(),
status: participantStatusEnum("status").default("REGISTERED"),
registeredAt: timestamp("registered_at").defaultNow(),
attended: boolean("attended").default(false),
attendedAt: timestamp("attended_at"),
});
Lessons Learned
1. Waiting Lists Are Harder Than They Look
The tricky part isn't adding someone to a waiting listβit's promoting them when a spot opens. You need to:
- Maintain FIFO order
- Handle concurrent cancellations
- Send promotion emails reliably
- Update UI state in real-time
2. Email Deliverability Matters
Sending emails is easy. Getting them into inboxes (not spam) is hard. I had to:
- Configure proper SPF/DKIM records
- Use a reputable SMTP provider
- Include unsubscribe links
- Keep email content clean (no spammy words)
3. Mobile-First Is Non-Negotiable
80%+ of our community accesses the site from phones. Every feature had to work flawlessly on mobile:
- Touch-friendly buttons
- Single-column layouts
- Fast loading on slow connections
Results
After deploying the system:
| Metric | Before | After |
|---|---|---|
| Time to create event | 30 min | 5 min |
| Registration handling | Manual | Automatic |
| Check-in process | Paper list | QR scan |
| No-show rate | ~40% | ~20% |
The QR code emails alone cut no-shows in halfβpeople feel more committed when they have a "ticket."
What's Next
Features planned for future iterations:
- π± Mobile app for check-in (using the password field I added for mobile auth)
- π³ Paid event support (for workshops with materials)
- π Analytics dashboard (attendee demographics, popular event types)
- π Push notifications
Try It Yourself
The entire codebase is open source:
π Repository: Lampung-Dev/lampung-dev-web
π Pull Request: Event Management Enhancement #47
π₯ Demo Video: Watch on Loom
If you're building something similar for your community, feel free to fork the repo or reach out with questions!
Building tools for developer communities? I'd love to hear about your projects. Get in touch or find me on GitHub.
