Email autoresponders (vacation messages and out-of-office replies) automatically send predefined responses to incoming messages. While modern email clients handle this client-side, server-side autoresponders work 24/7 regardless of whether the user's email client is running. This guide covers implementing autoresponders with Postfix using both simple and advanced approaches.
Method 1: Postfix vacation Command
The vacation utility is the traditional Unix autoresponder. It tracks who has been replied to and avoids sending duplicate responses.
# Install vacation
sudo apt install vacation # Ubuntu/Debian
# Set up for a user
su - username
vacation -I # Initialize the .vacation.db file
# Create vacation message
cat > ~/.vacation.msg ~/.forward
# Disable vacation
rm ~/.forward
Method 2: Sieve-Based Autoresponder
If you use Dovecot with Sieve (recommended for virtual domains):
# User Sieve script (~/.dovecot.sieve)
require ["vacation", "variables"];
vacation
:days 1
:subject "Out of Office - Will return Jan 20"
:from "User Name "
:addresses ["user@example.com", "u.name@example.com"]
:mime
"MIME-Version: 1.0
Content-Type: text/html; charset=utf-8
Thank you for your email.
I am currently out of the office and will return on January 20, 2025.
For urgent matters, please contact backup@example.com.
Best regards,
User Name
";
Method 3: Custom Autoresponder Script
#!/usr/bin/env python3
# /usr/local/bin/autoresponder.py
# Advanced autoresponder with rate limiting and template support
import sys
import email
import sqlite3
import time
import subprocess
from email.mime.text import MIMEText
DB_PATH = "/var/lib/autoresponder/responses.db"
RATE_LIMIT = 86400 # Reply once per sender per day
def init_db():
conn = sqlite3.connect(DB_PATH)
conn.execute("""CREATE TABLE IF NOT EXISTS sent (
recipient TEXT, sender TEXT, sent_at REAL,
PRIMARY KEY (recipient, sender))""")
return conn
def should_respond(conn, recipient, sender):
row = conn.execute(
"SELECT sent_at FROM sent WHERE recipient=? AND sender=?",
(recipient, sender)).fetchone()
if row and time.time() - row[0] < RATE_LIMIT:
return False
return True
def record_response(conn, recipient, sender):
conn.execute(
"INSERT OR REPLACE INTO sent VALUES (?, ?, ?)",
(recipient, sender, time.time()))
conn.commit()
def get_template(recipient):
# Load per-user template
template_path = f"/var/lib/autoresponder/templates/{recipient}.txt"
try:
with open(template_path) as f:
return f.read()
except FileNotFoundError:
return None
# Read incoming message
msg = email.message_from_bytes(sys.stdin.buffer.read())
sender = email.utils.parseaddr(msg["From"])[1]
recipient = email.utils.parseaddr(msg["To"])[1]
# Skip automated messages
if msg.get("Auto-Submitted", "").lower() != "no" and msg.get("Auto-Submitted"):
sys.exit(0)
if msg.get("X-Auto-Response-Suppress"):
sys.exit(0)
if msg.get("Precedence", "").lower() in ("bulk", "junk", "list"):
sys.exit(0)
template = get_template(recipient)
if not template:
sys.exit(0)
conn = init_db()
if not should_respond(conn, recipient, sender):
sys.exit(0)
# Send response
reply = MIMEText(template)
reply["From"] = recipient
reply["To"] = sender
reply["Subject"] = f"Re: {msg.get('Subject', 'Your email')}"
reply["Auto-Submitted"] = "auto-replied"
reply["X-Auto-Response-Suppress"] = "All"
reply["In-Reply-To"] = msg.get("Message-ID", "")
subprocess.run(["/usr/sbin/sendmail", "-t", "-oi"], input=reply.as_bytes())
record_response(conn, recipient, sender)
conn.close()
Integrate with Postfix
# /etc/postfix/main.cf — use as a content filter
content_filter = autoresponder:dummy
# /etc/postfix/master.cf
autoresponder unix - n n - 10 pipe
flags=DRhu user=autoresponder argv=/usr/local/bin/autoresponder.py
# Or use always_bcc to copy messages to the autoresponder
# without modifying the delivery pipeline
Managing Autoresponders
# Enable autoresponder for a user
cat > /var/lib/autoresponder/templates/user@example.com.txt