- Created INSTRUCTIONS.md detailing project goals, usage, and progress tracking. - Defined project scope, technology stack, and core domain concepts. - Outlined step-by-step build process from scaffolding to deployment. - Included detailed descriptions for each step, including entity models, services, and controllers. - Established a decision log to track key choices made during development.
204 lines
9.5 KiB
Markdown
204 lines
9.5 KiB
Markdown
# 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<T>` with explicit HTTP status codes.
|
|
- Services must be annotated with `@Service` and **never** depend on controllers.
|
|
- Repositories must extend `JpaRepository<Entity, IdType>`.
|
|
- 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/<short-description>`, `fix/<short-description>`, `chore/<short-description>`
|
|
- 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`.
|