AI Email Automation Platform
LLM-driven email triage for co-ownership property management — classifying maintenance requests and routing them to service providers through an async-first BullMQ pipeline with a two-stage confirmation audit trail.
- Client
- Confidential · PropTech
- Role
- {{TODO: e.g. Solution Architect / Lead Backend Engineer}}
- Period
- {{TODO: e.g. 2024–2025}}
Summary
Overview
The platform automates the email triage that property syndics (French co-ownership managers) handle by hand: tenant maintenance requests arrive by email, get classified by urgency and problem type, and are routed to a pre-configured pool of service providers — plumbers, electricians, locksmiths, roofers, elevator technicians.
The core design bet is reliability over speed. Every inbound email is persisted first, then processed asynchronously through a BullMQ pipeline so a burst of requests, a slow LLM call, or a provider outage never drops a message. An LLM extracts structured intent from unstructured text, and a two-stage confirmation flow keeps the property manager in control before any provider is committed.
Context
The Problem
Syndics triage maintenance email manually: reading each message, judging urgency, identifying the problem, finding the right provider, and chasing confirmations by reply. This is slow, error-prone, and opaque on cost — and an SLA miss on an urgent issue (a burst pipe, a broken lock) has real consequences for residents.
Constraints
- No email loss. A dropped maintenance request is a failed SLA; ingestion must be durable and retriable.
- Structured output from unstructured text. Downstream routing depends on reliable problem-type and urgency fields, not free-form prose.
- Cost discipline. LLM spend must stay bounded and attributable per email — no vector DB or RAG for a sub-1000/month volume.
- Human-in-the-loop compliance. A property manager must approve work before any provider is dispatched, with an auditable trail.
- Reply threading across providers. Gmail and Outlook do not preserve conversation semantics consistently, so provider confirmations must be matched robustly.
Scope
Requirements
Functional
- ▹Ingest email from Gmail / Outlook via OAuth (webhook push + scheduled polling)
- ▹Classify urgency (urgent / non-urgent) and problem type (water, electricity, breakage, locksmith, roofer, elevator)
- ▹Extract sender, property and issue from unstructured email text
- ▹Route to a pre-configured provider pool per problem type
- ▹Two-stage confirmation: admin approval then token-based provider confirmation
- ▹Follow-up information requests with reply tracking (Message-ID + thread-ID)
- ▹Cost tracking from estimated to actual per intervention
- ▹Admin dashboard: email volume, intervention SLA, cost analysis
Non-functional
- ▹No email loss — durable ingestion with async job retry and persistence
- ▹Analysis latency under 5s; no real-time requirement (SLA ~1h)
- ▹Bounded, attributable LLM cost with per-email token accounting
- ▹Handle bursts of tens to hundreds of emails/day per building
- ▹JWT auth, confirmation-token expiry, no PII in logs
System Design
Architecture
Email Ingest Layer
OAuth-based Gmail and Outlook sync behind a common interface (webhook push plus scheduled polling); every message is persisted with its Message-ID before any processing so nothing is lost.
Async Processing Pipeline
BullMQ workers on Redis deduplicate by Message-ID, extract sender info, and hand off to AI analysis — decoupling inbound bursts from downstream latency.
AiService (LLM Abstraction)
OpenAI gpt-4o-mini as primary with a Gemini fallback, using JSON mode for guaranteed structured output (problem type, urgency, summary) and per-call token logging for cost audit.
Two-Stage Confirmation
Admin approval creates an intervention in a wait-for-approval state, then a token-based email lets the provider confirm — keeping non-technical providers in the loop with an auditable trail.
Reply Tracking
Outbound provider requests are matched to inbound replies by SMTP Message-ID and thread-ID dual-key, driving intervention status transitions and cost reconciliation.
Stack
Technology Stack
Backend
Data / Queue
AI / Email
Frontend
Persistence
Data Model
- Email: raw message — from, subject, body, provider, message-id, thread-id
- AiAnalystResponse: LLM output — problem type, urgency, summary, extracted fields, token usage
- Intervention: work ticket — provider, status, estimated/actual cost, confirmation token + TTL
- SelectedProvider: per-problem-type provider preferences for routing
- InformationRequest: outbound provider requests with SMTP Message-ID for reply tracking
- Client: tenant/owner — name, email, address, account number
Reasoning
Key Decisions & Trade-offs
Async BullMQ pipeline instead of synchronous processing
trade-off
Adds a few seconds of latency, but guarantees retry, prevents email loss, and fairly absorbs bursts instead of overloading the request path.
gpt-4o-mini with Gemini fallback and JSON mode
trade-off
Slightly lower raw accuracy than a larger model, but a narrow, structured email domain plus JSON mode keeps parsing reliable and cost roughly a third of the alternative.
Two-stage confirmation instead of auto-dispatch
trade-off
Adds time to resolution, but the property manager approves before any provider is committed — required for legal control and giving a full audit trail.
No vector DB / RAG; keyword search only
trade-off
No historical-precedent retrieval, but at sub-1000-email/month volume simple search suffices and the token budget stays tight.
Message-ID + thread-ID dual-key reply matching
trade-off
Extra matching complexity, but Gmail and Outlook do not consistently preserve conversation semantics, so the dual key avoids silent reply drops.
Operations
Scaling & Reliability
Reliability: BullMQ exponential backoff (bounded retries, capped delay) with a dead-letter queue for forensics; every email is persisted before processing and deduplicated by a unique index on provider + message-id.
Graceful degradation: the LLM layer falls back from OpenAI to Gemini on rate limits, and defers to manual review if both are unavailable — analysis stalls, but nothing is lost.
Burst smoothing: the Redis queue absorbs traffic spikes without database lock-up; the polling scheduler scales linearly and workers drain the backlog as capacity frees.
Cost guardrails: a daily quota caps OpenAI calls with a budget-overrun alert and a manual pause, and per-call token logging enables showback per email.
What made this hard
Architect's Challenges
- →Guaranteeing no email loss under burst load with durable, retriable async ingestion
- →Turning unstructured tenant email into reliable structured intent via JSON-mode LLM output
- →Keeping LLM cost bounded and attributable without a vector DB or RAG
- →Matching provider replies across Gmail/Outlook where conversation semantics are inconsistent
- →Maintaining a defensible audit trail through two-stage human-in-the-loop confirmation
Results
Outcome
- ▹Provider email lock-in removed — OAuth abstraction makes a provider swap a single-module change
- ▹Structured, cost-audited LLM classification with per-email token showback
- ▹{{TODO: add real metrics — email-to-intervention latency, approval SLA, provider confirm rate, cost/intervention}}