# Development Guide

This document provides information for developers contributing to simple-email-gw.

## Prerequisites

- Python 3.11 or higher
- [uv](https://docs.astral.sh/uv/) package manager

## Getting Started

### Clone and Install

```bash
git clone https://github.com/christophevg/simple-email-gw.git
cd simple-email-gw

# Install development dependencies
make dev
```

### Run Tests

```bash
make test
```

### Run Linter

```bash
make lint
```

### Type Check

```bash
make typecheck
```

### Run All Checks

```bash
make all
```

## Project Structure

```
simple-email-gw/
├── docs/                    # Documentation
│   ├── api.md               # API reference
│   ├── configuration.md     # Configuration options
│   ├── development.md       # Development guide
│   ├── installation.md      # Installation guide
│   ├── mcp-tools.md         # MCP tools reference
│   └── security.md          # Security features
├── src/simple_email_gw/     # Source code
│   ├── __init__.py
│   ├── config.py            # Configuration handling
│   ├── mcp.py               # MCP server
│   ├── imap/                # IMAP client
│   │   └── client.py
│   ├── smtp/                # SMTP client
│   │   └── client.py
│   ├── connections/         # Connection pooling
│   │   └── pool.py
│   └── safety/              # Security features
│       ├── audit.py
│       ├── rate_limiter.py
│       └── sanitize.py
├── tests/                   # Test suite
│   ├── conftest.py
│   ├── test_config.py
│   ├── test_rate_limiter.py
│   ├── test_sanitize.py
│   └── test_whitelist.py
├── pyproject.toml
├── Makefile
└── README.md
```

## Code Style

### Formatting

- Use **two spaces** for indentation
- Maximum line length: 100 characters
- Use `ruff format` to auto-format

```bash
make format
```

### Linting

We use `ruff` for linting. Run it with:

```bash
make lint
```

### Type Annotations

All public functions should have type annotations. We use `mypy` for type checking:

```bash
make typecheck
```

## Testing

### Running Tests

```bash
# Run all tests
make test

# Run with coverage
make test-cov

# Run specific test file
uv run pytest tests/test_sanitize.py

# Run specific test
uv run pytest tests/test_sanitize.py::test_sanitize_subject
```

### Writing Tests

We use `pytest` with the following patterns:

```python
import pytest
from simple_email_gw import sanitize_subject


class TestSanitizeSubject:
  """Tests for subject line sanitization."""

  def test_removes_crlf(self):
    """CRLF sequences should be replaced with spaces."""
    result = sanitize_subject("Hello\r\nWorld")
    assert result == "Hello World"

  def test_removes_cr_only(self):
    """CR characters should be replaced with spaces."""
    result = sanitize_subject("Hello\rWorld")
    assert result == "Hello World"

  def test_handles_normal_text(self):
    """Normal text should pass through unchanged."""
    result = sanitize_subject("Normal subject")
    assert result == "Normal subject"
```

### Test Conventions

- Use descriptive test names
- Group related tests in classes
- Use `autouse=True` fixtures for setup
- Test both success and error paths

## MCP Server Development

### Running the Server

```bash
# Create .env file with credentials
cat > .env << EOF
EMAIL_IMAP_HOST=imap.gmail.com
EMAIL_SMTP_HOST=smtp.gmail.com
EMAIL_USERNAME=your-email@gmail.com
EMAIL_PASSWORD=your-app-password
EOF

# Run the MCP server
make mcp-server
```

### Adding New Tools

Add new MCP tools in `src/simple_email_gw/mcp.py`:

```python
@mcp.tool
async def new_tool(
  account: Annotated[str, Field(description="Account name")],
  ctx: Context = None,
) -> dict[str, str]:
  """Description of the new tool.

  Args:
    account: The account name.

  Returns:
    Dictionary with result.
  """
  if ctx:
    await ctx.info("Performing operation")

  try:
    pool = await get_pool()
    client = await pool.get_imap_client(account)
    # Do something
    return {"status": "ok"}
  except ValueError:
    raise ToolError(f"Account not found: {account}")
  except Exception:
    raise ToolError("Operation failed. Check server logs for details.")
```

## Building and Publishing

### Build Distribution

```bash
make build
```

This creates:
- `dist/simple_email_gw-0.1.0-py3-none-any.whl`
- `dist/simple_email_gw-0.1.0.tar.gz`

### Publish to PyPI

```bash
make publish
```

Requires PyPI credentials to be configured.

## Debugging

### Enable Debug Logging

```python
import logging

logging.basicConfig(level=logging.DEBUG)
```

### IMAP Debug Mode

```python
# Enable IMAP protocol logging
import aioimaplib
aioimaplib.log.setLevel(logging.DEBUG)
```

## Common Issues

### Import Errors

If you see import errors after adding new modules:

```bash
# Reinstall the package
make dev
```

### Test Failures

If tests fail after changes:

```bash
# Clean and reinstall
make clean
make dev
make test
```

### Type Checking Errors

If mypy reports errors:

1. Ensure all imports are properly typed
2. Check that return types match annotations
3. Run `uv run mypy src/ --show-error-codes` for details

## Contributing

1. Fork the repository
2. Create a feature branch: `git checkout -b feature/my-feature`
3. Make changes and add tests
4. Run all checks: `make all`
5. Commit with conventional format: `git commit -m "feat: add new feature"`
6. Push and create a pull request

### Commit Message Format

We use conventional commits:

- `feat:` New features
- `fix:` Bug fixes
- `docs:` Documentation changes
- `test:` Test changes
- `refactor:` Code refactoring
- `chore:` Maintenance tasks

## Release Process

1. Update version in `pyproject.toml` and `__init__.py`
2. Update CHANGELOG (if exists)
3. Run `make all` to verify
4. Build and publish: `make build publish`
5. Create git tag: `git tag v0.x.x`
6. Push tag: `git push origin v0.x.x`