AI-powered lease deadline monitoring with webhook alerts.
Transforms natural language lease clauses into structured deadline trackers.
Think of LeaseSentinel as a three-stage pipeline:
- Ingest → User pastes lease clause + webhook URL
- Extract → Gemini 1.5 Flash parses dates from natural language
- Monitor → Firestore stores the sentinel; cron job checks daily
- Alert → When
triggerDatearrives, webhook fires with payload
Time to Hello World: ~3 minutes
- Node.js 18+
- Google Cloud project with Firestore enabled
- Firebase Admin SDK credentials
git clone <repo-url>
cd lease-sentinel
npm installCreate .env.local with:
# Auth (NextAuth v5)
AUTH_SECRET="openssl rand -base64 32"
AUTH_GOOGLE_ID="your-google-oauth-client-id"
AUTH_GOOGLE_SECRET="your-google-oauth-secret"
RESEND_API_KEY="your-resend-api-key"
# AI
GOOGLE_API_KEY="your-gemini-api-key"
# Firebase Admin
FIREBASE_PROJECT_ID="your-project-id"
FIREBASE_CLIENT_EMAIL="[email protected]"
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"npm run devOpen http://localhost:3000, sign in with Google or Magic Link email, and create your first Sentinel.
LeaseSentinel follows a Deep Module design philosophy: each module has a simple interface but encapsulates significant complexity internally. This minimizes cognitive load: developers interact with clean APIs without needing to understand implementation details.
| Module | Interface | Hidden Complexity |
|---|---|---|
| AI | extractLeaseData(text) → {eventName, triggerDate} |
Prompt engineering, JSON parsing, error recovery |
| Database | getAdminDb() → Firestore |
Singleton caching, credential management, Timestamp serialization |
| Auth | auth() → Session |
Dual-config Edge/Node split, JWT strategy, Firestore adapter |
| Webhook | dispatchAlert(url, payload) → boolean |
AbortController timeout, error handling |
A new developer working on the dashboard doesn't need to understand:
- How Gemini prompts are structured
- Why there are two auth configuration files
- How Firestore Timestamps are serialized
They call createSentinel() and get back an ActionState. The complexity is encapsulated.
NextAuth v5's Edge middleware is incompatible with the Firestore adapter. LeaseSentinel solves this with a split configuration:
| File | Runtime | Purpose |
|---|---|---|
auth.config.ts |
Edge | Shared config (providers, callbacks) |
auth.ts |
Node.js | Full NextAuth + Firestore adapter |
proxy.ts |
Edge | Middleware using Edge-safe config |
See ADR-001 for the full architectural decision record.
src/
├── actions/ # Server Actions (CRUD operations)
│ ├── sentinel.actions.ts # Create/Delete sentinels
│ ├── fetch-actions.ts # Read operations
│ └── auth.actions.ts # Sign in/out wrappers
├── app/ # Next.js App Router
│ ├── api/auth/ # NextAuth route handlers
│ └── page.tsx # Dashboard
├── components/ # React UI (shadcn/ui based)
├── lib/ # Core utilities
│ ├── ai.ts # Gemini extraction logic
│ ├── firebase.ts # Admin SDK initialization
│ ├── webhook.ts # HTTP dispatch with timeout
│ └── date-utils.ts # ISO date calculations
└── models/
└── schema.ts # Zod schemas + ActionState types
All mutations return ActionState<T> instead of throwing:
interface ActionState<T = unknown> {
success: boolean;
message?: string;
errors?: Record<string, string[]>;
data?: T;
}Every Sentinel is tagged with userId (user's email). All queries filter by this field, enforcing data isolation without Firestore security rules.
dispatchAlert() uses AbortController with a 5-second timeout, preventing slow external services from blocking the batch job.
Serverless webhook orchestration layer handling multi-channel alert delivery.
How it Works:
-
Webhook Receiver : The scenario triggers when LeaseSentinel's cron job dispatches a POST request containing the sentinel payload (
eventName,triggerDate,notificationMethod,notificationTarget). -
Router Module : A conditional router parses the
notificationMethodfield and splits execution into three isolated branches, ensuring each notification type is processed independently without blocking. -
Slack Branch : Executes a custom HTTP request to Slack's
users.lookupByEmailAPI to resolve the user ID from the target email, then posts a formatted Block Kit message to the resolved DM channel. -
SMS Branch (Twilio) : Connects to Twilio's Programmable Messaging API to deliver time-sensitive deadline alerts via SMS to the configured phone number.
-
Email Branch (Gmail) : Handles SMTP delivery through Gmail's API with templated HTML formatting for professional, branded notifications.
Architecture Benefits:
- Decoupled : Notification logic lives outside the Next.js runtime, enabling independent scaling and iteration
- Fault-tolerant : Each branch executes in isolation; a Twilio outage doesn't block Slack delivery
- Observable : Make.com provides execution history, retry logic, and error alerting out of the box
npm run dev # Start dev server
npm run build # Production build
npm run lint # ESLint
npm run test # Vitest unit tests
npm run docs # Generate TypeDoc documentation| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| Auth | NextAuth v5 + Google OAuth + Resend |
| Database | Firebase Firestore |
| AI | Google Gemini 1.5 Flash |
| Validation | Zod v4 |
| Styling | Tailwind CSS v4 |
| Testing | Vitest + React Testing Library |
| Documentation | TypeDoc |
| Deployment | Vercel |
MIT