Engineering reference β€” v1 (2026)

Technical Documentation & Architecture

The complete source of truth for the MageTech Lead Intelligence platform: system architecture, technology stack, every module and function, every API input & output, role-permission matrix, key business flows and full configuration.

0
Frontend routes
0
REST endpoints
0
Roles
0
Permissions
Before you read

About this document

Who it is for, how it is organized, and why reading it saves hours of code archaeology.

🎯

What it contains

Everything needed to understand, run, extend and maintain the platform: architecture, stack, modules, endpoints with request/response shapes, business rules, security model and configuration.

πŸ‘₯

Who should read it

Developers (onboarding, integration, debugging), architects (design review, extension), QA (expected behaviors & status catalogs), product managers (feature map & capabilities), sysadmins (deployment & configuration).

🧭

How to use it

Read Architecture and Stack first. Jump to API for endpoint contracts, Modules for per-feature function detail, Flows for how data moves, and Reference for enums, security and configuration.

πŸ’‘

Why it's useful

One canonical reference instead of digging through source. It answers β€œwhat should happen here?”, β€œwhich endpoint does that?”, β€œwho is allowed?”, and β€œhow is this configured?” in seconds.

3-tier
React SPA Β· REST API Β· Postgres
JWT
Stateless Β· 60 min access
14
Permission constants
38
LeadDto fields
System architecture

How the system is put together

Three clean tiers. The browser talks only to the Spring Boot API; the API owns all rules; PostgreSQL (with PGVector for AI search) is the single source of truth. Every record belongs to an organization (tenant).

Presentation

Next.js Frontend

React 19 SPA on Next 16 App Router (port 3000). Dark corporate theme, inline forms, ⌘K command palette, pure-CSS charts.

  • lib/api.ts β€” fetch client + token refresh
  • 21 routes under src/app
  • 26+ UI primitives (shadcn/Base UI)
Application

Spring Boot API

Java 21, REST under /api/v1 (port 8081). JWT security, RBAC enforced in services, Flyway migrations, scheduled campaign engine.

  • 24+ controllers across domains
  • @Scheduled campaign poll (60 s)
  • OpenAPI + Actuator exposed
Data

PostgreSQL + PGVector

Transactional core (JPA/Hibernate) plus vector embeddings for knowledge-base similarity search. Multi-tenant via organization_id.

  • Migrations V1 β†’ V6+
  • PGvector(1536) embedding column
  • JSONB analysis & step payloads

Animated shuttle = one authenticated API call (browser β†’ API β†’ DB β†’ back)

Request lifecycle

What happens on every request

One end-to-end round trip, from keyboard to database.

1
Browser

Client call

Frontend builds the URL via lib/api.ts β†’ http://localhost:8081/api/v1/… and attaches Authorization: Bearer <accessToken> (from localStorage).

2
Edge

Security filter chain

Spring Security (stateless, CSRF off) permits public paths (/auth/register|login|refresh, /webhooks/**, Swagger, Actuator health) and authenticates everything else.

3
Auth

JWT validation

JwtAuthenticationFilter parses the Bearer token, checks signature + expiry, and loads authorities: ROLE_<ROLE> plus each granted PERM_<PERMISSION>.

4
Controller

Routing & validation

The @RestController maps path, binds and validates the DTO, then forwards to the service β€” controllers stay thin.

5
Service

Authorization + business rules

Services explicitly check permissions (e.g. LEADS_WRITE). All domain rules live here: role promotions, campaign guards, reply auto-suppression.

6
Repository

Persistence

Spring Data JPA repositories, scoped by organization_id, hit PostgreSQL; @Transactional keeps operations atomic and audit events are recorded.

7
Response

Back to the client

DTOs (never entities) return as JSON. Errors use one shape: { error, message, timestamp, status }. On a 401, lib/api.ts transparently refreshes and retries once.

Technology stack

Versions, libraries & configuration

Exact versions as declared in backend/pom.xml and frontend/package.json.

🧩

Backend β€” Spring Boot 3

  • β€’ Java 21 Β· Maven mvnw.cmd
  • β€’ Spring Web, Security, Data JPA, Validation, Actuator
  • β€’ PostgreSQL + PGVector (embeddings)
  • β€’ Flyway migrations V1–V6
  • β€’ Apache POI + Commons CSV (import parsing)
  • β€’ OpenAPI / Swagger UI
  • β€’ Optional OpenAI (gpt-4o-mini, text-embedding-3-small)
  • β€’ @Scheduled campaign engine (60 s poll)
βš›οΈ

Frontend β€” Next.js 16

  • β€’ Next 16.3.5 App Router Β· Turbopack
  • β€’ React 19.2.8 Β· TypeScript ^5 (strict)
  • β€’ Tailwind CSS v4 (CSS-first, no config file)
  • β€’ shadcn/ui + @base-ui/react primitives
  • β€’ lucide-react icons Β· cmdk (⌘K palette)
  • β€’ Custom fetch client (lib/api.ts), no React Query
  • β€’ AuthProvider context + localStorage tokens
  • β€’ Charts hand-built in pure CSS (no chart lib)
πŸ–₯

Runtime & infrastructure

  • β€’ Backend: http://localhost:8081 (SERVER_PORT)
  • β€’ Frontend dev: http://localhost:3000
  • β€’ Apache httpd on 8080 (leave running)
  • β€’ CORS: backend allows localhost:3000
  • β€’ Env: DB_PASSWORD, JWT_SECRET, NEXT_PUBLIC_API_URL
  • β€’ Restart backend via mvnw.cmd spring-boot:run
API reference

Every endpoint, input & output

All endpoints live under /api/v1. Paginated lists return PageResponse<T> = { items, totalItems, totalPages, currentPage, pageSize }; plain collections return T[]. Errors always come as { error, message, timestamp, status }.

MethodEndpointPurposeInput β†’ Output
POST/auth/registerCreate org + first user{organizationName, firstName, lastName, email, password} β†’ 201 AuthResponse. Creates the workspace Owner.
POST/auth/loginSign in{email, password} β†’ AuthResponse (user, accessToken, refreshToken, expiresInSeconds, tokenType).
POST/auth/refreshRotate tokens{refreshToken} β†’ new AuthResponse. Refresh TTL 7 days.
GET/auth/meCurrent profile→ UserDto.
GET/usersList members→ UserDto[].
POST/usersCreate memberCreateUserRequest β†’ 201 UserDto. OWNER/ADMIN only; only OWNER may grant OWNER/ADMIN.
PUT/users/{id}Update memberUpdateUserRequest (incl. role) β†’ UserDto. Cannot change your own role/active.
DELETE/users/{id}Deactivate member→ 204. Guards: no self-deactivation, last owner protected.
MethodEndpointPurposeInput β†’ Output
GET/leadsPaged lead list?page,size,q,country,industryId,status,emailStatus,tagId,segmentId,companyId,leadSourceId,fromDate,toDate β†’ PageResponse<LeadDto> (38 fields, sort createdAt DESC).
GET/leads/{id}Lead detail→ LeadDto (technologies, tags, segments included).
POST/leadsCreate leadCreateLeadRequest (needs email OR companyName; duplicate email β†’ 400) β†’ 201 LeadDto.
PUT/leads/{id}Update leadUpdateLeadRequest (all optional) β†’ LeadDto.
DELETE/leads/{id}Delete lead→ 204. Blocked 409 while enrolled in active campaigns.
POST/leads/{id}/tags/{tagId}Add tag→ LeadDto. DELETE variant removes.
POST/leads/{id}/segments/{segmentId}Add to segment→ LeadDto. DELETE variant removes.
GET/leads/{id}/rawRaw import payloads→ LeadRawDataDto[].
GET/leads/{id}/campaignsCampaign membership→ CampaignRefDto[] (drives the delete-guard UI).
ALL/tags, /lead-sources, /segmentsTags & segments CRUDGET list / POST create / PUT (tags) / DELETE β†’ respective DTOs.
MethodEndpointPurposeInput β†’ Output
GET/companiesPaged companies?page,size,q β†’ PageResponse<CompanyDto>.
GET/companies/{id}Company detail→ CompanyDetailDto = company + domains[] + technologies[] + leadCount.
POST/companiesUpsert companyCompanyUpsertRequest β†’ 201 CompanyDto (find-or-create by domain β†’ name).
PUT/companies/{id}Update companyCompanyUpsertRequest β†’ CompanyDto.
DELETE/companies/{id}Delete company→ 204. Blocked 409 while leads are linked.
GET/companies/{id}/campaignsCampaign membership→ CampaignRefDto[].
GET/meta/industries · /meta/technologies · /meta/countriesReference data→ industries/technologies as {id,name[,category]}, countries as strings.
GET/meta/statusesEnum catalog→ {leadStatuses[], emailStatuses[]} — powers filter dropdowns.
MethodEndpointPurposeInput β†’ Output
POST/imports/uploadUpload CSV/XLSXmultipart file (xlsx/csv, ≀50 MB) β†’ ImportBatchDto (status PARSED). Max 200k rows.
GET/importsImport history→ PageResponse<ImportBatchDto>.
GET/imports/{id}/mappingGet column mapping→ MappingResponse (sourceColumns, targetFields, suggestedMapping, unmappedSourceColumns).
PUT/imports/{id}/mappingSave mapping{column→field} → MappingResponse; batch → MAPPED.
GET/imports/{id}/previewPreview rows?offset,limit (limit ≀ 500) β†’ PreviewResponse (columns + mapped rows).
GET/imports/{id}/duplicatesDuplicate review→ DuplicateResponse — groups keyed by DB lead with suggested action; batch → DUPLICATE_REVIEW.
POST/imports/{id}/confirmCommit import{mapping, duplicateDecisions} with actions IMPORT / MERGE / IGNORE β†’ ImportResult (imported, merged, ignored, error, invalidEmail counts).
GET/imports/{id}/errorsRow errors→ PageResponse<ImportErrorDto>.
DELETE/imports/{id}Cancel import→ ImportBatchDto (CANCELLED).
MethodEndpointPurposeInput β†’ Output
GET/campaignsList campaigns?page,size,status β†’ PageResponse<CampaignDto> (sort updatedAt DESC).
POST/campaignsCreate campaignCampaignRequest {name, description, sequence[], targetFilter} β†’ CampaignDto (DRAFT).
PUT/campaigns/{id}Update campaignCampaignRequest β†’ CampaignDto. Blocked while ACTIVE.
DELETE/campaigns/{id}Delete campaign→ 204. Blocked while ACTIVE.
POST/campaigns/{id}/startActivateRequires β‰₯ 1 step; DRAFT β†’ ACTIVE + startedAt.
POST/campaigns/{id}/pausePauseACTIVE β†’ PAUSED.
POST/campaigns/{id}/resumeResumePAUSED β†’ ACTIVE.
POST/campaigns/{id}/completeCompleteACTIVE β†’ COMPLETED + completedAt.
POST/campaigns/{id}/leadsAdd leads{leadIds[], filter} β†’ {added, skipped}. Leave ACTIVE to edit.
GET/campaigns/{id}/leadsEnrolled leads→ PageResponse<CampaignLeadDto> (per-lead step + status).
GET/campaigns/{id}/metricsHealth metrics→ CampaignMetrics {totalLeads, pending, sent, opened, replied, bounced, unsubscribed, failed, skipped}.
DELETE/campaigns/{id}/leads/{cid}Remove enrollee→ 204.
ALL/templatesEmail template CRUDGET/POST/PUT/DELETE + /duplicate + /preview (missing variables) + /generate (AI draft from a lead).
MethodEndpointPurposeInput β†’ Output
GET/messagesOutbound messages?campaignId,leadId β†’ PageResponse<EmailMessageDto> (sort sentAt DESC).
GET/repliesInbound replies→ PageResponse<ReplyDto>.
POST/repliesRecord reply (manual)ReplyCreateRequest β†’ ReplyDto. Triggers suppression + campaign/lead updates + maybe opportunity.
POST/webhooks/emailsEmail webhook (reply)WebhookEmailRequest matched to an outbound message by providerMessageId / inReplyTo β†’ ReplyDto. 400 if unmatched.
POST/webhooks/emails/openOpen event{providerMessageId} β†’ {ok, messageId}; marks OPENED.
POST/webhooks/emails/bounceBounce event{providerMessageId} β†’ {ok, messageId}; marks BOUNCED + suppresses.
GET/suppressionSuppression list?page,size β†’ PageResponse<SuppressionDto>.
POST/suppressionAdd suppression{emails[], reason} β†’ SuppressionDto.
DELETE/suppression/{id}Remove entry→ 204.
ALL/kb/documentsKnowledge docs CRUDGET/POST/PUT/DELETE β†’ KnowledgeDocumentDto; embedded on create/update.
POST/kb/searchVector search{query, limit} β†’ KnowledgeDocumentDto[], ordered by embedding cosine distance.
POST/kb/askGrounded Q&A{question, leadId?, companyId?} β†’ AskResponse {answer, sources[], model}.
MethodEndpointPurposeInput β†’ Output
GET/crm/opportunitiesOpportunity list?stage β†’ PageResponse<OpportunityDto>.
GET/crm/opportunities/pipelinePipeline summary→ PipelineSummary (per-stage count + value + totals).
ALL/crm/opportunities[/{id}]Opportunity CRUDPOST/PUT/DELETE β†’ OpportunityDto; stage changes write events + sync lead status.
GET/crm/opportunities/{id}/eventsRecorded events→ OpportunityEventDto[] (CREATED / STAGE_CHANGE / NOTE).
ALL/crm/meetings[/{id}]Meeting CRUDGET + /upcoming; POST/PUT/DELETE β†’ MeetingDto (PENDING/CONFIRMED/CANCELLED/DONE).
ALL/crm/proposals[/{id}]Proposal CRUDProposalRequest incl. line items. Editable only while DRAFT; edits bump version.
POST/crm/proposals/{id}/statusProposal status{status} β€” SENT β†’ opp PROPOSAL_SENT; ACCEPTED β†’ opp WON 100%; REJECTED β†’ opp LOST 0%.
ALL/abm/accounts[/{id}]ABM targets CRUDPOST/PUT/DELETE β†’ AbmAccountDto (tier TARGET / KEY / STRATEGIC).
GET/abm/suggestionsScored suggestions?limit=N β†’ AbmSuggestionDto[] ranked by fitScore.
MethodEndpointPurposeInput β†’ Output
POST/ai/leads/{leadId}/intelligenceRun AI analysisβ†’ LeadIntelligenceResult: score (0–100), summary, signals[], recommendedServices[], suggestedAngle, riskNotes[], model, analyzedAt.
GET/ai/leads/{leadId}/intelligenceLatest analysis→ newest result, or computes one on demand.
GET/ai/analysesAnalysis history→ PageResponse<AiAnalysis>.
GET/analytics/dashboardDashboard KPIs→ DashboardStats: KPIs + byStatus/byCountry/byIndustry/byTechnology + recentImports.
GET/integrations/statusConnector status→ {provider → boolean} (HUBSPOT / SALESFORCE / SIMULATED).
POST/integrations/{provider}/syncPush lead(s){leadIds?} (default top 100 by org) β†’ {provider, pushedLeads}.
GET/organizations/meWorkspace profile→ org details. PUT {name} → renamed org (ORG_WRITE).
ALL/magentech-services[/{id}]Services catalogGET ?activeOnly=true; POST/PUT/DELETE β†’ MageServiceDto.
ALL/playbooks[/{id}]Sales playbooksPlaybookRequest with steps [{title, guidance, templateId?, waitDays}] β†’ PlaybookDto.
Functional detail

Every module β€” what it does, what goes in, what comes out

Per-module purpose, features and explicit input β†’ output contracts. This is the β€œin and out” reference for features.

πŸ“Š Dashboard & Analytics

ReadGET /analytics/dashboard

Single aggregation endpoint powering the home screen, computed live from lead/company tables. Returns an empty structure when there is no data.

Input
  • Authenticated org (no parameters)
  • Read permission implied
Output
  • KPIs: totalLeads, validEmails, invalidEmails, suppressedEmails, totalCompanies, importedLeads, duplicateLeads
  • Distributions: byStatus, byCountry, byIndustry, byTechnology
  • recentImports (last 30 days, daily)

πŸ‘€ Auth & Identity

Public/auth/*

Registration bootstraps one organization plus the founding Owner. Login issues a JWT access token (60 min) and a 7-day refresh token. Deactivated users cannot sign in.

Input
  • register: org name, profile, email, password (10–100 chars)
  • login: email + password (BCrypt strength 12)
Output
  • AuthResponse: UserDto + accessToken + refreshToken + expiresInSeconds
  • JWT claims: sub=userId, org, role

πŸ§‘β€πŸ’Ό Team & Roles

Admin/users Β· /organizations

Member lifecycle: invite, change role, deactivate. Hard rules: only OWNER creates/promotes OWNER/ADMIN; no self-deactivation; the last active owner is always protected.

Input
  • CreateUserRequest (email, name, role)
  • UpdateUserRequest (role / active)
Output
  • UserDto list / single
  • Org rename via PUT /organizations/me

πŸ“‡ Leads

Core/leads

The central record: a 38-field DTO spanning contact, employment, company, location, enrichment (technologies, SEO), tags, segments and pipeline status. Delete is guarded against active-campaign enrollment.

Input
  • CreateLeadRequest β€” requires email OR companyName
  • Filters: q, country, industryId, status, emailStatus, tagId, segmentId, companyId, fromDate…
Output
  • LeadDto (38 fields) or PageResponse
  • Raw import payloads and campaign memberships

🏒 Companies

Core/companies

Account profiles de-duplicated by domain, enriched with technology signals and lead counts. Cannot be deleted while leads still reference it.

Input
  • CompanyUpsertRequest (profile, location, sizing, insights)
Output
  • CompanyDto list
  • CompanyDetailDto: + domains[], technologies[] (with source), leadCount

πŸ“₯ Import Pipeline

Wizard/imports

Guided 5-stage wizard: upload β†’ map β†’ preview β†’ review duplicates β†’ confirm. Parses xlsx/csv (≀50 MB, ≀200k rows), auto-suggests the column mapping, and produces a complete audit trail of imported / merged / ignored / errored rows.

Input
  • Multipart file (xlsx/csv)
  • Column mapping {sourceβ†’target}
  • Per-row duplicate decisions: IMPORT | MERGE | IGNORE
Output
  • ImportResult: totalRows, importedRows, mergedRows, ignoredRows, errorRows, invalidEmailRows
  • Row-level ImportError list
  • New leads land as IMPORTED; merged rows are marked duplicate and backfill missing fields

πŸš€ Campaigns

Scheduled/campaigns

Multi-step email campaigns with template sequences and wait days. A 60-second scheduled poll advances each active campaign's due pending leads. Editing / deleting / adding leads is locked while ACTIVE (pause first).

Input
  • CampaignRequest {name, description, sequence[{templateId, waitDays}], targetFilter}
  • State actions: start / pause / resume / complete
  • Add leads via leadIds or a target filter
Output
  • CampaignDto + metrics {totalLeads, pending, sent, opened, replied, bounced, unsubscribed, failed, skipped}
  • CampaignLeadDto[] per-enrollee progress

βœ‰οΈ Email Engine β€” Messages, Replies, Suppression

Delivery/messages Β· /replies Β· /webhooks Β· /suppression

Outbound sends run through a provider abstraction (default SIMULATED). Inbound replies and open/bounce events arrive via webhooks and update the whole graph: reply β†’ suppress sender, mark campaign/lead REPLIED, and auto-create an opportunity.

Input
  • Webhook events: emails (reply), emails/open, emails/bounce
  • Manual ReplyCreateRequest
  • Suppression {emails[], reason}
Output
  • EmailMessageDto / ReplyDto records
  • Suppression entries (sources: MANUAL, BOUNCE, COMPLAINT, UNSUBSCRIBE, IMPORT)
  • Lead β†’ REPLIED; opportunity auto-created when enabled

πŸ€– AI Intelligence

LLM/ai

Per-lead analysis: an LLM path (gpt-4o-mini, temperature 0.3, strict JSON) with a deterministic rule-based fallback so scoring always works even without a key. Results are persisted as AiAnalysis (JSONB) and drive the score badges (0–39 low, 40–69 medium, 70–84 good, 85–100 excellent).

Input
  • leadId (optionally re-run)
  • Fallback rules: valid email +30, website +10, employees +10, industry +10, country +10, tech +10, SEO +10, commerce tech +10
Output
  • LeadIntelligenceResult: score, summary, signals[], recommendedServices[] (top 5 with score & reasoning), suggestedAngle, riskNotes[], model, analyzedAt

πŸ“š Knowledge Base

RAG/kb

Documents are embedded (OpenAI text-embedding-3-small, 1536 dims; deterministic hash fallback) into PGVector. Search runs cosine similarity (ORDER BY embedding <=> vector); ask retrieves top-4 docs and returns a grounded answer with sources.

Input
  • Document {title, content, sourceType, tags}
  • search {query, limit}
  • ask {question, leadId?, companyId?}
Output
  • KnowledgeDocumentDto (includes distance)
  • AskResponse: answer, sources[], model

πŸ’Ό CRM β€” Opportunities, Meetings, Proposals

Pipeline/crm

Full pipeline: opportunities with stage / value / probability / expected close, activity events, meetings (PENDING β†’ CONFIRMED β†’ DONE), and proposals with line items. Stage and proposal changes cascade into lead-status synchronization.

Input
  • OpportunityRequest (stage, value, owner, nextAction…)
  • Proposal {status} transitions; meeting upserts
Output
  • OpportunityDto / PipelineSummary / events[]
  • Cascade: WON β†’ lead WON Β· LOST β†’ lead LOST Β· ACCEPTED β†’ opp WON (100%)

🎯 ABM

Accounts/abm

Account-based marketing: target accounts tiered TARGET / KEY / STRATEGIC with budgets, owners, and AI suggestions ranked by the same fit-scoring signals.

Input
  • AbmForm {company, tier, budget, score, notes}
  • suggestions?limit=N
Output
  • AbmAccountDto CRUD
  • AbmSuggestionDto[] {company, industry, leadCount, technologyCount, fitScore}

πŸ”Œ Integrations

Connectors/integrations

SPI-based CrmConnector with three providers. HUBSPOT and SALESFORCE activate when their tokens are configured; SIMULATED is always available for demos. Sync pushes lead data out to the CRM.

Input
  • {provider}/sync with optional leadIds (default top 100)
Output
  • {provider: available} status map
  • {provider, pushedLeads} sync result

🧩 Templates, Playbooks & Services Catalog

Content/templates Β· /playbooks Β· /magentech-services

Reusable content layer: email templates with {{variables}} and AI generation, sales playbooks with guided steps and wait days, and the services catalog the AI uses to recommend offerings.

Input
  • TemplateRequest {name, category, subject, body, variables}
  • PlaybookRequest with steps
  • template/generate {leadId, tone, goal, language}
Output
  • TemplatePreview {subject, body, missingVariables[]}
  • AI-drafted EmailTemplateDto
  • PlaybookDto / MageServiceDto CRUD
Business flows

How data actually moves

End-to-end behaviors: import, lead lifecycle, campaign execution, replies and AI scoring.

1 Β· Import pipeline stages

UPLOADED→ PARSED→ MAPPED→ VALIDATED→ DUPLICATE_REVIEW→ CONFIRMED→ COMPLETED
FAILED CANCELLED

Duplicate detection order: email β†’ LinkedIn URL β†’ company domain β†’ company + contact. Parsers: Apache POI (xlsx) + Commons CSV; maximum 200k rows per file.

2 Β· Lead lifecycle

IMPORTED→ NEW→ VALIDATED→ CONTACTED→ REPLIED→ QUALIFIED→ MEETING→ PROPOSAL→ NEGOTIATION
WON βœ“ LOST βœ— ARCHIVED

Status is advanced automatically: import β†’ IMPORTED; campaign send β†’ CONTACTED; reply β†’ REPLIED; CRM stages sync QUALIFIED / PROPOSAL / NEGOTIATION / WON / LOST back to the lead.

3 Β· Campaign execution (scheduled pump)

⏲
Every 60 s

Scheduler wakes

@Scheduled(fixedDelayString="${app.campaign.poll-millis:60000}", initialDelay=15000) scans ACTIVE campaigns.

πŸ”
Due filter

Find due enrollees

Pending CampaignLeads whose nextStepAt <= now and current step index is below the sequence length.

βœ‰οΈ
Send

Deliver the current step

Missing template or email β†’ FAILED; suppressed or empty recipient β†’ SKIPPED; success β†’ SENT, step + 1, nextStepAt = now + waitDays Β· 86400s.

🏁
Finish

Sequence exhausted

After the last step is sent, nextStepAt is cleared and the enrollee becomes idle.

4 Β· Reply processing (webhook β†’ effects)

Webhook / manual POST→ Match outbound msg→ Create Reply→ Suppress sender→ CampaignLead → REPLIED→ Lead → REPLIED→ Auto-create Opportunity

Auto-opportunity only when enabled (app.crm.auto-create-opportunity-on-reply=true) and no open opportunity exists for the lead yet.

5 Β· AI scoring (the rule fallback guarantees an answer)

analyzeLead(leadId)β†’ LLM configured?
Yes β†’ gpt-4o-mini⇄ Strict JSON parse
No / parse fail β†’ rulesβ†’ Score 0–100β†’ Persist AiAnalysisβ†’ Return result

6 Β· CRM stage β†’ default probability & lead sync

Opportunity stageDefault probabilityLead status sync
NEW10%β€”
QUALIFIED20%β†’ QUALIFIED
PROPOSAL_SENT40%β†’ PROPOSAL
NEGOTIATION60%β†’ NEGOTIATION
WON100%β†’ WON
LOST0%β†’ LOST
Reference

Enums, security, database & configuration

The canonical value catalog, role-permission matrix, data model and run configuration.

βš™οΈ

Status enums

EnumValues
LeadStatusIMPORTED Β· NEW Β· VALIDATED Β· CONTACTED Β· REPLIED Β· QUALIFIED Β· MEETING Β· PROPOSAL Β· NEGOTIATION Β· WON Β· LOST Β· ARCHIVED
EmailStatusUNKNOWN Β· VALID Β· INVALID Β· RISK Β· SUPPRESSED Β· DUPLICATE Β· BOUNCED Β· UNSUBSCRIBED
CampaignStatusDRAFT Β· ACTIVE Β· PAUSED Β· COMPLETED
CampaignLeadStatusPENDING Β· SENT Β· OPENED Β· REPLIED Β· BOUNCED Β· UNSUBSCRIBED Β· FAILED Β· SKIPPED
MessageStatusSENT Β· DELIVERED Β· OPENED Β· CLICKED Β· BOUNCED Β· FAILED
ImportStatusUPLOADED Β· PARSED Β· MAPPED Β· VALIDATED Β· DUPLICATE_REVIEW Β· CONFIRMED Β· COMPLETED Β· FAILED Β· CANCELLED
OpportunityStageNEW Β· QUALIFIED Β· PROPOSAL_SENT Β· NEGOTIATION Β· WON Β· LOST
ProposalStatusDRAFT Β· SENT Β· ACCEPTED Β· REJECTED
MeetingStatusPENDING Β· CONFIRMED Β· CANCELLED Β· DONE
SuppressionSourceMANUAL Β· BOUNCE Β· COMPLAINT Β· UNSUBSCRIBE Β· IMPORT
AbmTierTARGET Β· KEY Β· STRATEGIC
RoleOWNER Β· ADMIN Β· SALES_MANAGER Β· SALES_USER Β· VIEWER
πŸ›‘οΈ

Roles & permissions

5 roles Γ— 14 permission constants. JWT authorities: ROLE_<ROLE> + one PERM_<PERMISSION> each.

RoleGranted permissions
OWNERAll (incl. USERS_WRITE, ORG_WRITE, promotes admins)
ADMINAll permissions
SALES_MANAGERLEADS_R/W/D Β· IMPORTS_R/W Β· COMPANIES_R/W Β· SEGMENTS_R/W Β· USERS_READ Β· ORG_READ
SALES_USERLEADS_R/W Β· IMPORTS_READ Β· COMPANIES_READ Β· SEGMENTS_R/W Β· USERS_READ
VIEWERLEADS_READ Β· IMPORTS_READ Β· COMPANIES_READ Β· SEGMENTS_READ Β· USERS_READ Β· ORG_READ

Constants: LEADS_READ Β· LEADS_WRITE Β· LEADS_DELETE Β· IMPORTS_READ Β· IMPORTS_WRITE Β· COMPANIES_READ Β· COMPANIES_WRITE Β· SEGMENTS_READ Β· SEGMENTS_WRITE Β· USERS_READ Β· USERS_WRITE Β· ORG_READ Β· ORG_WRITE Β· AUDIO_READ.

  • β€’ Only OWNER may grant OWNER/ADMIN roles
  • β€’ Cannot deactivate or demote yourself
  • β€’ The last active owner is always protected
  • β€’ Deactivated users cannot sign in
πŸ—„οΈ

Database & migrations

Multi-tenant JPA: every table carries organization_id. Base hierarchy: BaseEntity (id, created_at, updated_at) β†’ BaseOrgEntity (+ organization_id). Embeddings live in a PGvector(1536) column; AI results and campaign steps use JSONB.

MigrationContent
V1__core.sqlorganizations, users, leads, tags, companies, raw data, reference data
V2__phase2_services_ai.sqlmage services + AI analysis tables
V3__phase3_campaigns.sqltemplates, campaigns, campaign_leads, messages, replies, suppression
V4__phase4_crm.sqlopportunities, events, meetings, proposals
V5__phase5_ai_kb_abm.sqlknowledge documents + PGVector, ABM accounts
V6__fix_opportunity_event_updated_at.sqladds updated_at to opportunity_event
🧾

Configuration

KeyDefault / value
SERVER_PORT8081
DB_PASSWORDPostgreSQL password (env)
JWT_SECRETβ‰₯ 32 bytes (env); access 60 min, refresh 7 days
app.campaign.poll-millis60000 (initial delay 15000)
app.crm.auto-create-opportunity-on-replytrue
app.integrations.hubspot-token / salesforce-*enable real connectors
app.uploads.dirper-org import file storage
ai.* (OpenAI)gpt-4o-mini Β· text-embedding-3-small
NEXT_PUBLIC_API_URLhttp://localhost:8081

Run backend: mvnw.cmd spring-boot:run with JAVA_HOME (JDK 21), DB_PASSWORD, JWT_SECRET and SERVER_PORT=8081 set.

Pricing & billing model

Usage-aware plans

Pricing is modeled as a workspace that bundles capacity β€” leads, AI analyses (scoring, recommendations, email generation, KB Q&A), enrichment and campaign contacts β€” rather than purely per-seat licensing. Users are simply a secondary constraint. This section documents the proposed plan tiers, the intended billing & subscription module, and migration approach.

Prices in USD. Annual plans billed once per year. CTA buttons are placeholders pending the Billing module.

Starter

$49/mo Β· annual $39/mo
Import pipeline + enrichment (limited)

$49/mo

2
Users
2.5K
Leads
250
AI / mo
MOST POPULAR

Growth

$149/mo Β· annual $119/mo
Full AI outreach + campaigns + CRM + HubSpot sync

$149/mo

5
Users
15K
Leads
2K
AI / mo

Professional

$399/mo Β· annual $319/mo
Adds Salesforce + API + ABM

$399/mo

15
Users
50K
Leads
10K
AI / mo

Enterprise

Custom pricing
SSO Β· dedicated tenant Β· SLA Β· custom limits

Custom/ contact sales

∞
Users
∞
Leads
∞
AI usage

Plan tiers β€” feature matrix

CapabilityStarterGrowthProfessionalEnterprise
Users2515Unlimited
Lead capacity2,50015,00050,000Custom
AI analyses / mo2502,00010,000Custom
AI emails / mo1001,0005,000Custom
Active campaigns320UnlimitedUnlimited
Campaign leads1,00010,00050,000Custom
Lead imports / CSV / Excelβœ“βœ“βœ“βœ“
Email validationβœ“βœ“βœ“βœ“
Company enrichmentLimitedβœ“βœ“βœ“
AI recommendationsβœ“βœ“βœ“βœ“
Knowledge base10 docs100 docs1,000 docsCustom
AI knowledge Q&Aβœ“βœ“βœ“βœ“
CRM β€” opportunities / meetings / proposalsβœ“βœ“βœ“βœ“
ABMβ€”βœ“βœ“βœ“
HubSpot integrationβ€”βœ“βœ“βœ“
Salesforceβ€”β€”βœ“βœ“
API accessβ€”β€”βœ“βœ“
Advanced analyticsβ€”βœ“βœ“βœ“
Audit trailβœ“βœ“βœ“βœ“
Priority supportβ€”βœ“βœ“Dedicated
SSOβ€”β€”β€”βœ“
Dedicated tenantβ€”β€”β€”βœ“

Add-ons (usage spikes, no forced upgrade)

Add-onTier 1Tier 2Tier 3
Additional leads+10,000 Β· $29+50,000 Β· $99+100,000 Β· $169
AI credits+1,000 analyses Β· $19+5,000 analyses Β· $69+10,000 analyses Β· $119
Enrichment+5,000 enrichments Β· $29+25,000 enrichments Β· $99+100,000 enrichments Β· $299
Campaign contacts+10,000 contacts Β· $19+50,000 contacts Β· $69+100,000 contacts Β· $119

Local pricing β€” India (INR)

GST extra where applicable.

PlanMonthly (INR)Annual (INR)
Starterβ‚Ή3,999β‚Ή39,990 / year
Growthβ‚Ή11,999β‚Ή119,990 / year
Professionalβ‚Ή31,999β‚Ή319,990 / year
EnterpriseCustomCustom

πŸš€ 14-day free trial

No credit card required. Seeded with a fixed quota, no plan selected.

100
Leads
50
AI analyses
25
AI emails
1
Campaign
1
User
Basic
CRM

🧾 Billing & Subscription module

⚠ PROPOSED β€” NOT YET IMPLEMENTED

Not present in the current codebase. Recommended future module so the documented plans can be enforced and invoiced. The current system has no plan limits β€” enrichment, AI quotas and campaign allowances are not yet gated.

1 Β· Subscription

organization_id β†’ plan tier (STARTER / GROWTH / PROFESSIONAL / ENTERPRISE), billing period (monthly / annual), start & renewal dates, soft limit overrides for add-ons.

➜

2 Β· Usage Meter

Counters per organization + period: AI analyses (scoring, recommendations, emails, KB Q&A), imported leads, enrichment calls, campaign contacts. Incremented at write-time in existing services.

➜

3 Β· Invoices

Records invoice, add-on line items (override allowances) and amount. Optional webhook to a payment provider; invoice stores provider reference.

➜

4 Β· Limit Enforcement

Checked inside campaign scheduler, AI services, import pipeline and CRM actions. Hard vs soft limits: soft β†’ warn, hard β†’ block with 402-style error payload.

➜

Consumers

Leads (import + enrichment) Β· AI (scoring / email / Q&A) Β· Campaigns (contacts, sends). Enforcement surfaces in the API as QUOTA_EXCEEDED.

  • β€’ Migration: single Flyway migration V7__billing.sql β†’ subscriptions, usage_counters, invoices, plan_tiers (seeded reference data).
  • β€’ Authz: read via existing multi-tenant pattern (BaseOrgEntity); plan changes restricted to OWNER.
  • β€’ Trial: on registration, create a 14-day TRIAL subscription with fixed quota; conversion swaps tier and resets period counters.
  • β€’ Pricing philosophy: workspace capacity model β€” leads / AI / enrichment / campaign contacts β€” with add-ons for temporary spikes (no forced plan upgrades).