Thanks for your interest in contributing to ownmail!
Note: This guide is for both human and AI contributors. For AI-specific guidelines, see .github/copilot-instructions.md.
# Clone the repo
git clone https://github.com/clee704/ownmail.git
cd ownmail
# Create a virtual environment
python3 -m venv venv
source venv/bin/activate
# Install in development mode
pip install -e ".[dev]"# Run directly
python3 -m ownmail --help
# Or after pip install -e .
ownmail --help- Follow PEP 8
- Use type hints where practical
- Keep functions focused and small
- Add docstrings for public methods
- No trailing whitespace
- Files must end with a newline (Linux style)
- Use LF line endings only, no CRLF (Linux style)
pytestWe enforce a coverage barrier to prevent regressions. Run tests with coverage:
pytest --cov=ownmail --cov-report=term-missingCurrent minimum coverage: 80% (configured in pyproject.toml).
When adding new code, write tests to maintain or improve coverage. The build will fail if coverage drops below the barrier.
Always run tests and lint before committing:
# Run lint check
ruff check .
# Run tests with coverage
pytest --cov=ownmail
# Or both together
ruff check . && pytestFix any lint errors before committing. Most can be auto-fixed with ruff check . --fix.
Use semantic commit messages with a clear, concise description:
<type>: <description>
[optional body]
| Type | Description |
|---|---|
feat |
New feature |
fix |
Bug fix |
docs |
Documentation changes |
test |
Adding or updating tests |
refactor |
Code refactoring (no functional change) |
perf |
Performance improvements |
chore |
Maintenance tasks (deps, CI, etc.) |
feat: add db-check command for database integrity
fix: use NOT IN instead of LEFT JOIN for FTS5 performance
docs: add detailed help messages for all commands
test: add unit tests for EmailParser
refactor: extract email parsing into separate class
perf: batch FTS deletes at end of reindex for 10x speedup
- Use imperative mood: "add feature" not "added feature"
- Keep first line under 72 characters
- Add body for complex changes explaining why, not just what
When changing the database schema:
We can change the schema freely since there are no published versions. Just update the CREATE TABLE statements in _init_db().
Once published, we must support migration from previous versions:
-
Adding a column: Use
ALTER TABLE ... ADD COLUMNwith try/except:try: conn.execute("ALTER TABLE emails ADD COLUMN new_column TEXT") except sqlite3.OperationalError: pass # Column already exists
-
Renaming a column: SQLite doesn't support
RENAME COLUMNin older versions. Create new, copy, drop old. -
Changing column type: Requires table rebuild (create new table, copy data, drop old, rename).
-
Always test migrations: Test with a database from the previous release.
For complex migrations, we may add a schema_version to sync_state:
conn.execute("INSERT OR REPLACE INTO sync_state VALUES ('schema_version', '2')")- Fork the repo and create a feature branch
- Make your changes with clear commit messages
- Update documentation if needed
- Add tests for new functionality
- Ensure all tests pass
- Submit a PR with a clear description
- Keep PRs self-contained — each PR should be logically independent and focused on one change
When contributing, keep these principles in mind:
- Files are the source of truth — The
.emlfiles are the archive. The database is just an index. - Fail gracefully — Handle malformed emails, network errors, etc. without crashing.
- Resumable operations — Long-running commands should be safe to Ctrl-C and resume.
- Minimal dependencies — Only add dependencies when truly necessary.
Open an issue for discussion before starting major changes.