Loading
Please wait...

3 Januari 2026

Building an Event Management System for a Developer Community

How I built a comprehensive event management system with registration, waiting lists, QR code tickets, and automated emails for LampungDev - a developer community in Indonesia.

5 min read

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:

  1. πŸ“ Create a Google Form for registration
  2. πŸ“Š Export responses to a spreadsheet
  3. βœ‰οΈ Manually email confirmations
  4. πŸ“‹ Print attendance lists
  5. πŸ”„ 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

ComponentTechnology
FrameworkNext.js 14 (App Router)
DatabasePostgreSQL on Supabase
ORMDrizzle ORM
UITailwind CSS + Shadcn UI
EmailNodemailer + React Email
RuntimeBun

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 TypeTrigger
Registration ConfirmationUser signs up for event
Waiting List NoticeEvent is full, user added to queue
Promotion NoticeSpot opens, user promoted from waiting list
Event Reminder24 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:

MetricBeforeAfter
Time to create event30 min5 min
Registration handlingManualAutomatic
Check-in processPaper listQR 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.

Continue Reading

Previous article

← Previous Article

Rebuilding My Laravel E-Learning App: A Journey from 5.2 to Modern Laravel

Next Article β†’

Laravel E-Learning Part 2: Setting Up Laravel 11 with Filament Admin Panel

Next article