</>longpham.tech
All case studies
AI & AutomationConfidential

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}}
Async-first BullMQ + Redis pipeline — no email loss under burst load
LLM classification (OpenAI gpt-4o-mini) with Gemini fallback
Two-stage confirmation: admin approval then token-based provider confirm
Reply tracking via SMTP Message-ID + thread-ID dual-key matching

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
Email volume in the tens to hundreds per day for mid-size buildingsBullMQ workers on Redis 7 with exponential backoff and a dead-letter queueConfirmation tokens with a 48h TTL for provider repliesPostgreSQL 15 with connection pooling for intervention tracking

System Design

Architecture

100% · drag to pan · scroll to zoom
Rendering diagram…

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

NestJSTypeScriptTypeORMJWT + Passport

Data / Queue

PostgreSQL 15Redis 7BullMQ

AI / Email

OpenAI gpt-4o-miniGemini (fallback)Gmail APIMicrosoft Graph

Frontend

Nuxt 3Vue 3

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}}