Automatically send received email to a specific Matrix room
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-08 15:33:30 -04:00
.env.example added schema 2026-10-08 15:33:30 -04:00
.gitignore first commit 2026-10-08 15:24:22 -04:00
email2matrix.py first commit 2026-10-08 15:24:22 -04:00
email2matrix_schema.sql added schema 2026-10-08 15:33:30 -04:00
LICENSE first commit 2026-10-08 15:24:22 -04:00
README.md added schema 2026-10-08 15:33:30 -04:00

Email2Matrix

Forward emails from multiple accounts to a Matrix room. Supports Gmail, Hotmail/Outlook, and custom IMAP servers.

Every matched email is sent to the room with its raw .eml file attached, saved to SQLite for history and search, and archived on disk.

Features

  • Multi-account support: Gmail, Hotmail/Outlook, and any IMAP server
  • Database storage: All emails saved to SQLite for history and search
  • Raw email archiving: Every processed email is saved as a .eml file and attached to the Matrix message
  • Filtering: Forward only specific senders, subjects, or recipients; blacklist takes precedence
  • Age limit: Skip emails older than a configurable number of days
  • Configurable preview: Control how many body lines appear in the room message, and whether links are shown
  • Mark as read: Optionally mark emails as read after processing (or leave untouched with BODY.PEEK)
  • Rate limiting: Prevent flooding with configurable max emails per check and inter-message delay
  • Full logging: All actions logged to database for debugging

Requirements

  • Python 3.8+
  • requests
  • SQLite3
  • Matrix homeserver with access token

Installation

# Clone the repository
cd ~/Code/Python
git clone <repo-url> Email2Matrix
cd Email2Matrix

# Install dependency
pip install requests

# Create your configuration
cp .env.example .env
# edit .env with your values

Configuration

  1. Copy the example config:
cp .env.example .env
  1. Edit .env with your values. The core settings:
# Database paths (optional - defaults to script directory)
DATABASE_PATH=/mnt/MidoriNC/Databases/email2matrix.db
SCHEMA_PATH=/mnt/MidoriNC/Databases/email2matrix_schema.sql

# Where raw .eml files are archived
EML_DIR=/mnt/MidoriNC/Emails

# Matrix Configuration
MATRIX_HOMESERVER=https://your-homeserver.com
MATRIX_ACCESS_TOKEN=your_bot_access_token
MATRIX_ROOM_ID=!your_room_id:your-homeserver.com

# Email Accounts (JSON array)
# Supported types: gmail, hotmail, custom (custom requires "host")
EMAIL_ACCOUNTS=[
  {
    "name": "gmail-personal",
    "type": "gmail",
    "user": "yourname@gmail.com",
    "password": "your_app_password_here"
  }
]

# Forwarding rules
EMAIL_FILTERS=[]          # empty = forward everything
EMAIL_EXCLUDES=[]        # checked first; match = skip

All options

Option Default Purpose
DATABASE_PATH script dir / email2matrix.db SQLite database location
SCHEMA_PATH script dir / email2matrix_schema.sql Schema file applied on startup
EML_DIR script dir / eml Where raw .eml files are archived
ATTACHMENTS_DIR script dir / attachments Where extracted attachments are written
MATRIX_HOMESERVER (required) Homeserver base URL
MATRIX_ACCESS_TOKEN (required) Bot account token
MATRIX_ROOM_ID (required) Target room (!id:server.com)
EMAIL_ACCOUNTS (required) JSON array of IMAP accounts
EMAIL_FILTERS [] Whitelist rules; empty = accept all
EMAIL_EXCLUDES [] Blacklist rules; checked before filters
EMAIL_FOLDER INBOX IMAP folder to monitor (Gmail alt: [Gmail]/All Mail)
MAX_EMAILS_PER_CHECK 10 Backlog cap per run
MAX_EMAIL_AGE_DAYS 8 Skip emails older than this (0 = no limit)
MARK_AS_READ true Mark processed emails as read; false uses BODY.PEEK and leaves them unread
PREVIEW_LINES 2 Body lines shown in the Matrix message (0 = no preview)
SHOW_LINKS_IN_PREVIEW true false strips lines containing URLs from the preview
SHOW_SENDER_ADDRESS true false shows only the sender name, not the address
SEND_ATTACHMENTS_TO_MATRIX false Also upload extracted attachments as separate room messages
MESSAGE_DELAY 1 Seconds between Matrix messages (rate-limit protection)

Matrix Setup

  1. Create a bot user (recommended) or use existing account:

    # For Synapse Docker:
    docker exec -it <synapse-container> register_new_matrix_user -c /data/homeserver.yaml
    
  2. Get access token:

    curl -X POST \
      -H "Content-Type: application/json" \
      -d '{"type":"m.login.password","user":"bot_username","password":"bot_password"}' \
      "https://your-homeserver/_matrix/client/v3/login"
    

    Or log into Element → Settings → Help & About → Advanced → Access Token

  3. Create/join a room and get the room ID:

    • Open room in Element
    • Click room name → Settings → Advanced
    • Copy "Internal room ID" (format: !xxxxxx:server.com)
  4. Invite bot to room:

    • In Element, invite @bot_username:your-homeserver.com to the room
    • Bot must accept via API:
      curl -X POST \
        "https://your-homeserver/_matrix/client/v3/join/!room_id:server.com?access_token=<token>"
      

Gmail Setup

  1. Enable 2FA on your Google account
  2. Go to https://myaccount.google.com/apppasswords
  3. Create new app password:
    • App: Mail
    • Device: Other (Email2Matrix)
  4. Copy the 16-character password (spaces optional)

Hotmail/Outlook Setup

Same process as Gmail:

  1. Enable 2FA
  2. Create app password at Microsoft account settings
  3. Use app password in .env

Custom IMAP

For custom domains, specify the IMAP host:

{
  "name": "custom-domain",
  "type": "custom",
  "user": "yourname@yourdomain.com",
  "password": "your_password",
  "host": "mail.yourdomain.com"
}

Usage

Manual Run

cd ~/Code/Python/Email2Matrix
python3 email2matrix.py

Output:

=== Email2Matrix ===
Time: 2026-04-06T00:15:44.576520
Database initialized
Accounts: 2

Checking gmail-personal...
  Found 3 new emails

Checking outlook-work...
  Found 0 new emails

Sending 3 emails to Matrix...
  ✓ Sent: Your order has shipped...
  ✓ Sent: Newsletter #42...
  ✓ Sent: Meeting reminder...

Done!

Cron Job Setup

The script is a single pass — check, forward, exit — so scheduling is cron's job:

# Edit crontab
crontab -e

# Add line (every 5 minutes)
*/5 * * * * /usr/bin/python3 /home/luci/Code/Python/Email2Matrix/email2matrix.py >> /home/luci/Code/Python/Email2Matrix/email2matrix.log 2>&1

Or add programmatically:

(crontab -l 2>/dev/null; echo "*/5 * * * * /usr/bin/python3 /home/luci/Code/Python/Email2Matrix/email2matrix.py >> /home/luci/Code/Python/Email2Matrix/email2matrix.log 2>&1") | crontab -

Cron Schedule Examples

Schedule Meaning
*/5 * * * * Every 5 minutes
*/10 * * * * Every 10 minutes
0 * * * * Every hour
0 */2 * * * Every 2 hours
0 9,17 * * * 9 AM and 5 PM daily

Filtering

Include Filter (Whitelist)

Only forward specific emails:

EMAIL_FILTERS=[
  {"type": "sender", "value": "newsletter@example.com"},
  {"type": "sender", "value": "boss@company.com"},
  {"type": "subject", "value": "urgent"},
  {"type": "to", "value": "support@mydomain.com"}
]
  • sender: Match email from specific address (partial match, case-insensitive)
  • subject: Match subject contains text (case-insensitive)
  • to: Match recipient address

Multiple filters = OR logic (match any filter passes)

Exclude Filter (Blacklist)

Skip emails from specific senders or with specific subjects:

EMAIL_EXCLUDES=[
  {"type": "sender", "value": "spammer@example.com"},
  {"type": "subject", "value": "unsubscribe"}
]

Priority: Excludes are checked first. If an email matches any exclude rule, it's skipped regardless of include filters.

Combined Example

Forward all emails except spam:

EMAIL_FILTERS=[]

EMAIL_EXCLUDES=[
  {"type": "sender", "value": "promo@store.com"},
  {"type": "subject", "value": "newsletter"}
]

Database

All emails stored in SQLite (path from DATABASE_PATH).

Tables

email_accounts: Account metadata

  • id, name, type, email_address, imap_host, created_at

emails: All received emails

  • id, account_id, message_id, thread_id
  • sender_name, sender_email, sender_domain
  • recipient_email, subject, body_text, body_html
  • date_sent, date_received, folder
  • has_attachments, is_read, is_forwarded
  • forwarded_at, matrix_event_id, matched_filters
  • processing_notes, created_at

email_attachments: Attachment records (defined in schema; not populated by the current script)

email_log: Processing log

  • id, account_id, action, status, message, email_id, created_at

Query Examples

-- All emails from a sender
SELECT * FROM emails WHERE sender_email LIKE '%@example.com';

-- Emails not forwarded
SELECT * FROM emails WHERE is_forwarded = 0;

-- Recent errors
SELECT * FROM email_log WHERE status = 'error' ORDER BY created_at DESC;

-- Email count by account
SELECT a.name, COUNT(*) FROM emails e
JOIN email_accounts a ON e.account_id = a.id
GROUP BY a.name;

Troubleshooting

Bot not in room

Error: M_FORBIDDEN: User not in room

Fix: Invite bot to room, then accept via API:

curl -X POST "https://your-homeserver/_matrix/client/v3/join/!room_id:server.com?access_token=<token>"

Gmail authentication failed

Error: LOGIN failed

Fix: Use app password, not your regular password. Enable 2FA first.

Hotmail/Outlook authentication failed

Error: BasicAuthBlocked or AUTHENTICATE failed

Fix: Microsoft disabled basic auth for some accounts. Create app password at: https://account.microsoft.com/security

If still failing, Microsoft may have fully disabled IMAP for your account (common for personal Outlook.com). Consider:

  • Using Gmail instead
  • Setting up a custom domain email

Invalid JSON in .env

Error: Invalid EMAIL_ACCOUNTS JSON

Fix: JSON must be valid. Common issues:

  • Trailing commas before ] or } (the script strips these automatically, but avoid them)
  • Missing quotes around strings
  • Unescaped special characters

Connection timeout

Error: Connection timed out

Fix:

  • Verify IMAP host is correct
  • Check firewall allows outbound IMAP (port 993)
  • Verify email provider allows IMAP access

No emails found

But you have unread emails:

  • Check EMAIL_FOLDER setting (some providers use different folder names)
  • Gmail: Try [Gmail]/All Mail instead of INBOX
  • Verify MARK_AS_READ=false to see if emails are being marked read elsewhere
  • Check MAX_EMAIL_AGE_DAYS — older emails are skipped by design

File Structure

~/Code/Python/Email2Matrix/
├── email2matrix.py          # Main script
├── email2matrix_schema.sql  # SQLite schema (applied on startup)
├── .env                     # Configuration (DO NOT COMMIT)
├── .env.example             # Example configuration
├── .gitignore
├── README.md                # This file
├── LICENSE                  # AGPLv3
├── email2matrix_state.json  # Processed email IDs (auto-generated)
├── eml/                     # .eml archive (auto-generated; gitignore it)
└── attachments/             # Extracted attachments (auto-generated; gitignore it)

Security Notes

  1. Never commit .env - Contains credentials
  2. Use app passwords - Never use your main email password
  3. Dedicated bot user - Create separate Matrix account for the bot
  4. File permissions - Run chmod 600 .env to restrict access
  5. Token rotation - Rotate access tokens periodically
  6. The .eml archive is private mail - keep EML_DIR outside any synced/shared folder, and make sure the directory is gitignored

License

Licensed under the GNU Affero General Public License v3.0 — see LICENSE.