A generic XMPP <-> Email bridge for bidirectional messaging between XMPP and email systems.
  • Python 67.9%
  • Shell 32.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Jason Hammons fd718ed1e2 Add footer stripper to remove email signatures before sending XMPP
Add footer stripper to remove email signatures before sending XMPP
2026-07-22 23:21:05 -04:00
.gitignore Add AGENTS.md and .gitignore 2026-05-12 19:55:11 -04:00
AGENTS.md Add footer stripper to remove email signatures before sending XMPP 2026-07-22 23:21:05 -04:00
example.users.conf.json Rename users.conf.json to example to match README.md. 2026-05-11 19:58:47 -04:00
install.sh Change default --match-from to all @cheogram.com JIDs 2026-07-10 23:39:51 -04:00
pipe-handler.sh Code review: fix critical login bug, install script improvements, docs accuracy 2026-07-06 02:11:17 -04:00
README.md Documentation and comment improvements. 2026-07-15 23:11:32 -04:00
requirements.txt Add requirements.txt 2026-05-08 23:57:36 -04:00
start-bridge.sh Code review: fix critical login bug, install script improvements, docs accuracy 2026-07-06 02:10:25 -04:00
xmpp-email-bridge.py Fix send_as_email bug breaking email sending. 2026-07-20 12:31:45 -04:00

xmpp-email-bridge

A generic XMPP <-> Email bridge for bidirectional messaging between XMPP and email systems.

Overview

This bridge converts messages in both directions:

  • XMPP → Email: Receives XMPP messages matching a configurable regex pattern and forwards them as email to local users
  • Email → XMPP: Receives email via Postfix pipe and sends as XMPP message to the corresponding JID

Diagram

The following diagram illustrates the components and data flow:

+------------------+    XMPP    +------------------+    Email   +------------------+
|                  |<---------->|                  |<---------->|                  |
|  XMPP System     |            |  XMPP Email      |            |  Email System    |
|  (any domain)    |            |  Bridge          |            |  (Postfix)       |
|                  |<---------->|                  |<---------->|                  |
+------------------+            +------------------+            +------------------+

Where:

  • The XMPP Email Bridge communicates with any XMPP server/domain over XMPP (bidirectional).
  • The Email System (Postfix) is used for:
    • Receiving email: Postfix pipes incoming email to the bridge (in email mode).
    • Sending email: The bridge sends email via SMTP to the local mail server (which is part of the Email System).

Multi-User Support

This version supports multiple users. Each user needs a username and password, and the XMPP JID is automatically derived from the username and domain.

  1. Create users.conf.json with username/password pairs (see Configuration below)
  2. The bridge automatically derives the XMPP JID as username@domain using the configured domain
  3. The bridge sends email to <bridge_local_part>@ (default: example.com) or the user's configured alias if send_as_email is set.

Setup

Dependencies

pip install slixmpp

Configuration

  1. Copy and customize the example config:
cp example.users.conf.json users.conf.json
chmod 600 users.conf.json
  1. Edit users.conf.json with your users:
{
  "username1": "password1",
  "username2": "password2",
  "user3": {
    "password": "securepass",
    "send_as_email": "alias@example.com"
  }
}

The send_as_email field lets a user's XMPP login (e.g., user3) use a different email address for the FROM and TO fields when sending emails. This is useful when the mailbox alias differs from the XMPP username. The bridge also falls back to matching by send_as_email when looking up credentials during Email→XMPP forwarding — if your local email arrives at an alias, it will still find the right user entry.

Security: The config file should only be readable by the owner (chmod 600) as it contains plaintext passwords.

Installation

The install.sh script deploys the bridge to /opt/xmpp-email-bridge/, sets up a dedicated Postfix pipe transport, and creates the necessary system account. It is idempotent — running it multiple times is safe.

Prerequisites

  • Root access (the script uses sudo)
  • Postfix installed and running
  • Python 3 with the slixmpp package (pip install slixmpp)

Before You Install

  1. Create a users.conf.json with your users:
cp example.users.conf.json users.conf.json
chmod 600 users.conf.json
# Edit users.conf.json with your actual users and passwords
  1. If running the install script from a different directory, pass --config-path to point to your config:
sudo ./install.sh --domain example.com --config-path /etc/xmpp-email-bridge/users.conf.json

One-Command Install (from repo directory)

Set the domain and run with BRIDGE_SCRIPT_DIR so all files are copied from the repo:

sudo BRIDGE_SCRIPT_DIR="$(pwd)" ./install.sh --domain example.com

This installs everything to /opt/xmpp-email-bridge/, configures Postfix transport routing (@cheogram.com → pipe), adds the xmpp-email-delim pipe service to master.cf, generates pipe-handler.sh with the correct domain baked in, and reloads Postfix.

One-Command Uninstall

To remove all bridge configuration:

sudo ./install.sh --uninstall

This removes the pipe service from master.cf, the transport map entries, the installed files from /opt/xmpp-email-bridge/, and reloads Postfix.

After Install

Start the multi-user bridge daemon:

cd /opt/xmpp-email-bridge && ./start-bridge.sh <domain>

The start-bridge.sh script launches one XMPP instance per user defined in users.conf.json. Output is consolidated to /var/log/xmpp-email-bridge/<domain>.log.

To stop the bridge, kill the PID shown by start-bridge.sh, or look up the running processes directly.

Usage

XMPP Daemon Mode

Run the bridge as an XMPP client that listens for incoming messages:

python3 xmpp-email-bridge.py xmpp --user username1

Required arguments:

  • --user: Username (local part only, no @domain) to look up in config

Optional:

  • --config: Path to users config file (default: users.conf.json)
  • --domain: XMPP and email domain (default: example.com)
  • --match-from: Regex pattern to match incoming XMPP from JIDs (default: ^\+[0-9]+@cheogram\.com$ - matches Cheogram phone-number JIDs)

Email Pipe Mode

Configure Postfix to pipe incoming email to this script:

  1. Add to /etc/postfix/master.cf:
xmpp-email-delim  unix  -       n       n       -       -       pipe
  flags=DRu user=xmppbridge argv=/opt/xmpp-email-bridge/pipe-handler.sh
  1. Add to /etc/postfix/transport (route cheogram.com addresses to the pipe):
cheogram.com    xmpp-email-delim:

Any email with a recipient domain matching cheogram.com (e.g. +1234567890@cheogram.com) will be piped to the bridge, which determines direction using --match-from:

Inbound (XMPP→Email): Sender is Cheogram, recipient is local — already handled by the XMPP→Email path, so this email is skipped

Outbound (Email→XMPP): Sender is local, recipient is Cheogram — credentials looked up for local sender, XMPP sent to Cheogram JID

  1. Update Postfix configuration:
postmap /etc/postfix/transport
postfix reload

Important: Transport maps route cheogram.com addresses to the pipe (see --match-from). - The --domain is used for two things: (1) the bridge's XMPP login (e.g., alice@example.com), and (2) extracting the local user's credentials - --match-from determines which addresses are Cheogram JIDs and which direction the message is going: - Inbound (XMPP→Email): Cheogram sender → bridge delivers to local email - Outbound (Email→XMPP): Local sender replies to Cheogram JID → bridge sends XMPP - Only addresses matching --match-from are bridged; all other email is handled normally by IMAP - Example: --domain example.com --match-from "^\+[0-9]+@cheogram\.com$", a reply from alice@example.com to +1234@cheogram.com triggers the pipe, credentials for alice are looked up, and XMPP is sent to +1234@cheogram.com

Note: Email mode automatically reads from users.conf.json to look up credentials. Use --domain to specify a custom domain if not using example.com.

Warning about mail loops: If you configure the bridge in xmpp mode to forward XMPP messages as email to the same domain that is handled by the Postfix transport map, you may create a mail loop. To avoid this, consider:

  • Using a subdomain for the XMPP bridge email traffic (e.g., bridge sends to local_part@bridge.yourdomain.com and only transport @yourdomain.com), or
  • Configuring Postfix to exclude emails with a specific header from the pipe (requires more advanced Postfix setup). For simple setups where the bridge in xmpp mode is only used to receive messages from XMPP (and not to send replies via email), loops are less of a concern.

Config file path: Use --config /path/to/users.conf.json to specify a custom config location.

How It Works

XMPP to Email

  1. Bridge receives XMPP message addressed to the bridge's JID
  2. Checks if the sender's JID matches the --match-from pattern
  3. Looks up credentials for the bridge's local part in config
  4. Sends email to <bridge_local_part>@<domain> (default: example.com) with: - From: bridge's XMPP JID - Reply-To: sender's JID (for proper replies) - Subject: Message from [original JID] - Body: Message content plus sender information

Email to XMPP

  1. Postfix pipes email to @cheogram.com to the script via email mode
  2. Script parses both From: and To: headers, stripping ;phone-context params
  3. --match-from pattern is applied to both addresses:
    • Sender matches: Inbound from Cheogram — already delivered via XMPP→Email, skip
    • Recipient matches: Outbound reply — local sender, Cheogram recipient → send via XMPP
    • Neither matches: Local email only — skip (handled by IMAP)
  4. For outbound: looks up local sender's XMPP credentials in config
  5. Cleans the Cheogram JID (strips ;phone-context from local part)
  6. Logs in as local user and sends XMPP message to Cheogram JID
  7. Message includes email subject and body

File Structure

xmpp-email-bridge/
├── xmpp-email-bridge.py      # Main script
├── users.conf.json           # User credentials (create this)
├── install.sh                # Postfix integration & deploy script
├── start-bridge.sh           # Multi-user launcher
├── pipe-handler.sh           # Postfix Pipe entry point template
└── README.md

Troubleshooting

"Config file not found": Copy example.users.conf.json to users.conf.json and edit it.

"No credentials found for user": Verify the local_part exists in config and matches the extracted identifier.

Connection errors: Check XMPP server details and credentials in users.conf.json.

Postfix not triggering: Check Postfix logs and ensure transport file is correctly formatted and compiled.

Testing Bi-Directional Communication

To test that the bridge works in both directions:

1. Set up two test users

Create a users.conf.json with two test users:

{
  "alice": "password123",
  "bob": "password456"
}

2. Test XMPP → Email direction

  1. Start the bridge for alice in one terminal:
    python3 xmpp-email-bridge.py xmpp --user alice --domain example.com
    
  2. Send an XMPP message from a JID matching the --match-from pattern (e.g., a Cheogram phone-number JID) to alice's JID (using any XMPP client):
    +1234567890@cheogram.com -> alice@example.com: Hello from SMS!
    
  3. Check that alice receives an email with:

3. Test Email → XMPP direction

  1. Send an email from alice@example.com to a Cheogram JID:
  2. The transport map routes @cheogram.com to the pipe, the bridge:
    • Detects recipient is Cheogram (outbound)
    • Looks up credentials for alice
    • Sends XMPP message to +1234567890@cheogram.com

4. Test reply handling

  1. Reply to the email received from the Cheogram bridge-inbound test
  2. The bridge detects the To: header matches cheogram pattern (outbound)
  3. Sends XMPP to the Cheogram JID with the reply content

Notes for testing:

  • Use --domain example.com --match-from "^\+[0-9]+@cheogram\.com$" for real Cheogram deployments
  • Ensure XMPP server is accessible and credentials are correct
  • For email testing, configure Postfix with @cheogram.com transport map
  • Check logs with --debug flag for detailed information about cheogram pattern matching

License

MIT