Add build instructions and project structure for Condado Abaixo da Média SA Email Bot
- 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.
This commit is contained in:
160
CLAUDE.md
160
CLAUDE.md
@@ -1,6 +1,6 @@
|
||||
# Condado Newsletter Bot
|
||||
# Condado Abaixo da Média SA — Email Bot
|
||||
|
||||
A newsletter bot built with **Kotlin** and **Spring Boot**. This file gives Claude persistent
|
||||
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.
|
||||
|
||||
---
|
||||
@@ -9,23 +9,33 @@ instructions and context about the project so every session starts with the righ
|
||||
|
||||
- **Language:** Kotlin (JVM)
|
||||
- **Framework:** Spring Boot 3.x
|
||||
- **Purpose:** Automate the creation, management, and delivery of newsletters
|
||||
- **Architecture:** REST API backend with scheduled jobs for sending newsletters
|
||||
- **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 |
|
||||
| Framework | Spring Boot 3.x |
|
||||
| Build Tool | Gradle (Kotlin DSL - `build.gradle.kts`) |
|
||||
| Database | PostgreSQL (via Spring Data JPA) |
|
||||
| Email | Spring Mail (SMTP / JavaMailSender) |
|
||||
| Scheduler | Spring `@Scheduled` tasks |
|
||||
| Testing | JUnit 5 + MockK |
|
||||
| Docs | Springdoc OpenAPI (Swagger UI) |
|
||||
| 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) |
|
||||
|
||||
---
|
||||
|
||||
@@ -35,20 +45,24 @@ instructions and context about the project so every session starts with the righ
|
||||
src/
|
||||
├── main/
|
||||
│ ├── kotlin/com/condado/newsletter/
|
||||
│ │ ├── CondadoNewsletterApplication.kt # App entry point
|
||||
│ │ ├── config/ # Spring configuration classes
|
||||
│ │ ├── controller/ # REST controllers
|
||||
│ │ ├── service/ # Business logic
|
||||
│ │ ├── repository/ # Spring Data JPA repositories
|
||||
│ │ ├── model/ # JPA entities
|
||||
│ │ ├── dto/ # Data Transfer Objects
|
||||
│ │ └── scheduler/ # Scheduled tasks
|
||||
│ │ ├── 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
|
||||
│ └── templates/ # Email HTML templates (Thymeleaf)
|
||||
│ ├── application.yml # Main config
|
||||
│ └── application-dev.yml # Dev profile config
|
||||
└── test/
|
||||
└── kotlin/com/condado/newsletter/ # Tests mirror main structure
|
||||
└── kotlin/com/condado/newsletter/ # Tests mirror main structure
|
||||
```
|
||||
|
||||
---
|
||||
@@ -66,10 +80,10 @@ src/
|
||||
./gradlew test
|
||||
|
||||
# Run a specific test class
|
||||
./gradlew test --tests "com.condado.newsletter.service.NewsletterServiceTest"
|
||||
./gradlew test --tests "com.condado.newsletter.service.PromptBuilderServiceTest"
|
||||
|
||||
# Generate OpenAPI docs (served at /swagger-ui.html when running)
|
||||
./gradlew bootRun
|
||||
# OpenAPI docs available at runtime
|
||||
# http://localhost:8080/swagger-ui.html
|
||||
```
|
||||
|
||||
---
|
||||
@@ -79,7 +93,7 @@ src/
|
||||
- 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 (`javax.validation`).
|
||||
- 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>`.
|
||||
@@ -87,20 +101,22 @@ src/
|
||||
- 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 | `NewsletterService` |
|
||||
| Functions | camelCase | `sendNewsletter()` |
|
||||
| Variables | camelCase | `subscriberList` |
|
||||
| Constants | SCREAMING_SNAKE_CASE | `MAX_RETRIES` |
|
||||
| DB tables | snake_case (plural) | `newsletter_subscribers` |
|
||||
| REST endpoints | kebab-case | `/api/v1/newsletter-issues` |
|
||||
| Packages | lowercase | `com.condado.newsletter` |
|
||||
| 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` |
|
||||
|
||||
---
|
||||
|
||||
@@ -116,15 +132,21 @@ src/
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description |
|
||||
|-----------------------|-----------------------------------|
|
||||
| `SPRING_DATASOURCE_URL` | PostgreSQL connection URL |
|
||||
| `SPRING_DATASOURCE_USERNAME` | DB username |
|
||||
| `SPRING_DATASOURCE_PASSWORD` | DB password |
|
||||
| `MAIL_HOST` | SMTP host |
|
||||
| `MAIL_PORT` | SMTP port |
|
||||
| `MAIL_USERNAME` | SMTP username |
|
||||
| `MAIL_PASSWORD` | SMTP password |
|
||||
| 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).
|
||||
|
||||
@@ -132,10 +154,44 @@ src/
|
||||
|
||||
## Key Domain Concepts
|
||||
|
||||
- **Subscriber:** A person who opted in to receive newsletters.
|
||||
- **NewsletterIssue:** A single newsletter edition with a subject and HTML body.
|
||||
- **Campaign:** A scheduled or triggered dispatch of a `NewsletterIssue` to a group of subscribers.
|
||||
- **SendLog:** A record of each email send attempt (status: PENDING / SENT / FAILED).
|
||||
- **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].
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user