diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index db7fa94..880507e 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -1,9 +1,8 @@ name: Build and Push Docker Image - on: schedule: - - cron: '0 0 1 * *' # This runs at midnight on the first day of every month + - cron: "0 0 1 * *" # This runs at midnight on the first day of every month push: branches: - main @@ -13,21 +12,20 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_TOKEN }} + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} - - name: Build the Docker image - id: build - run: | - docker build . -t k2patel/postfix:latest - echo "IMAGE_NAME=k2patel/postfix:latest" >> $GITHUB_ENV - - - name: Push the Docker image - run: docker push ${{ env.IMAGE_NAME }} + - name: Build the Docker image + id: build + run: | + docker build . -t k2patel/postfix:latest + echo "IMAGE_NAME=k2patel/postfix:latest" >> $GITHUB_ENV + - name: Push the Docker image + run: docker push ${{ env.IMAGE_NAME }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..388612b --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# Environment files with credentials +.env +.env.local +.env.*.local + +# Data directories +data/ +logs/ +mail/ +spool/ + +# Docker volumes +/var/log/ +/var/mail/ +/var/spool/ + +# Backup files +*.bak +*.backup +*.old + +# OS files +.DS_Store +Thumbs.db +.Spotlight-V100 +.Trashes + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Temporary files +*.tmp +*.temp +.cache/ + +# Postfix specific +sasl_passwd +sasl_passwd.db +header_tag + +# Build artifacts +*.log diff --git a/Dockerfile b/Dockerfile index e109e09..c232170 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,67 +1,58 @@ -#Postfix relay -FROM docker.io/library/alpine:latest +FROM docker.io/library/alpine:3.19 -# Variables for Labels: -ARG VENDOR="k2patel" +# Variables for Labels +ARG VENDOR="your-org" ARG COMPONENT="postfix-relay" ARG BUILD_DATE -ARG GIT_REPO +ARG GIT_REPO="https://github.com/your-org/docker-postfix" ARG VCS_REF -ARG VERSION="1" +ARG VERSION="2.0" ARG NAME="dockerized-${COMPONENT}" -ARG DESCRIPTION="dockerized environment." -ARG DOCUMENTATION="https://git.k2patel.in" -ARG AUTHOR="k2patel" +ARG DESCRIPTION="Postfix SMTP relay supporting multiple providers (Maileroo, Mailtrap, SendGrid)" +ARG DOCUMENTATION="https://github.com/your-org/docker-postfix" +ARG AUTHOR="Your Name " ARG LICENSE="MIT" -# END Variables -######################################### +# Labels LABEL org.label-schema.build-date="${BUILD_DATE}" \ - org.label-schema.name="${NAME}" \ - org.label-schema.description="${DESCRIPTION}" \ - org.label-schema.vcs-ref="${VCS_REF}" \ - org.label-schema.vcs-url="${GIT_REPO}" \ - org.label-schema.url="${GIT_REPO}" \ - org.label-schema.vendor="${VENDOR}" \ - org.label-schema.version="${VERSION}" \ - org.label-schema.usage="${DOCUMENTATION}" + org.label-schema.name="${NAME}" \ + org.label-schema.description="${DESCRIPTION}" \ + org.label-schema.vcs-ref="${VCS_REF}" \ + org.label-schema.vcs-url="${GIT_REPO}" \ + org.label-schema.vendor="${VENDOR}" \ + org.label-schema.version="${VERSION}" -LABEL org.opencontainers.image.created="${BUILD_DATE}" \ - org.opencontainers.image.url="${GIT_REPO}" \ - org.opencontainers.image.source="${GIT_REPO}" \ - org.opencontainers.image.version="${VERSION}" \ - org.opencontainers.image.revision="${VCS_REF}" \ - org.opencontainers.image.vendor="${VENDOR}" \ - org.opencontainers.image.title="${NAME}" \ - org.opencontainers.image.description="${DESCRIPTION}" \ - org.opencontainers.image.documentation="${DOCUMENTATION}" \ - org.opencontainers.image.authors="${AUTHOR}" \ - org.opencontainers.image.licenses="${LICENSE}" -######################################### +# Install required packages +RUN apk add --no-cache \ + postfix \ + postfix-pcre \ + cyrus-sasl \ + cyrus-sasl-login \ + libsasl \ + ca-certificates \ + bash \ + tzdata \ + mailx -ENV TIMEZONE=${TIMEZONE} -ENV SMTP_SERVER=${SMTP_SERVER} -ENV SERVER_HOSTNAME=${SERVER_HOSTNAME} -ENV SMTP_PORT=${SMTP_PORT} -ENV DOMAIN=${DOMAIN} -ENV SMTP_USERNAME=${SMTP_USERNAME} -ENV SMTP_PASSWORD=${SMTP_PASSWORD} +# Create necessary directories +RUN mkdir -p /var/spool/postfix /etc/postfix/sasl -# Intall Needed Package -RUN apk add --no-cache postfix libsasl supervisor ca-certificates bash rsyslog cyrus-sasl - -#Configure nodaemon mode for supervisord -RUN sed -i -e "s/^nodaemon=false/nodaemon=true/" /etc/supervisord.conf -#Change postfix to listen on all the interface -RUN sed -i -e 's/inet_interfaces = localhost/inet_interfaces = all/g' /etc/postfix/main.cf - -COPY etc/ /etc/ -VOLUME ["/var/log", "/var/mail", "/var/spool/postfix"] -COPY run.sh / +# Copy entrypoint script +COPY run.sh /run.sh RUN chmod +x /run.sh + +# Initialize postfix RUN newaliases +# Expose SMTP port EXPOSE 25 -ENTRYPOINT /usr/bin/supervisord -c /etc/supervisor/supervisord.conf +# Volumes for persistence +VOLUME ["/var/spool/postfix"] +# Health check +HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=40s \ + CMD postfix status || exit 1 + +# Run postfix in foreground +ENTRYPOINT ["/run.sh"] diff --git a/README.md b/README.md index 43edbf5..9c0c95b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,462 @@ -# docker-postfix -Postfix relay for personal use +# Docker Postfix SMTP Relay + +A lightweight Postfix SMTP relay container supporting multiple email service providers including Maileroo, Mailtrap, SendGrid, and any generic SMTP server. + +## Features + +- 🚀 Multi-provider support (Maileroo, Mailtrap, SendGrid, Gmail, Office365, any SMTP) +- 🔒 TLS/SSL encryption +- 🌐 Auto-detects /16 subnet for relay +- 📝 All logs to stderr (`docker logs`) +- 🏥 Built-in health checks +- 🔧 Simple Makefile-based operations + +## Quick Start + +### 1. Clone and Configure + +```bash +git clone https://github.com/your-org/docker-postfix.git +cd docker-postfix +cp env.sample .env +nano .env # Edit with your SMTP provider details +``` + +### 2. Build and Start + +```bash +cd test +make build +make up +``` + +### 3. Test + +```bash +cd test +make test TO=your@email.com +``` + +## Configuration + +### Environment Variables + +| Variable | Required | Default | Description | +|----------|----------|---------|-------------| +| `SMTP_SERVER` | Yes | - | SMTP server hostname | +| `SMTP_PORT` | Yes | 587 | SMTP server port | +| `SMTP_USERNAME` | Yes | - | SMTP username | +| `SMTP_PASSWORD` | Yes | - | SMTP password | +| `DOMAIN` | Yes | - | Domain for outgoing mail | +| `SERVER_HOSTNAME` | No | Auto | Server FQDN | +| `TIMEZONE` | No | America/New_York | Timezone | +| `SMTP_NETWORKS` | No | Auto /16 | Additional networks (comma-separated CIDR) | +| `SMTP_LISTEN_PORT` | No | 25 | Port to expose on host | +| `LOCAL_NETWORK` | No | Auto-detected | Override local network CIDR (e.g., 192.168.0.0/16) | +| `DEBUG` | No | no | Enable debug logging | + +### Provider Examples + +#### Maileroo +```bash +SMTP_SERVER=smtp.maileroo.com +SMTP_PORT=587 +SMTP_USERNAME=noreply@example.com +SMTP_PASSWORD=your_password +DOMAIN=example.com +``` + +#### Mailtrap +```bash +SMTP_SERVER=live.smtp.mailtrap.io +SMTP_PORT=587 +SMTP_USERNAME=your_username +SMTP_PASSWORD=your_password +DOMAIN=example.com +``` + +#### SendGrid +```bash +SMTP_SERVER=smtp.sendgrid.net +SMTP_PORT=587 +SMTP_USERNAME=apikey +SMTP_PASSWORD=your_api_key +DOMAIN=example.com +``` + +**Note**: For SendGrid, username must be `apikey` (literal string). + +#### Gmail +```bash +SMTP_SERVER=smtp.gmail.com +SMTP_PORT=587 +SMTP_USERNAME=your_email@gmail.com +SMTP_PASSWORD=your_app_password +DOMAIN=gmail.com +``` + +## Makefile Commands + +All operations are handled through the Makefile in the `test/` directory. + +### Setup Commands + +```bash +cd test + +make build # Build the Docker image +make up # Start the container +make down # Stop the container +make restart # Restart the container +``` + +### Monitoring Commands + +```bash +make logs # View logs (follow mode) +make logs-tail # View last 100 lines +make status # Show container and Postfix status +make queue # View mail queue +make health # Check container health +``` + +### Maintenance Commands + +```bash +make shell # Open shell in container +make config # Show Postfix configuration +make test # Send test email (auto-detects container) +make flush # Flush mail queue +make check # Validate Postfix configuration +``` + +### Cleanup Commands + +```bash +make clean # Remove container and volumes +make clean-all # Remove everything including images +``` + +### Advanced Commands + +```bash +make debug # Start with debug mode enabled +make validate # Validate .env file +make queue-delete # Delete all messages in queue +make stats # Show real-time container stats +``` + +### Full Command List + +Run `make help` or just `make` to see all available commands: + +```bash +cd test +make +``` + +## Network Configuration + +The container automatically allows relay from: +- Localhost (127.0.0.0/8) +- Auto-detected /16 subnet + +To add custom networks: +```bash +SMTP_NETWORKS=192.168.1.0/24,10.0.0.0/8 +``` + +## Using with Applications + +Configure your application to use the relay: + +```yaml +services: + your-app: + environment: + MAIL_HOST: postfix-relay + MAIL_PORT: 25 + depends_on: + - postfix +``` + +Connection details: +- **Host**: `postfix-relay` +- **Port**: `25` +- **Authentication**: Not required for local network + +## Testing + +### Quick Test (Recommended) + +The easiest way to test your Postfix relay: + +```bash +cd test +make test TO=recipient@example.com +``` + +**That's it!** No configuration needed. + +### What Gets Auto-Detected? + +The test script automatically finds and configures: + +| Feature | What Happens | Override Option | +|---------|--------------|-----------------| +| **Container Name** | Finds containers using `docker-postfix-postfix` image | Specify as first arg | +| **FROM Address** | Uses `noreply@DOMAIN` from container env | `FROM=email@domain.com` | +| **SMTP Port** | Detects mapped port (e.g., `25` or custom) | N/A (always detected) | +| **Host IP** | Converts `0.0.0.0` → `127.0.0.1` for local testing | N/A (always detected) | +| **Subject** | Defaults to "Test Email from Postfix Relay" | `SUBJECT="Your Subject"` | + +**No manual configuration required** - just provide the recipient email! + +### Advanced Testing Options + +**With custom FROM address:** +```bash +make test TO=user@example.com FROM=noreply@mydomain.com +``` + +**With custom subject:** +```bash +make test TO=user@example.com FROM=sender@domain.com SUBJECT="My Test Email" +``` + +**Using the test script directly:** +```bash +./test-email.sh user@example.com +./test-email.sh user@example.com sender@mydomain.com +./test-email.sh user@example.com sender@mydomain.com "Custom Subject" +``` + +**Manual container specification (if auto-detection fails):** +```bash +./test-email.sh my-container user@example.com sender@domain.com +``` + +### Test Script Features + +The `test-email.sh` script automatically: +- Finds containers using `docker-postfix-postfix` image +- Detects port mapping and converts `0.0.0.0` to `127.0.0.1` +- Uses proper SMTP protocol via sendmail +- Falls back to mail command if needed +- Shows queue status after sending +- Provides colored output for easy reading + +### Validation Tests + +```bash +cd test +make check # Validate Postfix configuration +make validate # Validate .env file +make queue # View mail queue +make config # Show current Postfix config +``` + +## Troubleshooting + +### Email Not Received + +**1. Check container logs:** +```bash +cd test +make logs-tail # Last 100 lines +make logs # Follow in real-time +``` + +**2. Check mail queue:** +```bash +make queue # View queued messages +make flush # Force processing +``` + +**3. Verify configuration:** +```bash +make config # Show Postfix config +make check # Validate config +make validate # Validate .env file +``` + +### SMTP Protocol Errors + +If you see "improper command pipelining" errors: + +**Use the test script** (handles protocol correctly): +```bash +./test-email.sh user@example.com sender@domain.com +``` + +**Verify FROM domain matches DOMAIN setting:** +```bash +# In .env file +DOMAIN=mydomain.com + +# Use matching FROM address +make test TO=user@example.com FROM=noreply@mydomain.com +``` + +**Check mynetworks configuration:** +```bash +docker exec postfix-relay postconf mynetworks +``` + +### Container Not Auto-Detected + +**1. Verify container is running:** +```bash +docker ps | grep postfix +``` + +**2. Start the container:** +```bash +cd test +make up +``` + +**3. Manually specify container:** +```bash +./test-email.sh my-container-name user@example.com +``` + +### Enable Debug Mode + +For detailed SMTP transaction logs: +```bash +cd test +make debug +``` + +### Emails Stuck in Queue + +```bash +cd test +make queue # View queue +make flush # Force processing +make queue-delete # Delete all (with confirmation) +``` + +### Network Configuration Issues + +**Override auto-detected network:** +```bash +# In .env file +LOCAL_NETWORK=192.168.1.0/24 +``` + +**Add additional networks:** +```bash +SMTP_NETWORKS=10.0.0.0/8,172.16.0.0/12 +``` + +### Container Health Issues + +```bash +cd test +make status # Check status +make health # Check health status +make restart # Restart container +make down # Stop container +make up # Start fresh +``` + +## Examples + +### Testing Examples + +**Simple test with Gmail:** +```bash +cd test +make test TO=youraddress@gmail.com +# FROM will auto-detect from DOMAIN in .env +``` + +**Test with custom FROM and subject:** +```bash +make test TO=client@example.com FROM=support@mycompany.com SUBJECT="Production Test" +``` + +**Test from command line:** +```bash +./test-email.sh user@example.com noreply@mydomain.com "Hello World" +``` + +**Verify email was sent:** +```bash +make queue # Should show empty queue if sent +make logs-tail # Check for "status=sent" +``` + +### Basic Workflow + +```bash +# Initial setup +cd docker-postfix +cp env.sample .env +nano .env + +# Build and start +cd test +make build +make up + +# Check status +make status +make logs + +# Test email delivery +make test TO=your@email.com + +# Stop +make down +``` + +### Development Workflow + +```bash +cd test + +# Start with debug +make debug + +# Check logs in another terminal +make logs + +# Restart after changes +make restart + +# Clean up +make clean +``` + +### Monitoring Workflow + +```bash +cd test + +# Check everything +make status +make health +make queue +make logs-tail + +# Continuous monitoring +make logs +``` + +## Security + +- Store credentials in `.env` (never commit to git) +- Limit relay to trusted networks only +- TLS automatically enabled for known providers +- Use app-specific passwords for Gmail + +## License + +MIT License - See [LICENSE](LICENSE) file for details. + +## Support + +For issues and questions, please open an issue on GitHub. \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 7b6e79d..0f5a998 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,19 +1,48 @@ +version: "3.8" + services: postfix: - container_name: postfix - image: docker.io/k2patel/postfix:latest - ports: - - "10825:25/tcp" - environment: - TIMEZONE: ${TIMEZONE} - SMTP_SERVER: ${SMTP_SERVER} - SERVER_HOSTNAME: ${SERVER_HOSTNAME} - SMTP_PORT: ${SMTP_PORT} - DOMAIN: ${DOMAIN} - SMTP_USERNAME: ${SMTP_USERNAME} - SMTP_PASSWORD: ${SMTP_PASSWORD} - volumes: - - "${DATA_FOLDER}/logs:/var/log" - - "${DATA_FOLDER}/mail:/var/mail" - - "${DATA_FOLDER}/spool:/var/spool/postfix" + build: . + container_name: postfix-relay + hostname: ${SERVER_HOSTNAME:-postfix-relay} restart: unless-stopped + ports: + - "${SMTP_LISTEN_PORT:-25}:25" + environment: + - TIMEZONE=${TIMEZONE:-America/New_York} + - SMTP_SERVER=${SMTP_SERVER} + - SMTP_PORT=${SMTP_PORT:-587} + - SMTP_USERNAME=${SMTP_USERNAME} + - SMTP_PASSWORD=${SMTP_PASSWORD} + - DOMAIN=${DOMAIN} + - SERVER_HOSTNAME=${SERVER_HOSTNAME} + - SMTP_NETWORKS=${SMTP_NETWORKS} + - SMTP_HEADER_TAG=${SMTP_HEADER_TAG} + - DEBUG=${DEBUG:-no} + volumes: + - "${DATA_FOLDER:-.}/logs:/var/log" + - "${DATA_FOLDER:-.}/mail:/var/mail" + - "${DATA_FOLDER:-.}/spool:/var/spool/postfix" + networks: + - postfix-network + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + labels: "service=postfix-relay" + healthcheck: + test: ["CMD", "postfix", "status"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + +networks: + postfix-network: + driver: bridge + ipam: + driver: default + config: + - subnet: 172.28.0.0/16 + gateway: 172.28.0.1 diff --git a/env.sample b/env.sample index 3a67878..d5d165d 100644 --- a/env.sample +++ b/env.sample @@ -1,8 +1,111 @@ +# ============================================ +# Postfix SMTP Relay - Environment Variables +# ============================================ +# Copy this file to .env and configure your settings +# Supports: Maileroo, Mailtrap, SendGrid, and generic SMTP servers + +# ============================================ +# General Configuration +# ============================================ + +# Timezone (default: America/New_York) TIMEZONE='America/New_York' -SMTP_SERVER='smtp.example.com' -SERVER_HOSTNAME=server_name + +# Domain for outgoing mail +DOMAIN=example.com + +# Server hostname (optional - defaults to container hostname) +SERVER_HOSTNAME=mail.example.com + +# Port to expose on host (default: 25) +SMTP_LISTEN_PORT=25 + +# Data folder for logs, mail, and spool (default: current directory) +DATA_FOLDER=./data + +# Optional: Custom networks to allow relay (comma-separated CIDR notation) +# SMTP_NETWORKS=192.168.1.0/24,10.0.0.0/8 + +# Optional: Add custom header tag to relayed messages +# SMTP_HEADER_TAG=MyRelay + +# Debug mode (yes/no) - enables verbose logging +DEBUG=no + +# ============================================ +# SMTP Provider Configuration +# Choose ONE of the following configurations +# ============================================ + +# -------------------------------------------- +# Option 1: Maileroo (Current Configuration) +# -------------------------------------------- +SMTP_SERVER='smtp.maileroo.com' SMTP_PORT=587 -DOMAIN=smtp_domain -SMTP_USERNAME=apikey -SMTP_PASSWORD='api_key' -DATA_FOLDER='Local data folder' +SMTP_USERNAME=noreply@example.com +SMTP_PASSWORD=your_maileroo_password_here + +# -------------------------------------------- +# Option 2: Mailtrap +# -------------------------------------------- +# SMTP_SERVER='live.smtp.mailtrap.io' +# SMTP_PORT=587 +# SMTP_USERNAME=your_mailtrap_username +# SMTP_PASSWORD=your_mailtrap_password + +# For Mailtrap Sandbox (testing): +# SMTP_SERVER='sandbox.smtp.mailtrap.io' +# SMTP_PORT=2525 +# SMTP_USERNAME=your_mailtrap_sandbox_username +# SMTP_PASSWORD=your_mailtrap_sandbox_password + +# -------------------------------------------- +# Option 3: SendGrid +# -------------------------------------------- +# SMTP_SERVER='smtp.sendgrid.net' +# SMTP_PORT=587 +# SMTP_USERNAME=apikey +# SMTP_PASSWORD=your_sendgrid_api_key_here + +# -------------------------------------------- +# Option 4: Generic SMTP Server +# -------------------------------------------- +# SMTP_SERVER='smtp.example.com' +# SMTP_PORT=587 +# SMTP_USERNAME=your_username +# SMTP_PASSWORD=your_password + +# -------------------------------------------- +# Option 5: Gmail (requires app password) +# -------------------------------------------- +# SMTP_SERVER='smtp.gmail.com' +# SMTP_PORT=587 +# SMTP_USERNAME=your_email@gmail.com +# SMTP_PASSWORD=your_app_password + +# -------------------------------------------- +# Option 6: Office 365 / Outlook +# -------------------------------------------- +# SMTP_SERVER='smtp.office365.com' +# SMTP_PORT=587 +# SMTP_USERNAME=your_email@outlook.com +# SMTP_PASSWORD=your_password + +# ============================================ +# Notes +# ============================================ +# +# 1. The container automatically detects /16 subnet for relay +# Additional networks can be added via SMTP_NETWORKS +# +# 2. All logs are sent to stderr and visible via: +# docker logs postfix-relay +# +# 3. TLS is automatically enabled for known providers +# (Maileroo, Mailtrap, SendGrid) +# +# 4. Health check monitors Postfix status every 30 seconds +# +# 5. For SendGrid, username must be "apikey" (literal string) +# +# ============================================ diff --git a/run.sh b/run.sh old mode 100644 new mode 100755 index 7d0ef74..b045ef6 --- a/run.sh +++ b/run.sh @@ -1,9 +1,26 @@ #!/bin/bash +set -e + +# Redirect all output to stderr for docker logs +exec 1>&2 + +echo "========================================" +echo "Postfix SMTP Relay Configuration" +echo "========================================" + [ "${DEBUG}" == "yes" ] && set -x -/usr/lib/postfix/post-install meta_directory=/etc/postfix create-missing -/usr/lib/postfix/master +# Set timezone +if [ -n "${TIMEZONE}" ]; then + echo "Setting timezone to: ${TIMEZONE}" + cp /usr/share/zoneinfo/${TIMEZONE} /etc/localtime + echo "${TIMEZONE}" > /etc/timezone +fi + +# Initialize postfix directories (Alpine-specific) +# Postfix on Alpine doesn't use post-install in the same way +# The directories are already created during package installation function add_config_value() { local key=${1} @@ -12,77 +29,191 @@ function add_config_value() { [ "${key}" == "" ] && echo "ERROR: No key set !!" && exit 1 [ "${value}" == "" ] && echo "ERROR: No value set !!" && exit 1 - echo "Setting configuration option ${key} with value: ${value}" - postconf -e "${key} = ${value}" + echo " Setting: ${key} = ${value}" + postconf -e "${key} = ${value}" } -[ -z "${SMTP_SERVER}" ] && echo "SMTP_SERVER is not set" && exit 1 -[ -z "${SMTP_PORT}" ] && echo "SMTP_PORT is not set" && exit 1 -[ -z "${SMTP_USERNAME}" ] && echo "SMTP_USERNAME is not set" && exit 1 -[ -z "${SMTP_PASSWORD}" ] && echo "SMTP_PASSWORD is not set" && exit 1 +# Validate required environment variables +[ -z "${SMTP_SERVER}" ] && echo "ERROR: SMTP_SERVER is not set" && exit 1 +[ -z "${SMTP_PORT}" ] && echo "ERROR: SMTP_PORT is not set" && exit 1 +[ -z "${SMTP_USERNAME}" ] && echo "ERROR: SMTP_USERNAME is not set" && exit 1 +[ -z "${SMTP_PASSWORD}" ] && echo "ERROR: SMTP_PASSWORD is not set" && exit 1 -#Check for subnet restrictions -nets='127.0.0.1, 172.0.0.0/8, 10.0.0.0/8, 192.168.0.0/16' -if [ ! -z "${SMTP_NETWORKS}" ]; then - for i in $(sed 's/,/\ /g' <<<$SMTP_NETWORKS); do - if grep -Eq "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}" <<<$i ; then - nets+=", $i" - else - echo "$i is not in proper IPv4 subnet format. Ignoring." - fi - done +# Detect SMTP provider and set defaults +SMTP_PROVIDER="generic" +if [[ "${SMTP_SERVER}" == *"mailtrap.io"* ]]; then + SMTP_PROVIDER="mailtrap" + echo "Detected provider: Mailtrap" +elif [[ "${SMTP_SERVER}" == *"sendgrid"* ]]; then + SMTP_PROVIDER="sendgrid" + echo "Detected provider: SendGrid" +elif [[ "${SMTP_SERVER}" == *"maileroo"* ]]; then + SMTP_PROVIDER="maileroo" + echo "Detected provider: Maileroo" +else + echo "Using generic SMTP provider: ${SMTP_SERVER}" fi -# Set needed config options -add_config_value "myhostname" ${SERVER_HOSTNAME} -add_config_value "mydomain" ${DOMAIN} +# Get local network subnet - allow override via environment variable +if [ -z "${LOCAL_NETWORK}" ]; then + # Auto-detect and use /16 by default + LOCAL_NETWORK=$(ip route | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+' | head -1 | awk '{print $1}') + if [ -z "$LOCAL_NETWORK" ]; then + LOCAL_IP=$(hostname -i | grep -E -o '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1) + if [ -n "$LOCAL_IP" ]; then + # Extract first two octets for /16 network + NETWORK_PREFIX=$(echo $LOCAL_IP | cut -d. -f1-2) + LOCAL_NETWORK="${NETWORK_PREFIX}.0.0/16" + else + LOCAL_NETWORK="172.16.0.0/16" + fi + fi + echo "Auto-detected local network: ${LOCAL_NETWORK}" +else + echo "Using provided LOCAL_NETWORK: ${LOCAL_NETWORK}" +fi + +# Build mynetworks - always include localhost and detected local network +nets="127.0.0.0/8, [::1]/128, ${LOCAL_NETWORK}" + +# Add custom networks if specified +if [ ! -z "${SMTP_NETWORKS}" ]; then + echo "Adding custom networks..." + for i in $(sed 's/,/ /g' <<<$SMTP_NETWORKS); do + if grep -Eq "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}" <<<$i ; then + nets="${nets}, $i" + echo " Added network: $i" + else + echo " WARNING: $i is not in proper IPv4 subnet format. Ignoring." + fi + done +fi + +echo "" +echo "Allowed networks: ${nets}" +echo "" + +# Set hostname and domain defaults +SERVER_HOSTNAME_DEFAULT=$(hostname -f) +DOMAIN_DEFAULT=$(hostname -d) + +# Use provided values or defaults +SERVER_HOSTNAME=${SERVER_HOSTNAME:-${SERVER_HOSTNAME_DEFAULT}} +DOMAIN=${DOMAIN:-${DOMAIN_DEFAULT}} + +echo "Configuring Postfix..." +echo "----------------------------------------" + +# Basic configuration +add_config_value "myhostname" "${SERVER_HOSTNAME}" +add_config_value "mydomain" "${DOMAIN}" add_config_value "mydestination" '$myhostname' add_config_value "myorigin" '$mydomain' +add_config_value "mynetworks" "${nets}" +add_config_value "inet_interfaces" "all" +add_config_value "inet_protocols" "ipv4" + +# Relay configuration add_config_value "relayhost" "[${SMTP_SERVER}]:${SMTP_PORT}" +add_config_value "relay_domains" "*" + +# SASL Authentication add_config_value "smtp_sasl_auth_enable" "yes" add_config_value "smtp_sasl_password_maps" "lmdb:/etc/postfix/sasl_passwd" add_config_value "smtp_sasl_security_options" "noanonymous" add_config_value "smtp_sasl_tls_security_options" "noanonymous" -add_config_value "smtp_tls_security_level" "encrypt" +add_config_value "smtp_sasl_mechanism_filter" "plain, login" + +# TLS configuration +if [ "${SMTP_PROVIDER}" == "sendgrid" ] || [ "${SMTP_PROVIDER}" == "mailtrap" ] || [ "${SMTP_PROVIDER}" == "maileroo" ]; then + add_config_value "smtp_use_tls" "yes" + add_config_value "smtp_tls_security_level" "encrypt" + add_config_value "smtp_tls_CAfile" "/etc/ssl/certs/ca-certificates.crt" + add_config_value "smtp_tls_session_cache_database" "btree:\${data_directory}/smtp_scache" + add_config_value "smtp_tls_loglevel" "1" +else + add_config_value "smtp_tls_security_level" "may" +fi + +# Size limits add_config_value "header_size_limit" "4096000" -add_config_value "smtpd_sasl_local_domain" ${DOMAIN} -add_config_value "smtpd_recipient_restrictions" "permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination" -add_config_value "mailbox_size_limit" "256000000" +add_config_value "mailbox_size_limit" "0" +add_config_value "message_size_limit" "52428800" +add_config_value "recipient_delimiter" "+" -# Create sasl_passwd file with auth credentials -if [ ! -f /etc/postfix/sasl_passwd ]; then - grep -q "${SMTP_SERVER}" /etc/postfix/sasl_passwd > /dev/null 2>&1 - if [ $? -gt 0 ]; then - echo "Adding SASL authentication configuration" - echo "[${SMTP_SERVER}]:${SMTP_PORT} ${SMTP_USERNAME}:${SMTP_PASSWORD}" >> /etc/postfix/sasl_passwd - postmap /etc/postfix/sasl_passwd - fi +# Relay restrictions +add_config_value "smtpd_relay_restrictions" "permit_mynetworks, reject_unauth_destination" +add_config_value "smtpd_recipient_restrictions" "permit_mynetworks, reject_unauth_destination" +add_config_value "smtpd_sasl_local_domain" "${DOMAIN}" + +# Logging configuration - send logs to stdout/stderr +add_config_value "maillog_file" "/dev/stdout" +add_config_value "maillog_file_prefixes" "/var, /dev" + +# Queue settings +add_config_value "maximal_queue_lifetime" "1d" +add_config_value "bounce_queue_lifetime" "1d" +add_config_value "queue_run_delay" "300s" +add_config_value "minimal_backoff_time" "300s" +add_config_value "maximal_backoff_time" "4000s" + +# Debug settings (enable if DEBUG=yes) +if [ "${DEBUG}" == "yes" ]; then + add_config_value "debug_peer_level" "2" fi -#Set header tag +echo "----------------------------------------" +echo "" + +# Create SASL password file +echo "Configuring SASL authentication..." +echo "[${SMTP_SERVER}]:${SMTP_PORT} ${SMTP_USERNAME}:${SMTP_PASSWORD}" > /etc/postfix/sasl_passwd +chmod 600 /etc/postfix/sasl_passwd +postmap /etc/postfix/sasl_passwd +# Set permissions on the generated database file (may have different extensions) +chmod 600 /etc/postfix/sasl_passwd.* 2>/dev/null || true +echo " SASL credentials configured for ${SMTP_SERVER}:${SMTP_PORT}" +echo "" + +# Set header tag if specified if [ ! -z "${SMTP_HEADER_TAG}" ]; then - postconf -e "header_checks = regexp:/etc/postfix/header_tag" - echo -e "/^MIME-Version:/i PREPEND RelayTag: $SMTP_HEADER_TAG\n/^Content-Transfer-Encoding:/i PREPEND RelayTag: $SMTP_HEADER_TAG" > /etc/postfix/header_tag - echo "Setting configuration option SMTP_HEADER_TAG with value: ${SMTP_HEADER_TAG}" + echo "Setting SMTP header tag: ${SMTP_HEADER_TAG}" + postconf -e "header_checks = regexp:/etc/postfix/header_tag" + echo -e "/^MIME-Version:/i PREPEND RelayTag: $SMTP_HEADER_TAG\n/^Content-Transfer-Encoding:/i PREPEND RelayTag: $SMTP_HEADER_TAG" > /etc/postfix/header_tag + echo "" fi - -add_config_value "mynetworks" "${nets}" - -#Start services - -# If host mounting /var/spool/postfix, we need to delete old pid file before -# starting services +# Clean up old PID file if mounting /var/spool/postfix +echo "Cleaning up old PID files..." rm -f /var/spool/postfix/pid/master.pid +# Validate postfix configuration +echo "Validating Postfix configuration..." postconf -c /etc/postfix/ if [[ $? != 0 ]]; then - echo "Postfix configuration error, refusing to start." + echo "" + echo "========================================" + echo "ERROR: Postfix configuration error!" + echo "========================================" exit 1 -else - postfix -c /etc/postfix/ start - sleep 126144000 fi -ENTERYPOINT /usr/bin/supervisord -c /etc/supervisor/supervisord.conf +echo "" +echo "========================================" +echo "Postfix configuration completed successfully" +echo "========================================" +echo "Provider: ${SMTP_PROVIDER}" +echo "Relay host: ${SMTP_SERVER}:${SMTP_PORT}" +echo "Hostname: ${SERVER_HOSTNAME}" +echo "Domain: ${DOMAIN}" +echo "Networks: ${nets}" +echo "========================================" +echo "" + +# Start postfix in foreground mode +echo "Starting Postfix in foreground mode..." +echo "" + +# Run postfix in foreground - this will handle all logging to stdout/stderr +exec postfix start-fg diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..7004ded --- /dev/null +++ b/test/Makefile @@ -0,0 +1,188 @@ +.PHONY: help build up down restart logs shell test clean status queue config + +# Change to parent directory for docker-compose commands +COMPOSE=cd .. && docker-compose + +# Default target +help: + @echo "Docker Postfix SMTP Relay - Makefile Commands" + @echo "==============================================" + @echo "" + @echo "Setup and Build:" + @echo " make build - Build the Docker image" + @echo " make up - Start the container" + @echo " make down - Stop the container" + @echo " make restart - Restart the container" + @echo "" + @echo "Monitoring:" + @echo " make logs - View container logs (follow)" + @echo " make logs-tail - View last 100 lines" + @echo " make status - Show container and Postfix status" + @echo " make queue - View mail queue" + @echo " make health - Check container health" + @echo "" + @echo "Maintenance:" + @echo " make shell - Open shell in container" + @echo " make config - Show Postfix configuration" + @echo " make test - Send test email (auto-detects container)" + @echo " Usage: make test TO=user@example.com [FROM=sender@domain.com]" + @echo " make flush - Flush mail queue" + @echo " make check - Check Postfix configuration" + @echo "" + @echo "Cleanup:" + @echo " make clean - Remove container and volumes" + @echo " make clean-all - Remove everything including images" + @echo "" + @echo "Advanced:" + @echo " make debug - Start with debug mode enabled" + @echo " make validate - Validate environment file" + @echo " make stats - Show real-time stats" + @echo "" + +# Build the Docker image +build: + @echo "Building Docker image..." + @$(COMPOSE) build + +# Start the container +up: + @echo "Starting Postfix relay..." + @$(COMPOSE) up -d + @echo "Waiting for container to be healthy..." + @sleep 5 + @make status + +# Stop the container +down: + @echo "Stopping Postfix relay..." + @$(COMPOSE) down + +# Restart the container +restart: down up + +# View logs +logs: + @$(COMPOSE) logs -f + +# View last 100 lines of logs +logs-tail: + @$(COMPOSE) logs --tail=100 + +# Open shell in container +shell: + @$(COMPOSE) exec postfix bash + +# Show container and Postfix status +status: + @echo "Container Status:" + @echo "=================" + @docker ps --filter name=postfix-relay --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" + @echo "" + @echo "Postfix Status:" + @echo "===============" + @$(COMPOSE) exec postfix postfix status || echo "Container not running" + +# View mail queue +queue: + @echo "Mail Queue:" + @echo "===========" + @$(COMPOSE) exec postfix postqueue -p + +# Flush mail queue +flush: + @echo "Flushing mail queue..." + @$(COMPOSE) exec postfix postqueue -f + @echo "Queue flushed" + +# View Postfix configuration +config: + @echo "Postfix Configuration:" + @echo "=====================" + @$(COMPOSE) exec postfix postconf -n + +# Check Postfix configuration +check: + @echo "Checking Postfix configuration..." + @$(COMPOSE) exec postfix postfix check + @$(COMPOSE) exec postfix postconf -c /etc/postfix/ + @echo "Configuration check complete" + +# Send test email +test: + @if [ -z "$(TO)" ]; then \ + echo "Usage: make test TO=recipient@example.com [FROM=sender@domain.com] [SUBJECT='Test Email']"; \ + echo ""; \ + echo "Examples:"; \ + echo " make test TO=user@example.com"; \ + echo " make test TO=user@example.com FROM=noreply@mydomain.com"; \ + echo " make test TO=user@example.com FROM=sender@domain.com SUBJECT='My Test'"; \ + echo ""; \ + echo "Note: Container is auto-detected from docker-postfix-postfix image"; \ + echo " FROM defaults to noreply@ if not specified"; \ + exit 1; \ + fi + @if [ -n "$(CONTAINER)" ]; then \ + ./test-email.sh $(CONTAINER) $(TO) $(FROM) $(SUBJECT); \ + else \ + ./test-email.sh $(TO) $(FROM) $(SUBJECT); \ + fi + +# Validate environment file +validate: + @echo "Validating .env file..." + @if [ ! -f ../.env ]; then \ + echo "ERROR: .env file not found. Copy env.sample to .env in parent directory"; \ + exit 1; \ + fi + @grep -q "SMTP_SERVER=" ../.env || (echo "ERROR: SMTP_SERVER not set" && exit 1) + @grep -q "SMTP_PORT=" ../.env || (echo "ERROR: SMTP_PORT not set" && exit 1) + @grep -q "SMTP_USERNAME=" ../.env || (echo "ERROR: SMTP_USERNAME not set" && exit 1) + @grep -q "SMTP_PASSWORD=" ../.env || (echo "ERROR: SMTP_PASSWORD not set" && exit 1) + @grep -q "DOMAIN=" ../.env || (echo "ERROR: DOMAIN not set" && exit 1) + @echo "Environment file validation passed" + +# Clean up (remove container and volumes) +clean: + @echo "Cleaning up containers and volumes..." + @$(COMPOSE) down -v + @echo "Cleaning local data directories..." + @cd .. && rm -rf data logs mail spool + @echo "Cleanup complete" + +# Clean everything including images +clean-all: clean + @echo "Removing Docker images..." + @$(COMPOSE) down --rmi all + @echo "Complete cleanup finished" + +# Show queue details +queue-detail: + @echo "Detailed Queue Information:" + @echo "==========================" + @$(COMPOSE) exec postfix sh -c "postqueue -p | tail -n 1" + +# Delete all queued messages +queue-delete: + @echo "WARNING: This will delete ALL messages in the queue!" + @read -p "Are you sure? (yes/no): " confirm; \ + if [ "$$confirm" = "yes" ]; then \ + $(COMPOSE) exec postfix postsuper -d ALL; \ + echo "All queued messages deleted"; \ + else \ + echo "Cancelled"; \ + fi + +# Enable debug mode +debug: + @echo "Restarting with DEBUG mode enabled..." + @$(COMPOSE) down + @cd .. && DEBUG=yes docker-compose up + +# Show real-time statistics +stats: + @docker stats postfix-relay + +# Run health check +health: + @echo "Running health check..." + @docker inspect postfix-relay --format='{{.State.Health.Status}}' 2>/dev/null || echo "Container not running" diff --git a/test/test-email.sh b/test/test-email.sh new file mode 100755 index 0000000..d8361e3 --- /dev/null +++ b/test/test-email.sh @@ -0,0 +1,200 @@ +#!/bin/bash + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Parse arguments flexibly +# Supports: recipient [from] [subject] +# Or: container recipient [from] [subject] +CONTAINER_NAME="" +RECIPIENT="" +FROM_EMAIL="" +SUBJECT="Test Email from Postfix Relay" + +# Check if first argument looks like an email +if [[ "$1" =~ @.*\. ]]; then + # First arg is email - it's the recipient + RECIPIENT="${1}" + FROM_EMAIL="${2}" + SUBJECT="${3:-Test Email from Postfix Relay}" +else + # First arg is not email - assume it's container name + if [ -n "$1" ]; then + CONTAINER_NAME="${1}" + RECIPIENT="${2}" + FROM_EMAIL="${3}" + SUBJECT="${4:-Test Email from Postfix Relay}" + fi +fi + +# Function to print colored messages +print_error() { + echo -e "${RED}ERROR: $1${NC}" >&2 +} + +print_success() { + echo -e "${GREEN}SUCCESS: $1${NC}" +} + +print_info() { + echo -e "${YELLOW}INFO: $1${NC}" +} + +# Show usage if recipient is not provided +if [ -z "$RECIPIENT" ]; then + echo "Usage: $0 RECIPIENT_EMAIL [FROM_EMAIL] [SUBJECT]" + echo " or: $0 CONTAINER_NAME RECIPIENT_EMAIL [FROM_EMAIL] [SUBJECT]" + echo "" + echo "Arguments:" + echo " RECIPIENT_EMAIL - Email address to send test email to (required)" + echo " FROM_EMAIL - From email address (optional, default: noreply@)" + echo " SUBJECT - Email subject (default: 'Test Email from Postfix Relay')" + echo " CONTAINER_NAME - Docker container name (optional, auto-detected)" + echo "" + echo "Examples:" + echo " $0 user@example.com" + echo " $0 user@example.com sender@mydomain.com" + echo " $0 user@example.com sender@mydomain.com 'My Test Email'" + echo " $0 my-postfix user@example.com sender@mydomain.com" + echo "" + exit 1 +fi + +# Auto-detect container name if not provided +if [ -z "$CONTAINER_NAME" ]; then + print_info "Auto-detecting container..." + + # Try to find container by image name pattern + CONTAINER_NAME=$(docker ps --filter "ancestor=docker-postfix-postfix" --format '{{.Names}}' | head -n 1) + + # Fallback: try common naming patterns + if [ -z "$CONTAINER_NAME" ]; then + CONTAINER_NAME=$(docker ps --filter "name=postfix" --format '{{.Names}}' | head -n 1) + fi + + if [ -z "$CONTAINER_NAME" ]; then + print_error "Could not auto-detect container. Please specify container name." + echo "" + echo "Available containers:" + docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}" + echo "" + echo "Usage: $0 CONTAINER_NAME RECIPIENT_EMAIL" + exit 1 + fi + + print_success "Detected container: ${CONTAINER_NAME}" +fi + +# Check if container exists +if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + print_error "Container '${CONTAINER_NAME}' is not running" + echo "" + echo "Available containers:" + docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" + exit 1 +fi + +print_info "Container: ${CONTAINER_NAME}" + +# Get the port mapping for the container +PORT_MAPPING=$(docker port "${CONTAINER_NAME}" 25 2>/dev/null | head -n 1) + +if [ -z "$PORT_MAPPING" ]; then + print_error "Could not determine port mapping for container '${CONTAINER_NAME}'" + exit 1 +fi + +# Extract host and port from mapping (format: 0.0.0.0:25 or 127.0.0.1:25) +HOST=$(echo "$PORT_MAPPING" | cut -d: -f1) +PORT=$(echo "$PORT_MAPPING" | cut -d: -f2) + +# Convert 0.0.0.0 to 127.0.0.1 for local communication +if [ "$HOST" = "0.0.0.0" ]; then + HOST="127.0.0.1" +fi + +# Set default FROM email if not provided +if [ -z "$FROM_EMAIL" ]; then + # Try to get domain from container environment or use hostname + DOMAIN=$(docker exec "$CONTAINER_NAME" printenv DOMAIN 2>/dev/null || echo "relay.local") + FROM_EMAIL="noreply@${DOMAIN}" +fi + +print_info "SMTP Server: ${HOST}:${PORT}" +print_info "From: ${FROM_EMAIL}" +print_info "Recipient: ${RECIPIENT}" +print_info "Subject: ${SUBJECT}" +echo "" + +# Generate email body with timestamp +TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S %Z") +EMAIL_BODY="This is a test email sent from the Postfix SMTP relay. + +Container: ${CONTAINER_NAME} +Sent at: ${TIMESTAMP} +SMTP Server: ${HOST}:${PORT} + +If you received this email, your Postfix relay is working correctly! + +--- +Automated test email" + +print_info "Sending test email..." +echo "" + +# Method 1: Try using sendmail directly (most reliable for Postfix) +print_info "Attempting to send via sendmail..." + +SENDMAIL_OUTPUT=$(docker exec -i "$CONTAINER_NAME" sendmail -v -f "$FROM_EMAIL" "$RECIPIENT" 2>&1 << EOFMAIL +From: ${FROM_EMAIL} +To: ${RECIPIENT} +Subject: ${SUBJECT} + +${EMAIL_BODY} +EOFMAIL +) +SENDMAIL_EXIT=$? + +if [ $SENDMAIL_EXIT -eq 0 ]; then + print_success "Email queued successfully via sendmail" + echo "$SENDMAIL_OUTPUT" | grep -i "queued\|sent" || true +else + print_error "Sendmail method failed" + echo "Output: $SENDMAIL_OUTPUT" + echo "" + + # Method 2: Try using mailx/mail command + print_info "Trying alternative method using mail command..." + + MAIL_OUTPUT=$(docker exec "$CONTAINER_NAME" sh -c " + echo '${EMAIL_BODY}' | mail -v -s '${SUBJECT}' -r '${FROM_EMAIL}' '${RECIPIENT}' 2>&1 + ") + MAIL_EXIT=$? + + if [ $MAIL_EXIT -eq 0 ]; then + print_success "Email queued successfully via mail command" + else + print_error "Mail command failed" + echo "Output: $MAIL_OUTPUT" + echo "" + print_info "Check container logs for more details:" + echo " docker logs $CONTAINER_NAME" + exit 1 + fi +fi + +echo "" +print_info "Checking mail queue..." +docker exec "$CONTAINER_NAME" postqueue -p 2>/dev/null || echo "Queue check not available" + +echo "" +print_success "Test complete! Check the recipient mailbox for the test email." +echo "" +print_info "You can monitor the logs with:" +echo " docker logs -f ${CONTAINER_NAME}" +echo "" diff --git a/test/test.sh b/test/test.sh new file mode 100755 index 0000000..6d73ff0 --- /dev/null +++ b/test/test.sh @@ -0,0 +1,353 @@ +#!/bin/bash + +# Postfix SMTP Relay Test Script +# This script validates the Postfix relay configuration and sends test emails + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Functions +print_header() { + echo -e "\n${BLUE}========================================${NC}" + echo -e "${BLUE}$1${NC}" + echo -e "${BLUE}========================================${NC}\n" +} + +print_success() { + echo -e "${GREEN}✓ $1${NC}" +} + +print_error() { + echo -e "${RED}✗ $1${NC}" +} + +print_warning() { + echo -e "${YELLOW}⚠ $1${NC}" +} + +print_info() { + echo -e "${BLUE}ℹ $1${NC}" +} + +# Check if .env file exists +check_env() { + print_header "Checking Environment Configuration" + + if [ ! -f .env ]; then + print_error ".env file not found" + print_info "Copy env.sample to .env and configure it" + exit 1 + fi + print_success ".env file found" + + # Source the .env file + source .env + + # Check required variables + local required_vars=("SMTP_SERVER" "SMTP_PORT" "SMTP_USERNAME" "SMTP_PASSWORD" "DOMAIN") + local missing_vars=0 + + for var in "${required_vars[@]}"; do + if [ -z "${!var}" ]; then + print_error "$var is not set" + missing_vars=$((missing_vars + 1)) + else + print_success "$var is set" + fi + done + + if [ $missing_vars -gt 0 ]; then + print_error "$missing_vars required variable(s) missing" + exit 1 + fi + + print_info "Provider: $SMTP_SERVER:$SMTP_PORT" + print_info "Domain: $DOMAIN" +} + +# Check if Docker is running +check_docker() { + print_header "Checking Docker" + + if ! command -v docker &> /dev/null; then + print_error "Docker is not installed" + exit 1 + fi + print_success "Docker is installed" + + if ! docker info &> /dev/null; then + print_error "Docker daemon is not running" + exit 1 + fi + print_success "Docker daemon is running" + + if ! command -v docker-compose &> /dev/null; then + print_error "Docker Compose is not installed" + exit 1 + fi + print_success "Docker Compose is installed" +} + +# Check container status +check_container() { + print_header "Checking Container Status" + + if ! docker ps --filter name=postfix-relay --format '{{.Names}}' | grep -q postfix-relay; then + print_warning "Container is not running" + print_info "Starting container..." + docker-compose up -d + sleep 10 + fi + + if docker ps --filter name=postfix-relay --format '{{.Names}}' | grep -q postfix-relay; then + print_success "Container is running" + else + print_error "Failed to start container" + exit 1 + fi + + # Check health status + health_status=$(docker inspect postfix-relay --format='{{.State.Health.Status}}' 2>/dev/null || echo "unknown") + if [ "$health_status" = "healthy" ]; then + print_success "Container is healthy" + elif [ "$health_status" = "starting" ]; then + print_warning "Container is starting..." + print_info "Waiting for health check..." + sleep 10 + else + print_warning "Health status: $health_status" + fi +} + +# Check Postfix status +check_postfix() { + print_header "Checking Postfix Status" + + if docker exec postfix-relay postfix status &> /dev/null; then + print_success "Postfix is running" + else + print_error "Postfix is not running" + print_info "Checking logs..." + docker logs --tail 50 postfix-relay + exit 1 + fi +} + +# Check network connectivity +check_network() { + print_header "Checking Network Connectivity" + + source .env + + # Check if nc (netcat) is available in container + if docker exec postfix-relay which nc &> /dev/null; then + if docker exec postfix-relay nc -zv ${SMTP_SERVER} ${SMTP_PORT} 2>&1 | grep -q succeeded; then + print_success "Can connect to ${SMTP_SERVER}:${SMTP_PORT}" + else + print_error "Cannot connect to ${SMTP_SERVER}:${SMTP_PORT}" + print_info "Check firewall and network settings" + fi + else + print_warning "netcat not available, skipping connectivity test" + fi +} + +# Check Postfix configuration +check_config() { + print_header "Validating Postfix Configuration" + + if docker exec postfix-relay postfix check 2>&1 | grep -q error; then + print_error "Postfix configuration has errors" + docker exec postfix-relay postfix check + exit 1 + else + print_success "Postfix configuration is valid" + fi + + # Show key configuration + print_info "Key Configuration Values:" + docker exec postfix-relay postconf -n | grep -E "^(myhostname|mydomain|relayhost|mynetworks)" | while read line; do + echo " $line" + done +} + +# Check mail queue +check_queue() { + print_header "Checking Mail Queue" + + queue_output=$(docker exec postfix-relay postqueue -p) + + if echo "$queue_output" | grep -q "Mail queue is empty"; then + print_success "Mail queue is empty" + else + print_warning "Mail queue has messages:" + echo "$queue_output" + fi +} + +# Send test email +send_test_email() { + print_header "Sending Test Email" + + if [ -z "$1" ]; then + read -p "Enter recipient email address: " recipient + else + recipient="$1" + fi + + if [ -z "$recipient" ]; then + print_error "No recipient provided" + return 1 + fi + + # Validate email format + if ! echo "$recipient" | grep -qE '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'; then + print_error "Invalid email address format" + return 1 + fi + + print_info "Sending test email to: $recipient" + + timestamp=$(date '+%Y-%m-%d %H:%M:%S') + hostname=$(docker exec postfix-relay hostname) + + test_message="Test email from Postfix SMTP Relay + +Timestamp: $timestamp +Hostname: $hostname +Container: postfix-relay + +If you receive this email, your Postfix relay is working correctly! +" + + if docker exec postfix-relay sh -c "echo '$test_message' | mail -s 'Postfix Relay Test - $timestamp' $recipient" 2>&1; then + print_success "Test email sent to $recipient" + print_info "Check the mail queue and logs:" + echo "" + docker exec postfix-relay postqueue -p + else + print_error "Failed to send test email" + return 1 + fi +} + +# Show logs +show_logs() { + print_header "Recent Logs" + docker logs --tail 50 postfix-relay +} + +# Full test suite +run_full_test() { + print_header "Running Full Test Suite" + + check_env + check_docker + check_container + check_postfix + check_network + check_config + check_queue + + print_header "Test Summary" + print_success "All checks passed!" + print_info "Container is ready to relay emails" + + echo "" + read -p "Do you want to send a test email? (y/n): " send_test + if [ "$send_test" = "y" ] || [ "$send_test" = "Y" ]; then + send_test_email + fi +} + +# Main menu +show_menu() { + echo "" + echo -e "${BLUE}Postfix SMTP Relay Test Menu${NC}" + echo "================================" + echo "1) Run full test suite" + echo "2) Check environment" + echo "3) Check container status" + echo "4) Check Postfix configuration" + echo "5) Check mail queue" + echo "6) Send test email" + echo "7) Show logs" + echo "8) Exit" + echo "" + read -p "Select option: " option + + case $option in + 1) run_full_test ;; + 2) check_env ;; + 3) check_container && check_postfix ;; + 4) check_config ;; + 5) check_queue ;; + 6) send_test_email ;; + 7) show_logs ;; + 8) exit 0 ;; + *) print_error "Invalid option"; show_menu ;; + esac +} + +# Parse command line arguments +if [ $# -eq 0 ]; then + # No arguments, show menu + show_menu +else + case "$1" in + --full|-f) + run_full_test + ;; + --email|-e) + if [ -n "$2" ]; then + send_test_email "$2" + else + send_test_email + fi + ;; + --check|-c) + check_env + check_docker + check_container + check_postfix + check_config + ;; + --logs|-l) + show_logs + ;; + --queue|-q) + check_queue + ;; + --help|-h) + echo "Postfix SMTP Relay Test Script" + echo "" + echo "Usage: $0 [option] [arguments]" + echo "" + echo "Options:" + echo " -f, --full Run full test suite" + echo " -c, --check Run configuration checks" + echo " -e, --email [address] Send test email" + echo " -q, --queue Check mail queue" + echo " -l, --logs Show recent logs" + echo " -h, --help Show this help message" + echo "" + echo "Examples:" + echo " $0 # Interactive menu" + echo " $0 --full # Run all tests" + echo " $0 --email user@example.com # Send test email" + echo " $0 --check # Check configuration" + echo "" + ;; + *) + print_error "Unknown option: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac +fi