- Python 67.9%
- Shell 32.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .gitignore | ||
| AGENTS.md | ||
| example.users.conf.json | ||
| install.sh | ||
| pipe-handler.sh | ||
| README.md | ||
| requirements.txt | ||
| start-bridge.sh | ||
| xmpp-email-bridge.py | ||
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.
- Create
users.conf.jsonwith username/password pairs (see Configuration below) - The bridge automatically derives the XMPP JID as
username@domainusing the configured domain - The bridge sends email to <bridge_local_part>@ (default:
example.com) or the user's configured alias ifsend_as_emailis set.
Setup
Dependencies
pip install slixmpp
Configuration
- Copy and customize the example config:
cp example.users.conf.json users.conf.json
chmod 600 users.conf.json
- Edit
users.conf.jsonwith 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
slixmpppackage (pip install slixmpp)
Before You Install
- Create a
users.conf.jsonwith 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
- If running the install script from a different directory, pass
--config-pathto 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:
- 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
- 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
- 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.comand 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
- Bridge receives XMPP message addressed to the bridge's JID
- Checks if the sender's JID matches the
--match-frompattern - Looks up credentials for the bridge's local part in config
- 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
- Postfix pipes email to
@cheogram.comto the script viaemailmode - Script parses both
From:andTo:headers, stripping;phone-contextparams --match-frompattern 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)
- For outbound: looks up local sender's XMPP credentials in config
- Cleans the Cheogram JID (strips
;phone-contextfrom local part) - Logs in as local user and sends XMPP message to Cheogram JID
- 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
- Start the bridge for alice in one terminal:
python3 xmpp-email-bridge.py xmpp --user alice --domain example.com - Send an XMPP message from a JID matching the
--match-frompattern (e.g., a Cheogram phone-number JID) to alice's JID (using any XMPP client):+1234567890@cheogram.com -> alice@example.com: Hello from SMS! - Check that alice receives an email with:
- From: alice@example.com (the bridge user) or the configured
send_as_emailalias - To: alice@example.com (or the configured alias)
- Reply-To: +1234567890@cheogram.com
- Subject: Message from +1234567890@cheogram.com
- Body: Hello from SMS!
- From: alice@example.com (the bridge user) or the configured
3. Test Email → XMPP direction
- Send an email from alice@example.com to a Cheogram JID:
- To: +1234567890@cheogram.com
- From: alice@example.com
- Subject: Test email
- Body: This is a test email
- The transport map routes
@cheogram.comto 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
- Reply to the email received from the Cheogram bridge-inbound test
- The bridge detects the To: header matches cheogram pattern (outbound)
- 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.comtransport map - Check logs with
--debugflag for detailed information about cheogram pattern matching
License
MIT