# Condado Abaixo da Média SA — Email Bot A backend service built with **Kotlin** and **Spring Boot**. This file gives the AI persistent instructions and context about the project so every session starts with the right knowledge. --- ## Project Overview - **Language:** Kotlin (JVM) - **Framework:** Spring Boot 3.x - **Purpose:** Simulate virtual employees of the fictional company "Condado Abaixo da Média SA". Each entity is a virtual employee with a name, email address, job title, personality, and an email schedule. At the scheduled time, the system reads recent emails from the company mailbox (filtered by a configurable time window), builds a prompt from the entity's profile + the email history, sends that prompt to an AI (OpenAI API), and dispatches the AI-generated email via SMTP. - **Tone rule (critical):** Every generated email must be written in an **extremely formal, corporate tone** — but the **content is completely casual and nonsensical**, like internal jokes between friends. This contrast is the core joke of the project and must be preserved in every generated email. - **Architecture:** REST API backend + scheduled AI-driven email dispatch --- ## Tech Stack | Layer | Technology | |----------------|---------------------------------------------------| | Language | Kotlin (JVM) | | Framework | Spring Boot 3.x | | Build Tool | Gradle (Kotlin DSL — `build.gradle.kts`) | | Database | PostgreSQL (via Spring Data JPA) | | Email Reading | Jakarta Mail (IMAP) — read inbox for context | | Email Sending | Spring Mail (SMTP / JavaMailSender) | | AI Integration | OpenAI API (`gpt-4o`) via HTTP client | | Scheduler | Spring `@Scheduled` tasks | | Testing | JUnit 5 + MockK | | Docs | Springdoc OpenAPI (Swagger UI) | --- ## Project Structure ``` src/ ├── main/ │ ├── kotlin/com/condado/newsletter/ │ │ ├── CondadoApplication.kt # App entry point │ │ ├── config/ # Spring configuration classes │ │ ├── controller/ # REST controllers │ │ ├── service/ # Business logic │ │ │ ├── EntityService.kt # CRUD for virtual entities │ │ │ ├── EmailReaderService.kt # Reads emails via IMAP │ │ │ ├── PromptBuilderService.kt # Builds AI prompt from entity + emails │ │ │ ├── AiService.kt # Calls OpenAI API │ │ │ └── EmailSenderService.kt # Sends email via SMTP │ │ ├── repository/ # Spring Data JPA repositories │ │ ├── model/ # JPA entities │ │ ├── dto/ # Data Transfer Objects │ │ └── scheduler/ # Scheduled tasks (trigger per entity) │ └── resources/ │ ├── application.yml # Main config │ └── application-dev.yml # Dev profile config └── test/ └── kotlin/com/condado/newsletter/ # Tests mirror main structure ``` --- ## Build & Run Commands ```bash # Build the project ./gradlew build # Run the application (dev profile) ./gradlew bootRun --args='--spring.profiles.active=dev' # Run all tests ./gradlew test # Run a specific test class ./gradlew test --tests "com.condado.newsletter.service.PromptBuilderServiceTest" # OpenAPI docs available at runtime # http://localhost:8080/swagger-ui.html ``` --- ## Coding Standards - Use **Kotlin idiomatic** style: data classes, extension functions, and null-safety operators. - Prefer `val` over `var` wherever possible. - Use **constructor injection** for dependencies (never field injection with `@Autowired`). - All DTOs must be **data classes** with validation annotations (`jakarta.validation`). - Controller methods must return `ResponseEntity` with explicit HTTP status codes. - Services must be annotated with `@Service` and **never** depend on controllers. - Repositories must extend `JpaRepository`. - Use `@Transactional` on service methods that modify data. - All public functions must have **KDoc** comments. - Use **`snake_case`** for database columns and **`camelCase`** for Kotlin properties. - Keep controllers thin — business logic belongs in services. - The AI prompt construction logic must live **exclusively** in `PromptBuilderService` — no other class should build or modify prompt strings. --- ## Naming Conventions | Artifact | Convention | Example | |----------------|-----------------------------------|-------------------------------| | Classes | PascalCase | `PromptBuilderService` | | Functions | camelCase | `buildPrompt()` | | Variables | camelCase | `entityList` | | Constants | SCREAMING_SNAKE_CASE | `MAX_EMAIL_CONTEXT_DAYS` | | DB tables | snake_case (plural) | `virtual_entities` | | REST endpoints | kebab-case | `/api/v1/virtual-entities` | | Packages | lowercase | `com.condado.newsletter` | --- ## Testing Guidelines - Every service class must have a corresponding unit test class. - Use **MockK** for mocking (not Mockito). - Integration tests use `@SpringBootTest` and an **H2 in-memory** database. - Test method names follow the pattern: `should_[expectedBehavior]_when_[condition]`. - Minimum 80% code coverage for service classes. --- ## Environment Variables | Variable | Description | |---------------------------|------------------------------------------------------| | `SPRING_DATASOURCE_URL` | PostgreSQL connection URL | | `SPRING_DATASOURCE_USERNAME` | DB username | | `SPRING_DATASOURCE_PASSWORD` | DB password | | `MAIL_HOST` | SMTP host (for sending emails) | | `MAIL_PORT` | SMTP port | | `MAIL_USERNAME` | SMTP username (also used as IMAP login) | | `MAIL_PASSWORD` | SMTP/IMAP password | | `IMAP_HOST` | IMAP host (for reading the shared inbox) | | `IMAP_PORT` | IMAP port (default: 993) | | `IMAP_INBOX_FOLDER` | IMAP folder to read (default: `INBOX`) | | `OPENAI_API_KEY` | OpenAI API key for AI generation | | `OPENAI_MODEL` | OpenAI model to use (default: `gpt-4o`) | | `API_KEY` | API key to protect the REST endpoints | > ⚠️ Never hardcode credentials. Always use environment variables or a `.env` file (gitignored). --- ## Key Domain Concepts - **VirtualEntity:** A fictional employee of "Condado Abaixo da Média SA". Has a name, a real email address (used as sender), a job title, a personality description, an email schedule (cron expression), and an email context window (how many days back to read emails for context). - **EmailContext:** A snapshot of recent emails read from the shared IMAP inbox, filtered by the entity's configured context window (e.g., last 3 days). Used to give the AI conversational context. - **Prompt:** The full text sent to the OpenAI API. Built by `PromptBuilderService` from the entity's profile + the `EmailContext`. Always instructs the AI to write in an extremely formal corporate tone with completely casual/nonsensical content. - **DispatchLog:** A record of each AI email generation and send attempt for a given entity. Stores the generated prompt, the AI response, send status, and timestamp. --- ## The Prompt Template (Core Logic) Every prompt sent to the AI must follow this structure: ``` You are [entity.name], [entity.jobTitle] at "Condado Abaixo da Média SA". Your personality: [entity.personality] IMPORTANT TONE RULE: You must write in an extremely formal, bureaucratic, corporate tone — as if writing an official memo. However, the actual content of the email must be completely casual, trivial, or nonsensical — as if talking to close friends about mundane things. The contrast between the formal tone and the casual content is intentional and essential. Here are the most recent emails from the company inbox (last [entity.contextWindowDays] days) for context: [list of recent emails: sender, subject, body] Write a new email to be sent to the company group, continuing the conversation naturally. Reply or react to the recent emails if relevant. Sign off as [entity.name], [entity.jobTitle]. ``` --- ## Git Workflow - Branch naming: `feature/`, `fix/`, `chore/` - Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/): `feat:`, `fix:`, `chore:`, `docs:`, `test:` - PRs require at least one passing CI check before merging. - Never commit directly to `main`.