Milestone #78
Updated by Daniele Cruciani about 1 month ago
Setting up a mail server on Kubernetes that uses Gmail as a relay while maintaining good deliverability requires careful configuration. Here's a step-by-step guide:
## 1. **Prerequisites**
First, enable **"Less Secure Apps"** or better, create an **App Password**:
1. Enable 2FA on your Google account
2. Generate an App Password: `Security > App passwords > Generate`
3. Note down the generated 16-character password
## 2. **Kubernetes Deployment**
### **Mail Server Configuration (Postfix)**
```yaml
# postfix-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: postfix
spec:
replicas: 1
selector:
matchLabels:
app: postfix
template:
metadata:
labels:
app: postfix
spec:
containers:
- name: postfix
image: catatnight/postfix:latest
env:
- name: maildomain
value: "yourdomain.com" # Your domain
- name: smtp_user
value: "your-email@gmail.com:your-app-password"
- name: relayhost
value: "[smtp.gmail.com]:587"
ports:
- containerPort: 25
- containerPort: 587
- containerPort: 465
resources:
limits:
memory: "256Mi"
cpu: "250m"
---
apiVersion: v1
kind: Service
metadata:
name: postfix
spec:
selector:
app: postfix
ports:
- name: smtp
port: 25
targetPort: 25
- name: submission
port: 587
targetPort: 587
- name: smtps
port: 465
targetPort: 465
```
## 3. **Postfix Configuration for Gmail Relay**
Create a ConfigMap for additional Postfix settings:
```yaml
# postfix-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: postfix-main-cf
data:
main.cf: |
# Basic configuration
myhostname = mail.yourdomain.com
myorigin = /etc/mailname
mydestination = localhost
# Gmail relay configuration
relayhost = [smtp.gmail.com]:587
smtp_use_tls = yes
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_sasl_tls_security_options = noanonymous
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
# Important for sender rewriting
sender_canonical_classes = envelope_sender, header_sender
sender_canonical_maps = regexp:/etc/postfix/sender_canonical_maps
smtp_header_checks = regexp:/etc/postfix/header_checks
# Rate limiting to avoid Gmail limits
smtp_destination_concurrency_limit = 2
smtp_destination_rate_delay = 1s
# Prevent being marked as open relay
smtpd_recipient_restrictions = permit_mynetworks, reject_unauth_destination
```
## 4. **Authentication and Header Rewriting**
### SASL Password Secret:
```bash
kubectl create secret generic postfix-sasl \
--from-literal=sasl_passwd="[smtp.gmail.com]:587 your-email@gmail.com:your-app-password"
```
### Header Rewriting ConfigMap:
```yaml
# header-rewrite.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: postfix-header-rewrite
data:
header_checks: |
/^From:.*@.*/ REPLACE From: "Your Name" <your-email@gmail.com>
/^Reply-To:.*/ REPLACE Reply-To: "Your Name" <your-email@gmail.com>
/^Sender:.*/ REPLACE Sender: "Your Name" <your-email@gmail.com>
sender_canonical_maps: |
/.*/ your-email@gmail.com
```
## 5. **Updated Deployment with ConfigMaps**
```yaml
# updated-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: postfix
spec:
replicas: 1
selector:
matchLabels:
app: postfix
template:
metadata:
labels:
app: postfix
spec:
containers:
- name: postfix
image: catatnight/postfix:latest
env:
- name: maildomain
value: "yourdomain.com"
volumeMounts:
- name: postfix-config
mountPath: /etc/postfix/main.cf
subPath: main.cf
- name: header-rewrite
mountPath: /etc/postfix/header_checks
subPath: header_checks
- name: sender-canonical
mountPath: /etc/postfix/sender_canonical_maps
subPath: sender_canonical_maps
- name: sasl-secret
mountPath: /etc/postfix/sasl_passwd
subPath: sasl_passwd
lifecycle:
postStart:
exec:
command:
- /bin/sh
- -c
- |
postmap /etc/postfix/sasl_passwd
postmap /etc/postfix/sender_canonical_maps
postfix reload
volumes:
- name: postfix-config
configMap:
name: postfix-main-cf
- name: header-rewrite
configMap:
name: postfix-header-rewrite
- name: sender-canonical
configMap:
name: postfix-header-rewrite
- name: sasl-secret
secret:
secretName: postfix-sasl
```
## 6. **DNS Configuration for Better Deliverability**
Add these DNS records for your domain:
```
TXT @ "v=spf1 include:_spf.google.com ~all"
TXT _dmarc "v=DMARC1; p=none; rua=mailto:dmarc-reports@yourdomain.com"
TXT google._domainkey "google-site-verification=..."
```
## 7. **Test the Setup**
```bash
# Test from inside the cluster
kubectl exec -it <postfix-pod> -- /bin/bash
# Send test email
echo "Test email" | mail -s "Test Subject" recipient@example.com \
-a "From: Your Name <your-email@gmail.com>"
```
## 8. **Additional Deliverability Tips**
1. **Warm up the IP**: Gradually increase email volume
2. **DKIM Signing**: Consider using a DKIM signing service
3. **Consistent Headers**: Ensure From, Reply-To, and Sender headers match
4. **Email Content**: Avoid spam trigger words, use proper HTML/text alternatives
5. **Monitoring**: Check Gmail's sending limits (500 recipients/day for free accounts)
6. **Bounce Handling**: Implement proper bounce handling in your application
## 9. **Alternative: Use Mailgun or SendGrid**
For production, consider using a dedicated email service:
```yaml
# Using Mailgun as example
relayhost = [smtp.mailgun.org]:587
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
# sasl_passwd contains: [smtp.mailgun.org]:587 postmaster@yourdomain.com:api-key
```
## Important Notes:
- **Gmail Limits**: Free accounts have daily sending limits
- **App Passwords**: Required if 2FA is enabled
- **SPF/DKIM/DMARC**: Configure these properly even when using Gmail as relay
- **Rate Limiting**: Respect Gmail's rate limits (100 emails/hour)
This setup ensures your emails appear to come from your Gmail address properly authenticated, reducing spam filter issues.