Postfix email ingestion to FastAPI endpoint workflow with dummy values
Infrastructure

Postfix to FastAPI: Receive Emails and Pipe Them to an API Endpoint (Dummy Config)

Editor | February 28, 2026 | 6 min read

This guide shows how to receive emails on your server using Postfix and forward each incoming message to a FastAPI endpoint.

The flow is:

  1. Postfix accepts incoming mail for your domain.
  2. Postfix transport routes the message to a custom pipe.
  3. A Python script reads raw RFC822 email from stdin.
  4. The script parses To, From, Subject, and body.
  5. The script posts the parsed payload to FastAPI.
1. Update /etc/postfix/main.cf

Open file:

sudo nano /etc/postfix/main.cf

Use/update these settings:

smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no
append_dot_mydomain = no
readme_directory = no
compatibility_level = 3.6

# TLS (default/self-signed for now)
smtpd_tls_cert_file = /etc/ssl/certs/ssl-cert-snakeoil.pem
smtpd_tls_key_file = /etc/ssl/private/ssl-cert-snakeoil.key
smtpd_tls_security_level = may

smtp_tls_CApath = /etc/ssl/certs
smtp_tls_security_level = may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# Server identity (dummy values)
myhostname = mail.example.test
mydomain = example.test
myorigin = $mydomain

# Network
inet_interfaces = all
inet_protocols = ipv4

# Virtual domain
mydestination = localhost
virtual_mailbox_domains = example.test
transport_maps = hash:/etc/postfix/transport

# Security (prevent open relay)
smtpd_recipient_restrictions =
    permit_mynetworks,
    reject_unauth_destination

# Basic settings
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +

Reload Postfix:

sudo systemctl reload postfix
2. Add Pipe Service in /etc/postfix/master.cf

Open file:

sudo nano /etc/postfix/master.cf

Add this at the end:

mailpipe  unix  -       n       n       -       -       pipe
  flags=Rq user=appuser argv=/opt/mailpipe/mail_pipe.py

Reload Postfix:

sudo systemctl reload postfix
3. Add Transport Mapping

Open and edit:

sudo nano /etc/postfix/transport

Add:

example.test    mailpipe:

Build transport db:

sudo postmap /etc/postfix/transport
4. Create mail_pipe.py

Create script:

nano /opt/mailpipe/mail_pipe.py
chmod +x /opt/mailpipe/mail_pipe.py

Use this script:

#!/usr/bin/env python3

import sys
import requests
from email import message_from_bytes
from email.policy import default
from datetime import datetime

DEBUG_LOG = "/var/log/mailpipe/debug.log"
ERROR_LOG = "/var/log/mailpipe/error.log"
FASTAPI_URL = "http://127.0.0.1:8000/api/v1/inbound-email"


def log_debug(message: str):
    timestamp = datetime.utcnow().isoformat()
    with open(DEBUG_LOG, "a", encoding="utf-8") as f:
        f.write(f"[{timestamp}] {message}\\n")


def log_error(message: str):
    timestamp = datetime.utcnow().isoformat()
    with open(ERROR_LOG, "a", encoding="utf-8") as f:
        f.write(f"[{timestamp}] {message}\\n")


def extract_body(msg):
    if msg.is_multipart():
        for part in msg.walk():
            if part.get_content_type() == "text/plain":
                return part.get_content()
        return ""
    return msg.get_content()


def main():
    try:
        raw_email = sys.stdin.buffer.read()
        log_debug(f"Pipe executed. Email size: {len(raw_email)} bytes")

        msg = message_from_bytes(raw_email, policy=default)
        to_addr = msg.get("To")
        from_addr = msg.get("From")
        subject = msg.get("Subject")
        body = extract_body(msg)

        log_debug(f"Parsed Email - To: {to_addr}, From: {from_addr}, Subject: {subject}")
        log_debug(f"Body Preview (first 200 chars): {body[:200]}")

        payload = {
            "to": to_addr,
            "sender": from_addr,
            "subject": subject,
            "body": body,
        }

        response = requests.post(
            FASTAPI_URL,
            json=payload,
            timeout=10,
        )

        if response.status_code >= 400:
            raise Exception(f"FastAPI error: {response.status_code} {response.text}")

        log_debug("Email successfully sent to FastAPI")

    except Exception as e:
        log_error(str(e))


if __name__ == "__main__":
    main()

Restart Postfix:

sudo systemctl restart postfix
5. Quick Test

Send a test email to any address on @example.test and verify:

  • /var/log/mailpipe/debug.log for successful parse/send logs
  • /var/log/mailpipe/error.log for failures
  • FastAPI endpoint receives payload:
{
  "to": "user@example.test",
  "sender": "sender@example.com",
  "subject": "Test",
  "body": "Hello from SMTP"
}
Troubleshooting Notes
  • If mail is not routed to script, re-check /etc/postfix/transport and run sudo postmap /etc/postfix/transport again.
  • If script does not run, verify execute permission and exact path in master.cf.
  • If API calls fail, verify FastAPI service is reachable at 127.0.0.1:8000.
  • If endpoint needs auth, add headers/token in requests.post(...).
Security Hardening (Recommended)
  • Replace snakeoil certs with valid TLS certificates.
  • Validate/allowlist recipient domains in FastAPI.
  • Add rate limits and queue/retry behavior for endpoint failures.
  • Rotate and protect log files.

This setup is a practical starting point for building inbound email automation with Postfix and FastAPI.