From 55d9da3fb56cd9e8c87c06b2713fde415b57bfb8 Mon Sep 17 00:00:00 2001 From: James Pattinson Date: Sun, 12 Oct 2025 18:07:21 +0000 Subject: [PATCH] Initial commit: Postfix mail server with SES relay - Containerized Postfix configuration for mailing list management - Environment-based configuration for SES credentials - Template-based config generation for flexibility - Static virtual aliases (Phase 1) - Prepared for future web interface and SQL backend (Phase 2+) Features: - Docker Compose orchestration - Secure credential management via .env - Configurable SMTP host/port - Git-ignored sensitive files - Comprehensive documentation --- .env.example | 11 +++ .github/copilot-instructions.md | 97 +++++++++++++++++++++++++ .gitignore | 15 ++++ LICENSE | 21 ++++++ README.md | 124 ++++++++++++++++++++++++++++++++ docker-compose.yaml | 8 +++ postfix/Dockerfile | 25 +++++++ postfix/entrypoint.sh | 15 ++++ postfix/main.cf.template | 21 ++++++ postfix/sasl_passwd.template | 1 + postfix/virtual_aliases.cf | 1 + 11 files changed, 339 insertions(+) create mode 100644 .env.example create mode 100644 .github/copilot-instructions.md create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 docker-compose.yaml create mode 100644 postfix/Dockerfile create mode 100644 postfix/entrypoint.sh create mode 100644 postfix/main.cf.template create mode 100644 postfix/sasl_passwd.template create mode 100644 postfix/virtual_aliases.cf diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..3638bd0 --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +# SES/SMTP Configuration +# Copy this file to .env and fill in your actual credentials + +# Required: Your AWS SES credentials +SES_USER=your_ses_access_key_id +SES_PASS=your_ses_secret_access_key + +# Optional: SMTP server configuration +# Default is EU West 2 - change if using different region +SMTP_HOST=email-smtp.eu-west-2.amazonaws.com +SMTP_PORT=587 \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..1b6eafd --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,97 @@ +# Copilot Instructions: Mail List Manager (Postfix + SES + Web Interface) + +## Architecture Overview + +This is a containerized mailing list management system built around Postfix as an SMTP relay through Amazon SES. **Currently in Phase 1** with static configuration; **planned expansion** includes web frontend and SQL database for dynamic member management. + +**Current Components (Phase 1):** +- `docker-compose.yaml`: Single-service orchestration with SES credentials +- `postfix/`: Complete Postfix container configuration +- Static virtual aliases system for mailing list distribution + +**Planned Architecture (Phase 2+):** +- Web frontend for list management (view/add/remove members) +- SQL database backend for member storage +- Dynamic Postfix configuration generation +- Multi-service Docker Compose setup + +## Configuration Pattern + +**Current (Static):** Environment variable substitution for secure credential management: + +1. **Credentials Flow**: SES credentials passed via environment → `sasl_passwd.template` → runtime generation in `entrypoint.sh` +2. **Config Files**: Static configs (`main.cf`, `virtual_aliases.cf`) + dynamic SASL auth file +3. **Postfix Maps**: Hash databases generated at build time (virtual aliases) and runtime (SASL) + +**Future (Dynamic):** Database-driven configuration: +- Member lists stored in SQL database +- Web interface for CRUD operations on members +- `virtual_aliases.cf` generated from database at runtime +- Postfix reload triggered by configuration changes + +## Key Files and Their Roles + +- `main.cf`: Core Postfix config - relay through SES, domain settings, security +- `sasl_passwd.template`: Template for SES authentication (uses `${SES_USER}:${SES_PASS}`) +- `virtual_aliases.cf`: Static email forwarding rules (one mailing list currently) +- `entrypoint.sh`: Runtime credential processing and Postfix startup + +## Development Workflows + +**Building and Running:** +```bash +docker-compose up --build # Build and start mail server +docker-compose logs -f # Monitor mail delivery logs +``` + +**Adding Mailing Lists (Current):** +1. Edit `virtual_aliases.cf` with new list → recipients mapping +2. Rebuild container (postmap runs at build time) +3. No restart needed for existing virtual alias changes + +**Future Web Interface Workflow:** +1. Access web frontend at configured port +2. Use CRUD interface to manage mailing lists and members +3. Changes automatically update database and regenerate Postfix configs + +**Testing Mail Delivery:** +```bash +# From inside container +echo "Test message" | mail -s "Subject" community@lists.sasalliance.org + +# Check logs for SES relay status +docker-compose logs postfix | grep -E "(sent|bounced|deferred)" +``` + +## Security Considerations + +- SES credentials exposed in docker-compose.yaml (consider using Docker secrets) +- SASL password file permissions set to 600 in entrypoint +- TLS encryption enforced for SES relay (`smtp_tls_security_level = encrypt`) +- Only localhost and configured hostname accepted for local delivery + +## Configuration Conventions + +- **Hostname Pattern**: `lists.sasalliance.org` for mailing lists, origin domain `sasalliance.org` +- **Virtual Aliases**: Simple format `listname@lists.domain → recipient1, recipient2` +- **SES Region**: EU West 2 (`email-smtp.eu-west-2.amazonaws.com`) +- **Port Mapping**: Standard SMTP port 25 exposed to host + +## Common Modifications + +**Adding Recipients to Existing List (Current):** +Edit `virtual_aliases.cf`, add comma-separated emails, rebuild container. + +**New Mailing List (Current):** +Add line to `virtual_aliases.cf`: `newlist@lists.sasalliance.org recipients...` + +**Credential Updates:** +Update `SES_USER`/`SES_PASS` in docker-compose.yaml, restart container. + +## Migration Considerations + +When implementing the web frontend and database backend: +- Preserve existing `community@lists.sasalliance.org` list during migration +- Consider migration script to import current virtual aliases into database +- Plan for zero-downtime deployment with database-driven config generation +- Web interface should validate email addresses and handle duplicate members \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d10d8eb --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Environment variables with sensitive credentials +.env + +# Docker build artifacts +.dockerignore + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# OS files +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ffc18a7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Mail List Manager + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1e5996f --- /dev/null +++ b/README.md @@ -0,0 +1,124 @@ +# Mail List Manager + +A containerized mailing list management system built around Postfix as an SMTP relay through Amazon SES. + +## Architecture + +**Current (Phase 1):** Static configuration with environment-based credentials +- Postfix container configured as SES relay +- Static virtual aliases for mailing list distribution +- Environment variable configuration for security + +**Planned (Phase 2+):** Web interface with SQL backend +- Web frontend for list management (view/add/remove members) +- SQL database for member storage +- Dynamic Postfix configuration generation + +## Quick Start + +1. Copy the environment template: + ```bash + cp .env.example .env + ``` + +2. Edit `.env` with your SES credentials and configuration: + ```bash + # Required: Your SES credentials + SES_USER=your_ses_access_key + SES_PASS=your_ses_secret_key + + # Optional: SMTP configuration (defaults to EU West 2) + SMTP_HOST=email-smtp.eu-west-2.amazonaws.com + SMTP_PORT=587 + ``` + +3. Build and start the mail server: + ```bash + docker-compose up --build + ``` + +4. Test mail delivery: + ```bash + # From inside container + docker-compose exec postfix bash + echo "Test message" | mail -s "Subject" community@lists.sasalliance.org + + # Check logs + docker-compose logs -f postfix + ``` + +## Configuration + +### Adding Mailing Lists (Current) + +Edit `postfix/virtual_aliases.cf`: +``` +newlist@lists.sasalliance.org recipient1@domain.com, recipient2@domain.com +``` + +Then rebuild the container: +```bash +docker-compose up --build +``` + +### Domain Configuration + +The system is configured for: +- **Hostname**: `lists.sasalliance.org` (mailing lists) +- **Origin Domain**: `sasalliance.org` +- **SES Region**: EU West 2 (configurable via `SMTP_HOST`) + +## Security + +- SES credentials are stored in `.env` (git-ignored) +- SASL password files have restricted permissions (600) +- TLS encryption enforced for SES relay +- Only localhost and configured hostname accepted for local delivery + +## Development + +### Project Structure +``` +├── docker-compose.yaml # Service orchestration +├── .env # Environment configuration (not in git) +├── postfix/ +│ ├── Dockerfile # Postfix container build +│ ├── entrypoint.sh # Runtime configuration processing +│ ├── main.cf.template # Postfix main configuration template +│ ├── sasl_passwd.template # SES authentication template +│ └── virtual_aliases.cf # Static mailing list definitions +└── .github/ + └── copilot-instructions.md # AI agent guidance +``` + +### Environment Variables +- `SES_USER`: AWS SES access key ID +- `SES_PASS`: AWS SES secret access key +- `SMTP_HOST`: SMTP server hostname (default: email-smtp.eu-west-2.amazonaws.com) +- `SMTP_PORT`: SMTP server port (default: 587) + +### Debugging + +Monitor mail delivery: +```bash +# View all logs +docker-compose logs -f + +# Filter for delivery status +docker-compose logs postfix | grep -E "(sent|bounced|deferred)" + +# Check Postfix queue +docker-compose exec postfix postqueue -p +``` + +## Roadmap + +- [ ] Web frontend for mailing list management +- [ ] SQL database backend for member storage +- [ ] Dynamic configuration generation from database +- [ ] Multi-service Docker Compose architecture +- [ ] Migration tools for static → dynamic configuration + +## License + +MIT License - see LICENSE file for details. \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..cb6ebd1 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,8 @@ +version: "3.9" +services: + postfix: + build: ./postfix + container_name: postfix + env_file: .env + ports: + - "25:25" diff --git a/postfix/Dockerfile b/postfix/Dockerfile new file mode 100644 index 0000000..d6a40a5 --- /dev/null +++ b/postfix/Dockerfile @@ -0,0 +1,25 @@ +FROM debian:stable-slim + +# Install Postfix and tools +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + postfix \ + libsasl2-modules \ + mailutils \ + gettext-base \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Copy configs +COPY main.cf.template /etc/postfix/main.cf.template +COPY sasl_passwd.template /etc/postfix/sasl_passwd.template +COPY virtual_aliases.cf /etc/postfix/virtual_aliases.cf +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +# Generate Postfix maps for virtual aliases +RUN postmap /etc/postfix/virtual_aliases.cf + +# Expose SMTP +EXPOSE 25 + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/postfix/entrypoint.sh b/postfix/entrypoint.sh new file mode 100644 index 0000000..800b4a5 --- /dev/null +++ b/postfix/entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/sh +set -e + +# Generate main.cf from template with environment variables +envsubst < /etc/postfix/main.cf.template > /etc/postfix/main.cf + +# Generate SASL password file from environment variables +envsubst < /etc/postfix/sasl_passwd.template > /etc/postfix/sasl_passwd + +# Generate Postfix hash +postmap /etc/postfix/sasl_passwd +chmod 600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db + +# Start Postfix in foreground +exec postfix start-fg diff --git a/postfix/main.cf.template b/postfix/main.cf.template new file mode 100644 index 0000000..d4aa29a --- /dev/null +++ b/postfix/main.cf.template @@ -0,0 +1,21 @@ +# Basic +myhostname = lists.sasalliance.org +myorigin = sasalliance.org +mydestination = $myhostname, localhost.$mydomain, localhost + +# Relay through SES +relayhost = [${SMTP_HOST}]:${SMTP_PORT} +smtp_tls_security_level = encrypt +smtp_tls_note_starttls_offer = yes + +# SASL auth for SES +smtp_sasl_auth_enable = yes +smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd +smtp_sasl_security_options = noanonymous + +# Virtual aliases (static for now) +virtual_alias_maps = hash:/etc/postfix/virtual_aliases.cf + +# Other recommended settings +alias_maps = hash:/etc/aliases +alias_database = hash:/etc/aliases diff --git a/postfix/sasl_passwd.template b/postfix/sasl_passwd.template new file mode 100644 index 0000000..c7306c9 --- /dev/null +++ b/postfix/sasl_passwd.template @@ -0,0 +1 @@ +[${SMTP_HOST}]:${SMTP_PORT} ${SES_USER}:${SES_PASS} diff --git a/postfix/virtual_aliases.cf b/postfix/virtual_aliases.cf new file mode 100644 index 0000000..b21bd32 --- /dev/null +++ b/postfix/virtual_aliases.cf @@ -0,0 +1 @@ +community@lists.sasalliance.org james@pattinson.org, james.pattinson@sasalliance.org \ No newline at end of file