- Python 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .env.example | ||
| .gitignore | ||
| email2matrix.py | ||
| email2matrix_schema.sql | ||
| LICENSE | ||
| README.md | ||
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
.emlfile 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
- Copy the example config:
cp .env.example .env
- Edit
.envwith 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
-
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 -
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
-
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)
-
Invite bot to room:
- In Element, invite
@bot_username:your-homeserver.comto the room - Bot must accept via API:
curl -X POST \ "https://your-homeserver/_matrix/client/v3/join/!room_id:server.com?access_token=<token>"
- In Element, invite
Gmail Setup
- Enable 2FA on your Google account
- Go to https://myaccount.google.com/apppasswords
- Create new app password:
- App: Mail
- Device: Other (Email2Matrix)
- Copy the 16-character password (spaces optional)
Hotmail/Outlook Setup
Same process as Gmail:
- Enable 2FA
- Create app password at Microsoft account settings
- 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_idsender_name,sender_email,sender_domainrecipient_email,subject,body_text,body_htmldate_sent,date_received,folderhas_attachments,is_read,is_forwardedforwarded_at,matrix_event_id,matched_filtersprocessing_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_FOLDERsetting (some providers use different folder names) - Gmail: Try
[Gmail]/All Mailinstead ofINBOX - Verify
MARK_AS_READ=falseto 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
- Never commit
.env- Contains credentials - Use app passwords - Never use your main email password
- Dedicated bot user - Create separate Matrix account for the bot
- File permissions - Run
chmod 600 .envto restrict access - Token rotation - Rotate access tokens periodically
- The
.emlarchive is private mail - keepEML_DIRoutside any synced/shared folder, and make sure the directory is gitignored
License
Licensed under the GNU Affero General Public License v3.0 — see
LICENSE.