Important Days (Occasions) — Feature Doc
Status: [Live — April 2026]
Overview
The Important Days feature enables Leadmetrics to surface upcoming public holidays, cultural occasions, and observance days to both DMs and clients, and makes it one-click to generate a branded occasion post.
Three surfaces are active:
- Manage portal
/system/important-days— super admins curate the catalogue - DM portal
/occasions— DMs browse upcoming days and trigger posts per tenant - Dashboard widget — clients see the next 3 occasions with inline “Create post” buttons
Data Model
model ImportantDay {
id String @id @default(cuid())
name String // "Labour Day", "Diwali"
description String?
month Int // 1–12
day Int // 1–31
year Int? // null = recurring annually
isGlobal Boolean @default(false) // shows for all tenants
countries String[] // ["IN", "US"] — ISO 3166
platforms String[] // default platforms to suggest
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}Country matching: Tenant.country (existing field) is matched against ImportantDay.countries. isGlobal = true overrides — shown for all tenants regardless of country.
Seed data: 15 occasions loaded by packages/db/src/seed.ts — India-heavy (Republic Day, Gandhi Jayanti, Independence Day, Children’s Day, Holi, Ganesh Chaturthi, Dussehra, Diwali) + global (New Year, Valentine’s Day, Women’s Day, Earth Day, Labour Day, Christmas, New Year’s Eve).
Scheduler Job
Runs daily at 9:00 AM via the apps/servers/scheduler DB-poll architecture.
Handler: apps/servers/scheduler/src/handlers/occasion-reminders.ts
Logic:
- Find all
ImportantDaywheremonth + day = today + 4 days - For each day: find all active tenants whose
countrymatches (orisGlobal = true) - Per tenant: guard check — if any
ActivitywithcontentType = "occasion"and matching name was created this week, skip (dedup) - If not deduped: call
enqueueNotification()withtype: "occasion_reminder",templateSlug: "occasion-reminder", containingoccasionName,daysUntil,date
Scheduler task record: type: "occasion.daily.check", runs daily. Registered in task-runner.ts.
API Routes
Admin routes (/admin/v1/, requireSuperAdmin)
| Method | Route | Description |
|---|---|---|
GET | /admin/v1/important-days | List with ?month=&country=&active= filters |
POST | /admin/v1/important-days | Create |
PATCH | /admin/v1/important-days/:id | Update |
DELETE | /admin/v1/important-days/:id | Delete |
Tenant routes (/tenant/v1/, requireTenantUser)
| Method | Route | Description |
|---|---|---|
GET | /tenant/v1/important-days/upcoming | Next 30 days for tenant’s country |
POST | /tenant/v1/important-days/:id/create-post | Create occasion social post |
DM routes (/dm/v1/, requireDMAccess)
| Method | Route | Description |
|---|---|---|
GET | /dm/v1/important-days/upcoming?tenantId= | Next 30 days for tenant’s country |
POST | /dm/v1/important-days/:id/create-post | Create occasion social post { tenantId, platform, format? } |
Occasion Post Flow
Triggered from any surface:
DM clicks "Create post" (or client clicks from widget)
→ Activity created: deliverableType=social_post, contentType=occasion, dueDate=next occurrence
→ SocialPost created: contentType=occasion, status=queued
→ BullMQ job enqueued on social-post-writer
→ social-post-writer: festive prompt for occasion contentType
→ status: dm_review
→ DM approves
→ social-post-designer: SCENE_MAP["occasion"] visual
→ status: client_review
→ Client approves
→ social-publisher publishesWriter prompt (social-post-writer.worker.ts line ~145):
When contentType === "occasion":
- Opens with a festive greeting for the occasion name
- Keeps brand tone warm and celebratory
- Positions brand as wishing audience well
- Ends with relevant hashtags
Designer scene (SCENE_MAP[“occasion”]):
“Festive graphic composition with bold celebratory typography, brand colours prominent on a clean minimal background, warm and joyful atmosphere, occasion-themed visual elements, professional yet celebratory — suitable for greeting and brand awareness”
Dashboard Widget
File: apps/dashboard/src/app/(dashboard)/dashboard/UpcomingOccasionsCard.tsx
Shown in: RegularDashboard (stage ≥ 5 tenants only), after the PendingApprovalsCard.
Data fetched: getUpcomingOccasions(tenantId) in page.tsx — direct DB query (no API fetch), returns up to 3 occasions in the next 30 days sorted by daysUntil.
Server action: createOccasionPost(importantDayId, platform) in actions.ts.
UX:
- Card with violet border, CalendarDays icon header
- Each occasion shows name + urgency (“in 4 days”) + inline “Create post” button
- Click expands a platform dropdown + “Generate post” button
- On success: “Post queued” confirmation replaces the button
DM Portal Page
Route: /occasions (DM portal)
File: apps/dm/src/app/(dm)/occasions/page.tsx + OccasionsClient.tsx
Shows all upcoming occasions for the active tenant (next 30 days). Grid layout with search filter. Each card shows occasion name, date, description, urgency badge, and a platform dropdown + “Create post” button.
Proxy API routes:
apps/dm/src/app/api/important-days/route.ts(GET)apps/dm/src/app/api/important-days/[id]/create-post/route.ts(POST)
Files Reference
| File | Purpose |
|---|---|
packages/db/prisma/schema.prisma | ImportantDay model |
packages/db/src/seed.ts | 15 seeded occasions |
apps/api/src/routers/admin/important-days.ts | Admin CRUD |
apps/api/src/routers/tenant/important-days.ts | Dashboard/client API |
apps/api/src/routers/dm/important-days.ts | DM portal API |
apps/manage/src/app/(manage)/system/important-days/page.tsx | Manage list page |
apps/manage/src/app/(manage)/system/important-days/ImportantDaysClient.tsx | Manage client component |
apps/manage/src/app/actions/important-days.ts | Manage server actions |
apps/servers/scheduler/src/handlers/occasion-reminders.ts | Daily notification trigger |
apps/dashboard/src/app/(dashboard)/dashboard/UpcomingOccasionsCard.tsx | Dashboard widget |
apps/dashboard/src/app/(dashboard)/dashboard/actions.ts | Dashboard server action |
apps/dm/src/app/(dm)/occasions/page.tsx | DM occasions page |
apps/dm/src/app/(dm)/occasions/OccasionsClient.tsx | DM occasions client |
apps/dm/src/app/api/important-days/route.ts | DM proxy: GET |
apps/dm/src/app/api/important-days/[id]/create-post/route.ts | DM proxy: POST |
packages/agents/src/workers/social-post-writer.worker.ts | Occasion prompt (line ~145) |
packages/agents/src/workers/social-post-designer.worker.ts | SCENE_MAP[“occasion”] |