Welcome! This guide helps you contribute to EmailingEssay.
- Python 3.10+
- Git
- Fork and clone the repository
- Install dependencies:
pip install yagmail pytest
- Verify setup:
cd skills/send-email/scripts pytest
EmailingEssay/
├── commands/essay.md # Command definition
├── agents/essay-writer.md # Agent specification
├── … # Docs & manifests omitted
└── skills/
├── reflect/ # Reflection skill (agent-driven)
│ └── SKILL.md
└── send-email/ # Email sending skill
├── SKILL.md
└── scripts/ # Python implementation
├── main.py # Entry point
├── domain/ # Core entities
│ ├── code_generator.py
│ ├── config.py
│ ├── constants.py
│ ├── exceptions.py
│ ├── message_id.py # Message-ID minting (the ledger key)
│ ├── models.py
│ ├── thread_ref.py # In-Reply-To / References assembly
│ └── validators.py
├── usecases/ # Business logic
│ ├── command_builder.py
│ ├── factories.py
│ ├── import_legacy.py # Retroactive ledger migration
│ ├── ingest_replies.py # Reply ingestion
│ ├── ports.py
│ ├── schedule_essay.py
│ └── wait_essay.py
├── adapters/ # Interface implementations
│ ├── cli/ # CLI handlers & parser
│ ├── mail/
│ │ ├── imap_inbox.py # IMAP reply source
│ │ ├── ledger_recording_mail.py # MailPort decorator, records every send
│ │ └── yagmail_adapter.py # Gmail SMTP
│ ├── process/ # subprocess spawner
│ ├── scheduler/ # cron/Task Scheduler adapters
│ └── storage/
│ ├── ledger_storage.py # JSONL ledger + sent/ bodies
│ ├── path_resolver.py
│ ├── process_cache.py
│ ├── schedule_storage.py
│ └── waiter_storage.py
├── frameworks/ # External frameworks (templates)
├── tests/ # Test suite, mirrors the layers
│ ├── domain/test_message_id.py
│ ├── adapters/test_imap_inbox.py
│ ├── adapters/test_ledger_recording_mail.py
│ ├── adapters/storage/test_ledger_storage.py
│ ├── usecases/test_ingest_replies.py
│ ├── usecases/test_import_legacy.py
│ └── … # existing suites, one per layer
└── archive/ # Retired implementation backup
For detailed architecture, see CLAUDE.md → Clean Architecture Details section.
- Python: PEP 8, 100 char line limit
- Type hints: Required for public functions
- Docstrings: Triple-quoted, describe purpose
- Naming:
- Classes:
PascalCase - Functions/variables:
snake_case - Constants:
UPPER_SNAKE_CASE
- Classes:
cd skills/send-email/scripts
pytest # All tests
pytest tests/domain/ # Domain layer only
pytest -v # Verbose outputTests mirror the Clean Architecture layers:
tests/domain/- Entity teststests/usecases/- Business logic teststests/adapters/- Adapter tests
| Fixture | Description |
|---|---|
mock_mail_port |
Type-safe MailPort mock |
mock_scheduler_port |
SchedulerPort mock |
mock_schedule_storage |
ScheduleStoragePort mock |
mock_waiter_storage |
WaiterStoragePort mock |
mock_process_spawner |
ProcessSpawnerPort mock |
sample_schedule_dict |
Sample schedule data |
- Create a feature branch:
git checkout -b feature/your-feature - Make changes with tests
- Run full test suite:
pytest - Update CHANGELOG.md for user-facing changes
- Submit PR with clear description
- Tests pass
- New code has tests
- CHANGELOG.md updated (if applicable)
- No unrelated changes
- Create
adapters/mail/new_adapter.py - Implement
MailPortfromusecases/ports.py:from usecases.ports import MailPort class NewMailAdapter(MailPort): def send(self, to: str, subject: str, body: str) -> None: # Implementation pass def test(self) -> None: # Send test email pass def send_custom(self, subject: str, content: str) -> None: # Custom content pass
- Register in
usecases/factories.py - Add tests in
tests/adapters/
- Create
adapters/scheduler/new_scheduler.py - Implement
SchedulerPort:from usecases.ports import SchedulerPort, TaskInfo class NewSchedulerAdapter(SchedulerPort): def add(self, task_name: str, command: str, frequency: str, time: str, *, weekday: str = "", day_spec: str = "") -> None: pass def remove(self, name: str) -> None: pass def list(self) -> list[TaskInfo]: return []
- Handle platform detection in factories
EmailingEssay | GitHub