Many legacy systems, embedded devices, and applications:
- β Do not support OAuth2 for sending or receiving email
- β Cannot access Microsoft 365 endpoints directly due to network restrictions
- β Require SMTP/POP3 and basic username/password authentication
This proxy provides a secure bridge between those tools and Microsoft 365:
- π‘οΈ Messages are relayed through a single authenticated Microsoft 365 account
- π Only authorized clients (defined in config) can use the proxy
- π Useful when you can't expose your own mail servers in SPF records
- π₯ Enables retrieval of mail from shared mailboxes via POP3
- πΈ With a single low-cost Exchange Online Kiosk license + free shared mailboxes, you can build a distributed, multi-sender notification system β without a full SMTP infrastructure
Ideal for:
- Network monitoring software
- Firewalls, routers, printers, IP cameras
- IoT and industrial equipment
- Legacy on-prem systems
- Environments with no internet access or firewall egress rules
This tool helps consolidate notifications from many devices and subsystems under one tenant, while still keeping SPF/DKIM/DMARC compliant and not exposing internal systems.
- π POP3 STARTTLS support β mail clients can now upgrade plaintext POP3 connections to TLS securely
- π SMTP SMTPS support (TLS from start) β SMTPS (port 465) is now supported in addition to STARTTLS (port 587 or custom)
- π POP3S support (TLS from start) β full support for POP3 over SSL/TLS (port 995)
- π Simultaneous operation of all modes β you can now run:
- SMTP (with STARTTLS)
- SMTPS (SSL/TLS)
- POP3 (with STARTTLS)
- POP3S (SSL/TLS) β¦on different ports, either individually or together
- π Improved configuration parser:
- Clearer validation of TLS, ports, logging paths, and mailbox entries
- Prevents conflicts like
smtp_port == smtps_port - Enhanced proxy URL formatting and diagnostics
- π€ Better compatibility with popular clients and devices:
- Thunderbird, Outlook, MFDs, embedded IoT mailers, etc.
- β Final release for this stage β future possibilities (IMAP, shared calendars, folders) may push the project into enterprise mail territory, which goes beyond the scope and potentially conflicts with Microsoft licensing β and thatβs not a direction this project is taking.
This tool is designed for cases where you want to use real mail clients and devices (like scan-to-email from printers or backup software) with Microsoft 365 but want to avoid exposing user credentials or managing OAuth2 flows manually.
It provides local endpoints (SMTP and POP3) that forward requests securely via Microsoft Graph.
- β
Transparent mail sending via Microsoft Graph
/sendMail - β Works with shared mailboxes ("Send As")
- β Supports large attachments (up to 150MB via Graph API chunked upload)
- β POP3 receiving, with folder selection and message flags
- β Shared folder access for POP3 download (e.g. service@domain.com)
- β STARTTLS and SMTPS support
- β Multiple mailboxes with independent credentials
- β Authenticated and secure
- β Python-based and container-ready
pip install m365proxy
#Create a configuration file interactively
m365proxy configure
# First-time login to Microsoft 365 via Device Flow
m365proxy login
# Start the proxy (SMTP + POP3)
m365proxyWhen Microsoft 365 is unreachable:
- βοΈ Messages are queued to disk
- π Automatically retried in background (every 5 minutes)
- π§ No data loss β even if the device is offline for hours
You can inspect the queue at:
ls ~/.m365proxy/queue/ Legacy Device (SMTP) ββββββ
ββ> SMTP Proxy ββ> Graph API ββ> Exchange Mailbox
App / Printer / Camera βββ
POP3 Client <ββββββββββββββ POP3 Proxy <βββ Graph API (Shared Mail)
To install the proxy as a Python package:
pip install m365proxyYou can then run it via:
m365proxyOr:
python -m m365proxypython -m m365proxy [options]
python -m m365proxy [-config CONFIG] [command]Simple SMTP and POP3 mail proxy to Microsoft 365 mailbox over HTTPS (using Microsoft Graph API).
| Option | Description |
|---|---|
-h, --help |
Show help message and exit |
-config CONFIG |
Path to configuration file (default: <home_folder>/.m365proxy/config.json) |
-token TOKEN |
Path to token file (default: <config_folder>/tokens.enc) |
-log-file PATH |
Log file path (default: <config_folder>/m365.log) |
-log-level LEVEL |
Logging level for file. One of DEBUG, INFO, WARNING, ERROR (default: INFO) |
-bind ADDRESS |
Bind address for services (default: 127.0.0.1) |
-smtp-port PORT |
SMTP listening port (default: 10025) |
-pop3-port PORT |
POP3 listening port (optional, default: None) |
-https-proxy URL |
HTTPS proxy URL (e.g. http://proxy.local:3128) |
-no-ssl |
Disable SSL/TLS for SMTP and POP3 |
-debug |
Enable verbose output (CLI only) |
-quiet |
Suppress all output except errors (CLI only) |
| Command | Description |
|---|---|
| (none) | Start SMTP/POP3 proxy server |
init-config |
Create minimal default config.json |
configure |
Run interactive configuration |
login |
Start Microsoft device code login flow |
check-token |
Check current token validity |
show-token |
Show contents of decrypted token |
check-config |
Show effective configuration |
test |
Send a test email |
hash |
Hash password for use in config.json |
π
hashrequires an argument: the plain password to be hashed.
# Start the proxy with default config
python -m m365proxy
# Generate default config file
python -m m365proxy init-config
# Configure interactively
python -m m365proxy configure
# Login via Microsoft device code flow
python -m m365proxy login
# Start the proxy on custom SMTP port and bind to all interfaces
python -m m365proxy -smtp-port 2525 -bind 0.0.0.0
# Run with custom config and token paths in quiet mode
python -m m365proxy -config ./myconfig.json -token /tmp/mytoken.enc -quiet
# Run with detailed logging
python -m m365proxy -config /tmp/myconfig.json -token /tmp/mytoken.enc -log-file /tmp/m365.log -log-level DEBUGHere's how to run m365proxy in a container:
Dockerfile:
FROM python:3.11-slim
WORKDIR /app
# Install the proxy from PyPI
RUN pip install m365proxy
# Copy configuration files
COPY config.json .
COPY tokens.enc .
# Expose required ports
EXPOSE 10025 10110
CMD ["m365proxy"]Run example:
docker run -d \
-v $(pwd)/config.json:/app/config.json \
-v $(pwd)/tokens.enc:/app/tokens.enc \
-p 10025:10025 -p 10110:10110 \
m365proxyπ You can also override config or token paths with
-configand-tokenarguments.
To auto-start the proxy on Windows:
- Open Task Scheduler
- Choose Create Basic Task
- Trigger: At startup or Log on
- Action: Start a program
- Set
Program/script:to:SetpythonwAdd arguments:to:-m m365proxy -quiet
To run the proxy as a background service on Linux, create a systemd unit file:
/etc/systemd/system/m365proxy.service
[Unit]
Description=Microsoft 365 Mail Proxy
After=network.target
[Service]
ExecStart=/usr/bin/python3 -m m365proxy -quiet
WorkingDirectory=/opt/m365proxy
Restart=always
User=nobody
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.targetThen enable and start:
sudo systemctl daemon-reload
sudo systemctl enable m365proxy
sudo systemctl start m365proxy{
"user": "licensed@example.com",
"client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"tenant_id": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
"allowed_domains": ["example.com"],
"mailboxes": [
{
"username": "shared1@example.com",
"password": "hashed-secret1"
},
{
"username": "shared2@example.com",
"password": "hashed-secret2"
}
],
"bind": "127.0.0.1",
"smtp_port": 10025,
"pop3_port": 10110,
"https_proxy": {
"url": "http://proxy.local:3128",
"user": "proxyuser",
"password": "proxypass"
},
"logging": {
"level": "INFO"
}
}π All passwords hashed and stored locally.
To use Microsoft Graph API, you must register your application in Microsoft Entra ID (Azure Active Directory).
- Go to https://entra.microsoft.com
- Navigate to Azure Active Directory β App registrations β New registration
- Set a name like
smtp-proxy, leave redirect URI empty (device code flow doesn't require it)
- Copy the Client ID and Tenant ID into your
config.json
- Go to API permissions β Add a permission β Microsoft Graph β Delegated
- Add:
Mail.Send(required, for outgoing mail)Mail.Send.Shared(required, for outgoing mail)Mail.ReadWrite(optional, for incoming mail)Mail.ReadWrite.Shared(optional, for incoming mail)offline_access(required, for refresh tokens)
- Grant admin consent for your tenant
- Go to Authentication β Enable public client (mobile & desktop)
- Allow device code flow
- Run the proxy with
login - You will be prompted to sign in once in a browser
- After that, tokens will be refreshed automatically
To send or receive mail on behalf of shared addresses:
- Go to Microsoft 365 Admin Center β Shared mailboxes β Create
- Assign Send As or Send on Behalf rights to your user in the config
Grant Send As Rights (PowerShell):
Add-RecipientPermission -Identity shared@domain.com -Trustee user@domain.com -AccessRights SendAsGrant Send on Behalf:
Set-Mailbox shared@domain.com -GrantSendOnBehalfTo user@domain.comπ Send As is preferred for compatibility.
Shared mailboxes do not require licenses and can be used for routing, monitoring, and distribution identities.
To read or delete messages from shared mailboxes using the POP3 proxy, you must add the following delegated Microsoft Graph API permissions to your app:
Mail.ReadWriteMail.ReadWrite.Shared
These permissions allow the proxy to fetch and delete messages on behalf of the user or shared mailbox.
π‘οΈ Without
Mail.ReadWritethe proxy will not be able to mark or delete messages after downloading, which may result in repeated deliveries.
β Admin consent is required for these permissions.
If your use case does not require access to incoming mail, you can disable POP3 entirely:
{
"pop3_port": null
}π This prevents the proxy from exposing any POP3 service.
- All outgoing messages use the address specified in
MAIL FROM:. - The proxy preserves full message structure: subject, HTML, attachments.
- Attachments are limited to 80MB by default (adjustable).
- All allowed users are declared in
mailboxes[]. - SMTP/POP3 clients must authenticate using one of the defined
username/passwordcombinations. - POP3 supports UIDL to avoid downloading duplicates.
MIT β Author: sh0rch