Bug Report System Architecture & Developer Guide
In-app bug reporting for customer dashboard users. Reports are persisted to MongoDB, attachments uploaded to S3, and email notifications (with attachments) sent to the org admin via the existing email service.
Why
Customers previously had no in-app way to report bugs they relied on external channels (email, Slack, WhatsApp). This created friction, lost context (no device/browser info), and made it hard to track reports per org. The bug report system solves this by embedding a structured form directly in the dashboard with automatic metadata collection.
Architecture Overview
How It Works
1. User Flow
- User clicks "Report Bug" button in the top bar (between Help and Credits)
- A slide-in Sheet panel opens from the right with a smooth transition
- Metadata (email, userId, orgId, device, browser, page path) is auto-collected on open
- User fills in Summary, Details (max 2000 words), and Severity (Critical/High/Medium/Low)
- User optionally attaches up to 5 files (images/videos, max 5MB each) via drag-drop or paste
- On submit: form data + metadata + files sent as multipart/form-data to the API
- Success → toast + panel closes + form resets. Error → toast + panel stays open with data preserved.
2. Backend Flow
POST /bug-reportshitsverifyJwt→uploadMiddleware(multer, 5 files, 5MB limit) →submitBugReport- Controller validates required fields (summary, details, severity)
- Creates a
BugReportdocument in MongoDB with status "open" - Uploads each attachment to S3 at path:
{orgId}/{userId}/bugreport/{bugReportId}/{filename} - Updates the document with S3 keys
- Fires email notification to org admin (fire-and-forget) includes file attachments in the email
- Returns
201 { success: true, bugReportId }
3. Email Notification
- Looks up org admin via
Organization.ownerUID→User.email - HTML email includes: summary, severity (color-coded), reporter email, timestamp, attachment count
- Actual files are attached to the email (photos/videos viewable by the admin)
- Sent via BullMQ queue (3 retries with exponential backoff)
- Failure is logged but does NOT affect the API response
File Structure
backend/client_backend/src/
├── models/
│ └── BugReport.ts # Mongoose model + IBugReport interface
├── controllers/
│ └── bug-report.controller.ts # submitBugReport handler
├── routes/
│ └── bug-report.routes.ts # POST /bug-reports (JWT + multer)
├── services/
│ ├── bug-report-s3.service.ts # buildBugReportS3Key + uploadBugReportAttachment
│ └── bug-report-email.service.ts # sendBugReportNotification (with attachments)
├── middleware/
│ └── upload.middleware.ts # multer config (memory storage, 5MB, MIME filter)
└── app.ts # Route registration
app/src/
├── components/features/bug-report/
│ ├── BugReportButton.tsx # Top bar trigger button
│ ├── BugReportPanel.tsx # Sheet form (react-hook-form + zod)
│ ├── AttachmentUploader.tsx # Drag-drop + paste + validation
│ ├── metadataCollector.ts # Device/browser/page metadata
│ ├── bugReportSchema.ts # Zod schema (2000 word limit on details)
│ └── index.ts # Barrel export
├── services/
│ └── bugReport.service.ts # API client (FormData POST)
└── layouts/
└── SidebarLayout.tsx # Button + Panel integrationData Model
BugReport Collection
| Field | Type | Required | Default | Indexed | Description |
|---|---|---|---|---|---|
| summary | String | Yes | No | Brief bug description | |
| details | String | Yes | No | Detailed steps to reproduce | |
| severity | String | Yes | "medium" | No | critical/high/medium/low |
| attachments | String[] | No | [] | No | Array of S3 keys |
| reporterEmail | String | Yes | Yes | Reporter's email | |
| reporterUserId | String | Yes | Yes | Reporter's user ID | |
| orgId | String | Yes | Yes | Organization ID | |
| deviceInfo | String | No | "" | No | OS name/version |
| browserInfo | String | No | "" | No | Browser name/version |
| pagePath | String | No | "" | No | Page where bug was reported |
| status | String | No | "open" | No | open/in-progress/resolved/closed |
| createdAt | Date | Auto | No | Mongoose timestamps | |
| updatedAt | Date | Auto | No | Mongoose timestamps |
S3 Path Format
{bucket}/{orgId}/{userId}/bugreport/{bugReportId}/{filename}Example: oktivo-recordings/org_abc/user_xyz/bugreport/65f2a1b3c4d5e6f7/screenshot.png
API Reference
POST /bug-reports
Auth: JWT required
Content-Type: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
| summary | string | Yes | Bug title (min 1 char) |
| details | string | Yes | Description (min 1 char, max 2000 words) |
| severity | string | Yes | critical / high / medium / low |
| deviceInfo | string | No | Auto-collected OS info |
| browserInfo | string | No | Auto-collected browser info |
| pagePath | string | No | Auto-collected current page path |
| attachments | File[] | No | Max 5 files, 5MB each, image/video only |
Allowed MIME types: image/png, image/jpeg, image/gif, image/webp, video/mp4, video/webm
Responses:
| Status | Body | When |
|---|---|---|
| 201 | { success: true, bugReportId: "..." } | Report saved successfully |
| 400 | { success: false, error: "..." } | Validation failure |
| 500 | { success: false, error: "..." } | S3 upload or DB failure |
Error Handling
| Scenario | Frontend | Backend |
|---|---|---|
| Empty required field | Inline validation error (zod) | 400 response |
| File too large (>5MB) | Toast error, file rejected | 400 from multer |
| Invalid MIME type | Toast error, file rejected | 400 from multer |
| S3 upload failure | Error toast, form preserved | 500 response |
| DB save failure | Error toast, form preserved | 500 response |
| Email send failure | Not visible to user (fire-and-forget) | Logged, still returns 201 |
| Network error | Error toast, form data preserved, panel open |
Validation Rules
| Field | Frontend (Zod) | Backend (Controller) |
|---|---|---|
| summary | min 1 char | non-empty string |
| details | min 1 char, max 2000 words | non-empty string |
| severity | enum: critical/high/medium/low | must be in valid set |
| files | MIME whitelist + 5MB + max 5 files | multer fileFilter + limits |
UI/UX Details
- Location: Top bar button between Help and Credits in SidebarLayout
- Panel: Sheet component sliding in from the right side
- Transitions: Smooth slide animation (500ms open, 300ms close) with backdrop blur via
tw-animate-css - Word counter: Live "X / 2000 words" display below the Details textarea
- Attachments: Drag-and-drop zone + clipboard paste, image thumbnails, remove button per file
- Loading state: Submit button disabled with spinner during submission
- Success: Toast notification → panel auto-closes → form resets
- Error: Toast with error message → panel stays open → all form data preserved for retry
Dependencies Added
| Package | Purpose | Location |
|---|---|---|
| tw-animate-css | Tailwind v4 animation utilities (slide/fade) | app |
Configuration
No new environment variables required. Uses existing:
S3config (bucket, region, credentials) fromconfig/index.tsEmailconfig (Resend API key, default from) from email service initializationJWTmiddleware for authentication
Future Considerations
- Admin dashboard to view/manage/resolve bug reports
- Status update notifications back to the reporter
- Rate limiting on bug report submissions (prevent spam)
- File type expansion (PDF, logs)
- Bug report categories/tags for better organization