Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ jobs:
with:
python-version: "3.12"
- run: pip install -e ".[dev]"
working-directory: services/core
- run: pytest -q
working-directory: services/core

publish:
needs: test
Expand Down Expand Up @@ -48,7 +50,8 @@ jobs:

- uses: docker/build-push-action@v6
with:
context: .
context: services/core
file: docker/Dockerfile.core
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
Expand All @@ -67,4 +70,7 @@ jobs:
python-version: "3.12"
- run: pip install build
- run: python -m build
working-directory: services/core
- uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: services/core/dist
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,11 @@
docker-data/
.venv/
__pycache__/
*.egg-info/
*.egg-info/

# Web UI
node_modules/*
web/.next/
web/.env.local

mailagent-data/
2 changes: 2 additions & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
nodejs 22.16.0
python 3.12.11
55 changes: 55 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
DEV_COMPOSE = docker compose -f deploy/dev/compose.dev.yaml

.PHONY: dev dev-backend dev-ui generate generate-schema generate-sdk-ts generate-sdk-py build build-core build-ui test lint help

# Development
dev: ## Start full dev stack (backend docker + UI)
$(MAKE) dev-backend &
$(MAKE) dev-ui

dev-backend: ## Start mailserver + daemon + API in Docker (with file watch)
@./deploy/dev/dev-seed.sh
$(DEV_COMPOSE) up --build --watch

dev-ui: ## Start the web UI dev server
cd services/ui && pnpm dev

# SDK generation
generate-schema: ## Extract OpenAPI spec from the Python app
cd services/core && uv run python3 -c " \
from mailagent.api import create_app; \
from mailagent.config import Config, ConfigManager, Defaults, Settings; \
import json; \
cm = ConfigManager(Config(providers={}, defaults=Defaults(classify_provider='', reply_provider=''), inboxes=[], settings=Settings()), '/dev/null'); \
spec = create_app(cm).openapi(); \
open('../../packages/sdk-ts/openapi.json','w').write(json.dumps(spec, indent=2)); \
open('../../packages/sdk-py/openapi.json','w').write(json.dumps(spec, indent=2))"

generate-sdk-ts: generate-schema ## Generate TypeScript SDK
cd packages/sdk-ts && pnpm generate

generate-sdk-py: generate-schema ## Generate Python SDK
cd packages/sdk-py && uv run openapi-python-client generate --path openapi.json --output-path src --overwrite

generate: generate-sdk-ts generate-sdk-py ## Regenerate all SDKs

# Build
build-core: ## Build the core Docker image
docker build -f docker/Dockerfile.core services/core -t mailagent

build-ui: ## Build the web UI Docker image
docker build -f docker/Dockerfile.ui . -t mailagent-web

build: build-core build-ui ## Build all Docker images

# Test
test: ## Run Python tests
cd services/core && uv run pytest -q

# Lint
lint: ## Run linters
cd services/core && uv run ruff check
cd services/ui && pnpm lint

help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,19 @@ See the full [configuration reference](docs/configuration.md) and [examples](exa
## Development

```bash
uv sync --dev
uv run pytest -q
docker build -t mailagent/mailagent:local .
cp deploy/dev/mailagent.env.example deploy/dev/mailagent.env # add your API keys
make dev # full stack: mailserver + daemon + API + web UI
```

Or run parts separately:

```bash
make dev-backend # mailserver + daemon + API (Docker)
make dev-ui # Next.js dev server
```

Test accounts (`test@dev.local`, `support@dev.local`) are seeded automatically on first run. Config and compose files are in [`deploy/dev/`](deploy/dev/).

## License

[MIT](LICENSE)
76 changes: 76 additions & 0 deletions deploy/dev/compose.dev.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
services:
mailserver:
image: ghcr.io/docker-mailserver/docker-mailserver:latest
container_name: mailserver
hostname: mail.dev.local
env_file: mailserver.env.dev
ports:
- "25:25"
- "143:143"
- "587:587"
volumes:
- mail-data:/var/mail/
- mail-state:/var/mail-state/
- mail-logs:/var/log/mail/
- ../../docker-data/dms/config/:/tmp/docker-mailserver/
healthcheck:
test: "ss --listening --ipv4 --tcp | grep --silent ':smtp' || exit 1"
timeout: 3s
retries: 0

mailagent:
image: ghcr.io/vrag99/mailagent:latest
container_name: mailagent
build:
context: ../../services/core
dockerfile: ../../docker/Dockerfile.core
command: ["run"]
env_file: mailagent.env
environment:
- MAILAGENT_ENV=dev
volumes:
- mail-data:/var/mail/:ro
- ./mailagent.dev.yml:/app/config.yml
- ../../mailagent-data/:/app/data/
depends_on:
mailserver:
condition: service_healthy
develop:
watch:
- action: sync+restart
path: ../../services/core/src
target: /app/src
- action: rebuild
path: ../../services/core/pyproject.toml

mailagent-api:
image: ghcr.io/vrag99/mailagent:latest
container_name: mailagent-api
build:
context: ../../services/core
dockerfile: ../../docker/Dockerfile.core
command: ["serve", "--host", "0.0.0.0", "--port", "8000", "--dms-config", "/app/dms-config"]
env_file: mailagent.env
environment:
- MAILAGENT_ENV=dev
ports:
- "8000:8000"
volumes:
- ./mailagent.dev.yml:/app/config.yml
- ../../mailagent-data/:/app/data/
- ../../docker-data/dms/config/:/app/dms-config/
depends_on:
mailserver:
condition: service_healthy
develop:
watch:
- action: sync+restart
path: ../../services/core/src
target: /app/src
- action: rebuild
path: ../../services/core/pyproject.toml

volumes:
mail-data:
mail-state:
mail-logs:
36 changes: 36 additions & 0 deletions deploy/dev/dev-seed.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Seed test accounts in docker-mailserver for local development.
#
# Uses `docker compose run --rm` so it works BEFORE the stack is up.
# Skips seeding if accounts already exist in postfix-accounts.cf.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
COMPOSE_FILE="${SCRIPT_DIR}/compose.dev.yaml"
ACCOUNTS_FILE="${SCRIPT_DIR}/../../docker-data/dms/config/postfix-accounts.cf"
DOMAIN="dev.local"

accounts=(
"test@${DOMAIN}:password123"
"support@${DOMAIN}:password123"
)

if [ -f "${ACCOUNTS_FILE}" ] && [ -s "${ACCOUNTS_FILE}" ]; then
echo "Dev accounts already exist:"
cut -d'|' -f1 < "${ACCOUNTS_FILE}"
exit 0
fi

mkdir -p "$(dirname "${ACCOUNTS_FILE}")"

echo "Seeding dev mailserver accounts..."
for entry in "${accounts[@]}"; do
email="${entry%%:*}"
pass="${entry##*:}"
echo " Adding ${email}..."
docker compose -f "${COMPOSE_FILE}" run --rm -T mailserver setup email add "${email}" "${pass}" 2>/dev/null || true
done

echo "Done:"
cut -d'|' -f1 < "${ACCOUNTS_FILE}" 2>/dev/null || echo " (could not read accounts file)"
51 changes: 51 additions & 0 deletions deploy/dev/mailagent.dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# mailagent dev configuration
# Used by compose.dev.yaml for local development.

providers:
fast:
type: groq
model: llama-3.3-70b-versatile
api_key: ${GROQ_API_KEY}

defaults:
classify_provider: fast
reply_provider: fast
system_prompt: |
You are a helpful email assistant (dev mode).

inboxes:
- address: test@dev.local
name: Test User
credentials:
password: password123
workflows:
- name: auto-reply
match:
intent: default
action:
type: reply
prompt: |
Send a brief, friendly reply acknowledging the email.

- address: support@dev.local
name: Support
credentials:
password: password123
workflows:
- name: support-reply
match:
intent: "customer support question or help request"
action:
type: reply
prompt: |
Reply helpfully to the support request.
- name: fallback
match:
intent: default
action:
type: ignore

settings:
mail_host: mailserver
catch_up_on_start: true
log_level: debug
5 changes: 5 additions & 0 deletions deploy/dev/mailagent.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Dev environment variables for mailagent containers.
# Copy to mailagent.env and fill in your API keys:
# cp deploy/dev/mailagent.env.example deploy/dev/mailagent.env

GROQ_API_KEY=your-groq-api-key-here
34 changes: 34 additions & 0 deletions deploy/dev/mailserver.env.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# -----------------------------------------------
# --- Dev Mailserver Environment Variables ------
# -----------------------------------------------
# Stripped-down docker-mailserver config for local development.
# No TLS, no spam filtering, no DKIM/DMARC/SPF.

OVERRIDE_HOSTNAME=mail.dev.local
LOG_LEVEL=info

# Allow containers on the same Docker network to send mail without auth
PERMIT_DOCKER=network

# Disable TLS (no certs needed for local dev)
SSL_TYPE=

# Disable all security/spam services
ENABLE_OPENDKIM=0
ENABLE_OPENDMARC=0
ENABLE_POLICYD_SPF=0
ENABLE_SPAMASSASSIN=0
ENABLE_CLAMAV=0
ENABLE_AMAVIS=0
ENABLE_FAIL2BAN=0
ENABLE_POSTGREY=0
ENABLE_SRS=0

# Keep IMAP enabled (needed by mailer.py)
ENABLE_IMAP=1

# Disable update checks
ENABLE_UPDATE_CHECK=0

# Use maildir format
DOVECOT_MAILBOX_FORMAT=maildir
Loading