Development Guide¶
This document provides information for developers contributing to simple-email-gw.
Prerequisites¶
Python 3.11 or higher
uv package manager
Getting Started¶
Clone and Install¶
git clone https://github.com/christophevg/simple-email-gw.git
cd simple-email-gw
# Install development dependencies
make dev
Run Tests¶
make test
Run Linter¶
make lint
Type Check¶
make typecheck
Run All Checks¶
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 formatto auto-format
make format
Linting¶
We use ruff for linting. Run it with:
make lint
Type Annotations¶
All public functions should have type annotations. We use mypy for type checking:
make typecheck
Testing¶
Running Tests¶
# 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:
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=Truefixtures for setupTest both success and error paths
MCP Server Development¶
Running the Server¶
# 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:
@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¶
make build
This creates:
dist/simple_email_gw-0.1.0-py3-none-any.whldist/simple_email_gw-0.1.0.tar.gz
Publish to PyPI¶
make publish
Requires PyPI credentials to be configured.
Debugging¶
Enable Debug Logging¶
import logging
logging.basicConfig(level=logging.DEBUG)
IMAP Debug Mode¶
# Enable IMAP protocol logging
import aioimaplib
aioimaplib.log.setLevel(logging.DEBUG)
Common Issues¶
Import Errors¶
If you see import errors after adding new modules:
# Reinstall the package
make dev
Test Failures¶
If tests fail after changes:
# Clean and reinstall
make clean
make dev
make test
Type Checking Errors¶
If mypy reports errors:
Ensure all imports are properly typed
Check that return types match annotations
Run
uv run mypy src/ --show-error-codesfor details
Contributing¶
Fork the repository
Create a feature branch:
git checkout -b feature/my-featureMake changes and add tests
Run all checks:
make allCommit with conventional format:
git commit -m "feat: add new feature"Push and create a pull request
Commit Message Format¶
We use conventional commits:
feat:New featuresfix:Bug fixesdocs:Documentation changestest:Test changesrefactor:Code refactoringchore:Maintenance tasks
Release Process¶
Update version in
pyproject.tomland__init__.pyUpdate CHANGELOG (if exists)
Run
make allto verifyBuild and publish:
make build publishCreate git tag:
git tag v0.x.xPush tag:
git push origin v0.x.x