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
This commit is contained in:
James Pattinson
2025-10-12 18:07:21 +00:00
commit 55d9da3fb5
11 changed files with 339 additions and 0 deletions

11
.env.example Normal file
View File

@@ -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

97
.github/copilot-instructions.md vendored Normal file
View File

@@ -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

15
.gitignore vendored Normal file
View File

@@ -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

21
LICENSE Normal file
View File

@@ -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.

124
README.md Normal file
View File

@@ -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.

8
docker-compose.yaml Normal file
View File

@@ -0,0 +1,8 @@
version: "3.9"
services:
postfix:
build: ./postfix
container_name: postfix
env_file: .env
ports:
- "25:25"

25
postfix/Dockerfile Normal file
View File

@@ -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"]

15
postfix/entrypoint.sh Normal file
View File

@@ -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

21
postfix/main.cf.template Normal file
View File

@@ -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

View File

@@ -0,0 +1 @@
[${SMTP_HOST}]:${SMTP_PORT} ${SES_USER}:${SES_PASS}

View File

@@ -0,0 +1 @@
community@lists.sasalliance.org james@pattinson.org, james.pattinson@sasalliance.org