openapi: 3.1.0
info:
  # Do not change the title, if the title changes, the import paths will be broken
  title: Api
  version: 0.1.0
  description: AI Cold Outreach CRM API
servers:
  - url: /api
    description: Base API path
tags:
  - name: health
    description: Health operations
  - name: leads
    description: Lead management
  - name: campaigns
    description: Campaign management
  - name: templates
    description: Email template management
  - name: emails
    description: Email tracking and management
  - name: smtp
    description: SMTP configuration
  - name: ai
    description: AI generation endpoints
  - name: analytics
    description: Analytics and reporting
  - name: followups
    description: Follow-up automation
  - name: tools
    description: Public utility tools

paths:
  /healthz:
    get:
      operationId: healthCheck
      tags: [health]
      summary: Health check
      responses:
        "200":
          description: Healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthStatus"

  # ─── LEAD LISTS ─────────────────────────────────────────────────────────────
  /lead-lists:
    get:
      operationId: listLeadLists
      tags: [leads]
      summary: List all lead lists for the current user
      responses:
        "200":
          description: Array of lead lists
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/LeadList"
    post:
      operationId: createLeadList
      tags: [leads]
      summary: Create a new lead list
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateLeadListBody"
      responses:
        "201":
          description: Created lead list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LeadList"

  /lead-lists/{id}:
    patch:
      operationId: updateLeadList
      tags: [leads]
      summary: Rename a lead list
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateLeadListBody"
      responses:
        "200":
          description: Updated lead list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LeadList"
    delete:
      operationId: deleteLeadList
      tags: [leads]
      summary: Delete a lead list (leads remain, list_id cleared)
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "204":
          description: Deleted

  # ─── LEADS ──────────────────────────────────────────────────────────────────
  /leads:
    get:
      operationId: listLeads
      tags: [leads]
      summary: List all leads
      parameters:
        - name: listId
          in: query
          schema: { type: ["integer", "null"] }
        - name: campaignId
          in: query
          schema: { type: ["integer", "null"] }
        - name: status
          in: query
          schema: { type: ["string", "null"] }
        - name: search
          in: query
          schema: { type: ["string", "null"] }
        - name: page
          in: query
          schema: { type: integer, default: 1 }
        - name: pageSize
          in: query
          schema: { type: integer, default: 50 }
      responses:
        "200":
          description: List of leads
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LeadListResponse"
    post:
      operationId: createLead
      tags: [leads]
      summary: Create a new lead
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LeadInput"
      responses:
        "201":
          description: Created lead
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Lead"

  /leads/import:
    post:
      operationId: importLeads
      tags: [leads]
      summary: Bulk import leads from CSV data
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LeadImportInput"
      responses:
        "200":
          description: Import result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LeadImportResult"

  /leads/{id}:
    get:
      operationId: getLead
      tags: [leads]
      summary: Get a lead by ID
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Lead details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Lead"
    patch:
      operationId: updateLead
      tags: [leads]
      summary: Update a lead
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LeadUpdate"
      responses:
        "200":
          description: Updated lead
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Lead"
    delete:
      operationId: deleteLead
      tags: [leads]
      summary: Delete a lead
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "204":
          description: Deleted

  # ─── CAMPAIGNS ──────────────────────────────────────────────────────────────
  /campaigns:
    get:
      operationId: listCampaigns
      tags: [campaigns]
      summary: List all campaigns
      responses:
        "200":
          description: List of campaigns
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Campaign"
    post:
      operationId: createCampaign
      tags: [campaigns]
      summary: Create a campaign
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CampaignInput"
      responses:
        "201":
          description: Created campaign
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Campaign"

  /campaigns/{id}:
    get:
      operationId: getCampaign
      tags: [campaigns]
      summary: Get a campaign
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Campaign details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Campaign"
    patch:
      operationId: updateCampaign
      tags: [campaigns]
      summary: Update a campaign
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CampaignUpdate"
      responses:
        "200":
          description: Updated campaign
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Campaign"
    delete:
      operationId: deleteCampaign
      tags: [campaigns]
      summary: Delete a campaign
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "204":
          description: Deleted

  /campaigns/{id}/start:
    post:
      operationId: startCampaign
      tags: [campaigns]
      summary: Start a campaign
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Campaign started
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Campaign"

  /campaigns/{id}/pause:
    post:
      operationId: pauseCampaign
      tags: [campaigns]
      summary: Pause a campaign
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Campaign paused
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Campaign"

  /campaigns/{id}/resume:
    post:
      operationId: resumeCampaign
      tags: [campaigns]
      summary: Resume a paused or stopped campaign from where it left off
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Campaign resumed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Campaign"

  /campaigns/{id}/stop:
    post:
      operationId: stopCampaign
      tags: [campaigns]
      summary: Stop a campaign (halts sending, resumable later)
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Campaign stopped
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Campaign"

  /campaigns/{id}/archive:
    post:
      operationId: archiveCampaign
      tags: [campaigns]
      summary: Archive a campaign
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Campaign archived
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Campaign"

  /campaigns/{id}/unarchive:
    post:
      operationId: unarchiveCampaign
      tags: [campaigns]
      summary: Restore an archived campaign
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Campaign restored
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Campaign"

  /campaigns/{id}/duplicate:
    post:
      operationId: duplicateCampaign
      tags: [campaigns]
      summary: Duplicate a campaign's settings as a new draft
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "201":
          description: Duplicated campaign
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Campaign"

  /campaigns/bulk:
    post:
      operationId: bulkCampaignAction
      tags: [campaigns]
      summary: Apply an action to multiple campaigns at once
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BulkCampaignActionInput"
      responses:
        "200":
          description: Bulk action result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BulkActionResult"

  # ─── TEMPLATES ──────────────────────────────────────────────────────────────
  /templates:
    get:
      operationId: listTemplates
      tags: [templates]
      summary: List email templates
      responses:
        "200":
          description: List of templates
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/EmailTemplate"
    post:
      operationId: createTemplate
      tags: [templates]
      summary: Create an email template
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EmailTemplateInput"
      responses:
        "201":
          description: Created template
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmailTemplate"

  /templates/{id}:
    get:
      operationId: getTemplate
      tags: [templates]
      summary: Get a template
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Template details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmailTemplate"
    patch:
      operationId: updateTemplate
      tags: [templates]
      summary: Update a template
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EmailTemplateUpdate"
      responses:
        "200":
          description: Updated template
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmailTemplate"
    delete:
      operationId: deleteTemplate
      tags: [templates]
      summary: Delete a template
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "204":
          description: Deleted

  # ─── EMAILS ─────────────────────────────────────────────────────────────────
  /emails:
    get:
      operationId: listEmails
      tags: [emails]
      summary: List emails
      parameters:
        - name: leadId
          in: query
          schema: { type: ["integer", "null"] }
        - name: campaignId
          in: query
          schema: { type: ["integer", "null"] }
        - name: status
          in: query
          schema: { type: ["string", "null"] }
        - name: page
          in: query
          schema: { type: integer, default: 1 }
        - name: pageSize
          in: query
          schema: { type: integer, default: 50 }
      responses:
        "200":
          description: List of emails
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmailListResponse"
    post:
      operationId: sendEmail
      tags: [emails]
      summary: Send an email to a lead
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendEmailInput"
      responses:
        "201":
          description: Email queued/sent
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Email"

  /emails/compose:
    post:
      operationId: composeEmail
      tags: [emails]
      summary: Compose and immediately send an email to a lead
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ComposeEmailInput"
      responses:
        "200":
          description: Email sent result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ComposeEmailResult"

  /emails/{id}:
    get:
      operationId: getEmail
      tags: [emails]
      summary: Get email details
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Email details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Email"

  /emails/{id}/track-open:
    post:
      operationId: trackEmailOpen
      tags: [emails]
      summary: Track email open event
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Tracked
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Email"

  # ─── SMTP ────────────────────────────────────────────────────────────────────
  /smtp:
    get:
      operationId: listSmtpConfigs
      tags: [smtp]
      summary: List SMTP configurations
      responses:
        "200":
          description: List of SMTP configs
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/SmtpConfig"
    post:
      operationId: createSmtpConfig
      tags: [smtp]
      summary: Create SMTP configuration
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SmtpConfigInput"
      responses:
        "201":
          description: Created SMTP config
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SmtpConfig"

  /smtp/{id}:
    patch:
      operationId: updateSmtpConfig
      tags: [smtp]
      summary: Update SMTP configuration
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SmtpConfigUpdate"
      responses:
        "200":
          description: Updated SMTP config
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SmtpConfig"
    delete:
      operationId: deleteSmtpConfig
      tags: [smtp]
      summary: Delete SMTP configuration
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "204":
          description: Deleted

  /smtp/{id}/test:
    post:
      operationId: testSmtpConfig
      tags: [smtp]
      summary: Test an SMTP configuration
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Test result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SmtpTestResult"

  # ─── AI ─────────────────────────────────────────────────────────────────────
  /ai/generate-email:
    post:
      operationId: generateEmail
      tags: [ai]
      summary: Generate a personalized cold email using AI
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GenerateEmailInput"
      responses:
        "200":
          description: Generated email
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GeneratedEmail"

  /ai/analyze-website:
    post:
      operationId: analyzeWebsite
      tags: [ai]
      summary: Analyze a website and extract insights for personalization
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AnalyzeWebsiteInput"
      responses:
        "200":
          description: Website analysis
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebsiteAnalysis"

  # ─── FOLLOW-UPS ──────────────────────────────────────────────────────────────
  /followups:
    get:
      operationId: listFollowups
      tags: [followups]
      summary: List follow-ups
      parameters:
        - name: leadId
          in: query
          schema: { type: ["integer", "null"] }
        - name: campaignId
          in: query
          schema: { type: ["integer", "null"] }
        - name: status
          in: query
          schema: { type: ["string", "null"] }
      responses:
        "200":
          description: List of follow-ups
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Followup"

  /followups/bulk-delete:
    post:
      operationId: bulkDeleteFollowups
      tags: [followups]
      summary: Delete multiple follow-ups at once
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ids]
              properties:
                ids:
                  type: array
                  items: { type: integer }
      responses:
        "200":
          description: Number of follow-ups deleted
          content:
            application/json:
              schema:
                type: object
                required: [deleted]
                properties:
                  deleted: { type: integer }

  /followups/{id}/cancel:
    post:
      operationId: cancelFollowup
      tags: [followups]
      summary: Cancel a scheduled follow-up
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Cancelled follow-up
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Followup"

  # ─── ANALYTICS ───────────────────────────────────────────────────────────────
  /analytics/dashboard:
    get:
      operationId: getDashboardStats
      tags: [analytics]
      summary: Get dashboard summary statistics
      responses:
        "200":
          description: Dashboard statistics
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DashboardStats"

  /analytics/campaign/{id}:
    get:
      operationId: getCampaignAnalytics
      tags: [analytics]
      summary: Get analytics for a specific campaign
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Campaign analytics
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignAnalytics"

  /analytics/lead-pipeline:
    get:
      operationId: getLeadPipeline
      tags: [analytics]
      summary: Get lead counts grouped by status
      responses:
        "200":
          description: Lead pipeline breakdown
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/PipelineStage"

  /analytics/recent-activity:
    get:
      operationId: getRecentActivity
      tags: [analytics]
      summary: Get recent email activity feed
      parameters:
        - name: limit
          in: query
          schema: { type: integer, default: 20 }
      responses:
        "200":
          description: Recent activity items
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ActivityItem"

  # ─── TOOLS (public, no auth) ─────────────────────────────────────────────────
  /tools/url-status:
    get:
      operationId: checkUrlStatus
      tags: [tools]
      summary: Check the HTTP status and redirect chain of a URL (public, no auth)
      parameters:
        - name: url
          in: query
          required: true
          schema: { type: string, maxLength: 2048 }
        - name: maxRedirects
          in: query
          schema: { type: integer, minimum: 1, maximum: 10, default: 10 }
      responses:
        "200":
          description: URL status result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UrlStatusResult"

components:
  schemas:
    HealthStatus:
      type: object
      properties:
        status: { type: string }
      required: [status]

    # ── Lead List ────────────────────────────────────────────────────────────
    LeadList:
      type: object
      required: [id, name, createdAt, leadCount]
      properties:
        id: { type: integer }
        name: { type: string }
        createdAt: { type: string, format: date-time }
        leadCount: { type: integer }

    CreateLeadListBody:
      type: object
      required: [name]
      properties:
        name: { type: string, minLength: 1 }

    # ── Lead ────────────────────────────────────────────────────────────────
    Lead:
      type: object
      required: [id, name, email, status, createdAt]
      properties:
        id: { type: integer }
        name: { type: string }
        company: { type: ["string", "null"] }
        email: { type: string }
        website: { type: ["string", "null"] }
        phone: { type: ["string", "null"] }
        country: { type: ["string", "null"] }
        industry: { type: ["string", "null"] }
        serviceInterested: { type: ["string", "null"] }
        notes: { type: ["string", "null"] }
        status: { type: string }
        campaignId: { type: ["integer", "null"] }
        listId: { type: ["integer", "null"] }
        websiteAnalysis: { type: ["string", "null"] }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: ["string", "null"], format: date-time }

    LeadInput:
      type: object
      required: [name, email]
      properties:
        name: { type: string, minLength: 1 }
        company: { type: string }
        email: { type: string, format: email }
        website: { type: string }
        phone: { type: string }
        country: { type: string }
        industry: { type: string }
        serviceInterested: { type: string }
        notes: { type: string }
        status: { type: string }
        campaignId: { type: integer }

    LeadUpdate:
      type: object
      properties:
        name: { type: string }
        company: { type: string }
        email: { type: string }
        website: { type: string }
        phone: { type: string }
        country: { type: string }
        industry: { type: string }
        serviceInterested: { type: string }
        notes: { type: string }
        status: { type: string }
        campaignId: { type: ["integer", "null"] }
        websiteAnalysis: { type: string }

    LeadListResponse:
      type: object
      required: [leads, total, page, pageSize]
      properties:
        leads:
          type: array
          items:
            $ref: "#/components/schemas/Lead"
        total: { type: integer }
        page: { type: integer }
        pageSize: { type: integer }

    LeadImportInput:
      type: object
      required: [leads]
      properties:
        leads:
          type: array
          items:
            $ref: "#/components/schemas/LeadInput"
        campaignId: { type: ["integer", "null"] }
        listId: { type: ["integer", "null"] }
        listName: { type: ["string", "null"] }

    LeadImportResult:
      type: object
      required: [imported, duplicates, invalid, total]
      properties:
        imported: { type: integer }
        duplicates: { type: integer }
        invalid: { type: integer }
        total: { type: integer }
        errors:
          type: array
          items: { type: string }

    # ── Campaign ─────────────────────────────────────────────────────────────
    Campaign:
      type: object
      required: [id, name, status, serviceType, createdAt]
      properties:
        id: { type: integer }
        name: { type: string }
        serviceType: { type: string }
        status: { type: string }
        dailyLimit: { type: integer }
        fromName: { type: ["string", "null"] }
        fromEmail: { type: ["string", "null"] }
        replyTo: { type: ["string", "null"] }
        scheduledStartAt: { type: ["string", "null"], format: date-time }
        leadCount: { type: integer }
        sentCount: { type: integer }
        openCount: { type: integer }
        replyCount: { type: integer }
        createdAt: { type: string, format: date-time }

    CampaignInput:
      type: object
      required: [name, serviceType]
      properties:
        name: { type: string, minLength: 1 }
        serviceType: { type: string }
        dailyLimit: { type: integer }
        fromName: { type: string }
        fromEmail: { type: string }
        replyTo: { type: string }

    CampaignUpdate:
      type: object
      properties:
        name: { type: string }
        serviceType: { type: string }
        dailyLimit: { type: integer }
        fromName: { type: string }
        fromEmail: { type: string }
        replyTo: { type: string }
        status: { type: string }

    BulkCampaignActionInput:
      type: object
      required: [ids, action]
      properties:
        ids:
          type: array
          items: { type: integer }
          minItems: 1
        action:
          type: string
          enum: [resume, pause, stop, archive, unarchive, duplicate, delete]

    BulkActionResult:
      type: object
      required: [action, processed, failed]
      properties:
        action: { type: string }
        processed: { type: integer }
        failed: { type: integer }

    # ── EmailTemplate ────────────────────────────────────────────────────────
    EmailTemplate:
      type: object
      required: [id, name, subject, body, tone, createdAt]
      properties:
        id: { type: integer }
        name: { type: string }
        subject: { type: string }
        body: { type: string }
        tone: { type: string }
        serviceType: { type: ["string", "null"] }
        isActive: { type: boolean }
        createdAt: { type: string, format: date-time }

    EmailTemplateInput:
      type: object
      required: [name, subject, body, tone]
      properties:
        name: { type: string, minLength: 1 }
        subject: { type: string, minLength: 1 }
        body: { type: string, minLength: 1 }
        tone: { type: string }
        serviceType: { type: string }
        isActive: { type: boolean }

    EmailTemplateUpdate:
      type: object
      properties:
        name: { type: string }
        subject: { type: string }
        body: { type: string }
        tone: { type: string }
        serviceType: { type: string }
        isActive: { type: boolean }

    # ── Email ────────────────────────────────────────────────────────────────
    Email:
      type: object
      required: [id, leadId, subject, body, status, createdAt]
      properties:
        id: { type: integer }
        leadId: { type: integer }
        campaignId: { type: ["integer", "null"] }
        templateId: { type: ["integer", "null"] }
        smtpConfigId: { type: ["integer", "null"] }
        subject: { type: string }
        body: { type: string }
        status: { type: string }
        sentAt: { type: ["string", "null"], format: date-time }
        openedAt: { type: ["string", "null"], format: date-time }
        repliedAt: { type: ["string", "null"], format: date-time }
        bouncedAt: { type: ["string", "null"], format: date-time }
        openCount: { type: integer }
        failureReason: { type: ["string", "null"] }
        failureCode: { type: ["string", "null"] }
        bounceReason: { type: ["string", "null"] }
        sequenceNo: { type: integer }
        createdAt: { type: string, format: date-time }
        lead: { $ref: "#/components/schemas/Lead" }

    EmailListResponse:
      type: object
      required: [emails, total, page, pageSize]
      properties:
        emails:
          type: array
          items:
            $ref: "#/components/schemas/Email"
        total: { type: integer }
        page: { type: integer }
        pageSize: { type: integer }

    SendEmailInput:
      type: object
      required: [leadId, subject, body]
      properties:
        leadId: { type: integer }
        campaignId: { type: ["integer", "null"] }
        templateId: { type: ["integer", "null"] }
        smtpConfigId: { type: ["integer", "null"] }
        subject: { type: string }
        body: { type: string }
        sequenceNo: { type: integer }

    ComposeEmailInput:
      type: object
      required: [leadId, smtpConfigId, subject, body]
      properties:
        leadId: { type: integer }
        smtpConfigId: { type: integer }
        templateId: { type: ["integer", "null"] }
        subject: { type: string }
        body: { type: string }

    ComposeEmailResult:
      type: object
      required: [success]
      properties:
        success: { type: boolean }
        emailId: { type: ["integer", "null"] }
        subject: { type: ["string", "null"] }
        error: { type: ["string", "null"] }

    # ── SmtpConfig ───────────────────────────────────────────────────────────
    SmtpConfig:
      type: object
      required: [id, host, port, username, fromEmail, createdAt]
      properties:
        id: { type: integer }
        name: { type: ["string", "null"] }
        host: { type: string }
        port: { type: integer }
        username: { type: string }
        fromName: { type: ["string", "null"] }
        fromEmail: { type: string }
        secure: { type: boolean }
        dailyLimit: { type: integer }
        sentToday: { type: integer }
        isActive: { type: boolean }
        lastTestedAt: { type: ["string", "null"], format: date-time }
        lastTestSuccess: { type: ["boolean", "null"] }
        createdAt: { type: string, format: date-time }

    SmtpConfigInput:
      type: object
      required: [host, port, username, password, fromEmail]
      properties:
        name: { type: string }
        host: { type: string }
        port: { type: integer }
        username: { type: string }
        password: { type: string }
        fromName: { type: string }
        fromEmail: { type: string }
        secure: { type: boolean }
        dailyLimit: { type: integer }

    SmtpConfigUpdate:
      type: object
      properties:
        name: { type: string }
        host: { type: string }
        port: { type: integer }
        username: { type: string }
        password: { type: string }
        fromName: { type: string }
        fromEmail: { type: string }
        secure: { type: boolean }
        dailyLimit: { type: integer }
        isActive: { type: boolean }

    SmtpTestResult:
      type: object
      required: [success, message]
      properties:
        success: { type: boolean }
        message: { type: string }

    # ── AI ───────────────────────────────────────────────────────────────────
    GenerateEmailInput:
      type: object
      required: [leadId, tone, serviceType]
      properties:
        leadId: { type: integer }
        tone: { type: string }
        serviceType: { type: string }
        senderName: { type: string }
        senderCompany: { type: string }
        customInstructions: { type: string }

    GeneratedEmail:
      type: object
      required: [subject, body]
      properties:
        subject: { type: string }
        body: { type: string }
        alternativeSubjects:
          type: array
          items: { type: string }

    AnalyzeWebsiteInput:
      type: object
      required: [url]
      properties:
        url: { type: string }
        leadId: { type: ["integer", "null"] }

    WebsiteAnalysis:
      type: object
      required: [url, summary, painPoints]
      properties:
        url: { type: string }
        summary: { type: string }
        painPoints:
          type: array
          items: { type: string }
        recommendations:
          type: array
          items: { type: string }
        seoIssues:
          type: array
          items: { type: string }
        industry: { type: ["string", "null"] }

    # ── Followup ─────────────────────────────────────────────────────────────
    Followup:
      type: object
      required: [id, leadId, sequenceNo, scheduledAt, status, createdAt]
      properties:
        id: { type: integer }
        leadId: { type: integer }
        campaignId: { type: ["integer", "null"] }
        emailId: { type: ["integer", "null"] }
        sequenceNo: { type: integer }
        scheduledAt: { type: string, format: date-time }
        sentAt: { type: ["string", "null"], format: date-time }
        status: { type: string }
        subject: { type: ["string", "null"] }
        body: { type: ["string", "null"] }
        createdAt: { type: string, format: date-time }

    # ── Analytics ────────────────────────────────────────────────────────────
    DashboardStats:
      type: object
      required: [totalLeads, activeCampaigns, emailsSent, openRate, replyRate, meetingsBooked, bounceRate, emailsSentToday, processed, sent, failed, bounced, skipped, successRate]
      properties:
        totalLeads: { type: integer }
        activeCampaigns: { type: integer }
        emailsSent: { type: integer }
        openRate: { type: number }
        replyRate: { type: number }
        meetingsBooked: { type: integer }
        bounceRate: { type: number }
        emailsSentToday: { type: integer }
        newLeadsThisWeek: { type: integer }
        positiveReplies: { type: integer }
        processed: { type: integer }
        sent: { type: integer }
        failed: { type: integer }
        bounced: { type: integer }
        skipped: { type: integer }
        successRate: { type: number }

    CampaignAnalytics:
      type: object
      required: [campaignId, totalLeads, sent, opened, replied, bounced, openRate, replyRate]
      properties:
        campaignId: { type: integer }
        totalLeads: { type: integer }
        sent: { type: integer }
        opened: { type: integer }
        replied: { type: integer }
        bounced: { type: integer }
        openRate: { type: number }
        replyRate: { type: number }
        dailySendingData:
          type: array
          items:
            $ref: "#/components/schemas/DailySendingPoint"

    DailySendingPoint:
      type: object
      required: [date, sent, opened, replied]
      properties:
        date: { type: string }
        sent: { type: integer }
        opened: { type: integer }
        replied: { type: integer }

    PipelineStage:
      type: object
      required: [status, count]
      properties:
        status: { type: string }
        count: { type: integer }
        label: { type: string }

    ActivityItem:
      type: object
      required: [id, type, description, createdAt]
      properties:
        id: { type: integer }
        type: { type: string }
        description: { type: string }
        leadName: { type: ["string", "null"] }
        campaignName: { type: ["string", "null"] }
        createdAt: { type: string, format: date-time }

    # ── Tools ────────────────────────────────────────────────────────────────
    UrlStatusHop:
      type: object
      required: [url, status, statusText, location, ms]
      properties:
        url: { type: string }
        status: { type: integer }
        statusText: { type: string }
        location: { type: string }
        ms: { type: integer }

    UrlStatusResult:
      oneOf:
        - type: object
          required: [ok, chain, finalUrl, finalStatus]
          properties:
            ok: { type: boolean, enum: [true] }
            chain:
              type: array
              items:
                $ref: "#/components/schemas/UrlStatusHop"
            finalUrl: { type: string }
            finalStatus: { type: integer }
        - type: object
          required: [ok, error]
          properties:
            ok: { type: boolean, enum: [false] }
            error: { type: string }
