Files
ai-mailer/CLAUDE.md

6.0 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

ParaClub AI Mailer is an automated email response system written in Go that:

  • Fetches emails via IMAP from a configured mailbox
  • Gathers context from configured URLs (HTML content extraction)
  • Uses OpenRouter AI API to generate context-aware email replies
  • Saves AI-generated responses as email drafts
  • Processes emails sequentially with configurable polling intervals

The system is designed as a long-running service with graceful shutdown handling.

Build and Run Commands

# Install dependencies
go mod download

# Build the application
go build -o paraclub-ai-mailer ./cmd/paraclub-ai-mailer

# Run with default config (config.yaml in current directory)
./paraclub-ai-mailer

# Run with custom config path
./paraclub-ai-mailer -config /path/to/config.yaml

# Run tests (if any exist)
go test ./...

# Format code
go fmt ./...

# Vet code for issues
go vet ./...

Configuration

The application requires a config.yaml file. Use config.yaml.example as a template.

Key configuration sections:

  • imap: Server details, credentials, mailbox folders
  • ai: OpenRouter API key, model selection, temperature, max tokens
  • context: List of URLs to fetch as context for AI
  • polling: Email check interval (e.g., "5m")
  • logging: Log level and file path

Credentials support three formats:

  • Direct value: password: "mypassword"
  • Environment variable: password: "${IMAP_PASSWORD}"
  • File reference: password: "file:///path/to/password.txt"

Code Architecture

Module Structure

cmd/paraclub-ai-mailer/main.go    # Entry point, orchestration
config/config.go                   # Configuration loading and validation
internal/
  ├── logger/logger.go             # Centralized logging wrapper (logrus)
  ├── imap/imap.go                 # IMAP client, email operations
  ├── fetcher/fetcher.go           # HTML content fetching and text extraction
  └── ai/ai.go                     # OpenRouter AI integration

Key Design Patterns

Email Processing Flow (main.go:92-172):

  1. Fetch unprocessed emails (UNSEEN flag)
  2. Fetch context from all configured URLs
  3. For each email:
    • Detect language via AI
    • Generate AI reply with context
    • Save as draft with original email quoted
    • Mark as processed (move to processed_box, mark as read)

IMAP Client (internal/imap/imap.go):

  • Connection handling with automatic reconnection (ensureConnection, reconnect)
  • Folder path normalization with delimiter detection
  • Gmail-specific logic in ensureFolder (uses "/" delimiter, handles system folders)
  • Email content extraction handles multipart messages and quoted-printable encoding
  • Draft formatting includes HTML response + quoted original message
  • Panic recovery in SaveDraft and MarkAsProcessed

AI Integration (internal/ai/ai.go):

  • Two-stage process: language detection, then reply generation
  • Language-aware responses (system prompt includes detected language)
  • Output must be raw HTML (not markdown-wrapped)
  • Retry logic with max 3 attempts

Context Fetching (internal/fetcher/fetcher.go):

  • HTML to plain text extraction (strips tags, extracts text nodes)
  • 30-second timeout per URL
  • Batch fetching fails if any URL fails

Important Implementation Details

MIME Email Parsing (internal/imap/imap.go:287-510):

  • extractMessageContent handles multipart and single-part messages
  • Content-Transfer-Encoding support: quoted-printable
  • cleanMessageContent has a performHeaderStripping flag (true for fallback, false for parsed content)
  • Extensive debug logging for troubleshooting email parsing issues

Folder Management (internal/imap/imap.go:512-663):

  • ensureFolder detects Gmail vs generic IMAP servers
  • Gmail: Uses "/" delimiter, skips CREATE for system folders like "[Gmail]/Drafts"
  • Generic IMAP: Lists mailboxes to detect delimiter, creates folders if missing
  • Has timeout protection (30s for LIST, 35s overall)
  • Includes panic recovery

Logging Strategy:

  • All modules use internal/logger wrapper around logrus
  • Structured logging with fields (logrus.Fields)
  • Debug level logs include detailed information (e.g., email content lengths, folder paths)
  • Recent commits show enhanced logging for troubleshooting email extraction

Development Guidelines

Error Handling

  • Use defer/recover for panic protection in critical IMAP operations (SaveDraft, MarkAsProcessed, ensureFolder)
  • Retry mechanisms in AI client (max 3 attempts)
  • IMAP connection auto-reconnection on failure
  • Continue processing remaining emails if one fails

Adding New Features

  • New AI models: Update config.yaml model parameter (passed to OpenRouter API)
  • New context sources: Add URLs to config.yaml context.urls array
  • New IMAP operations: Add methods to IMAPClient struct, ensure connection handling
  • Email format changes: Modify draft template in SaveDraft (line 242-266)

Testing Considerations

  • Email parsing logic is complex (multipart, encoding) - test with various email formats
  • Gmail behavior differs from generic IMAP servers
  • Test with different folder delimiters ("/" vs ".")
  • Language detection affects response language

Dependencies

Core dependencies (go.mod):

  • github.com/emersion/go-imap - IMAP client library
  • github.com/sirupsen/logrus - Structured logging
  • golang.org/x/net/html - HTML parsing for context extraction
  • gopkg.in/yaml.v3 - Configuration file parsing

Common Issues

IMAP Folder Issues:

  • Check logs for delimiter detection (ensureFolder)
  • Gmail requires special handling for system folders
  • Folder names are case-sensitive

Email Content Extraction:

  • Multi-part emails may require different handling
  • Check Content-Transfer-Encoding header
  • Recent fixes improved header stripping and content cleaning

AI Response Issues:

  • Verify API key is correctly loaded from environment/file
  • Check model name is valid for OpenRouter
  • Response must be raw HTML (system prompt enforces this)