Skip to main content

Lightweight SMTP and POP3 mail proxy to Microsoft 365

Project description

SMTP & POP3 Proxy for Microsoft 365 Shared Mailboxes

PyPI version License: MIT GitHub Repo

Why This Project?

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.


โœจ New Features (v2)

โœจ Features

  • โœ… Full async SMTP/POP3 proxy
  • โœ… Microsoft Graph API integration (sendMail, get messages)
  • โœ… Shared mailbox support
  • โœ… OAuth2 Device Flow authentication
  • โœ… Encrypted token storage
  • โœ… Built-in mail queue (for offline mode)
  • โœ… Automatic resend when connection restored
  • โœ… Linux and Windows support
  • โœ… Drop-in SMTP/POP3 replacement

๐Ÿš€ Quick Start

pip install m365proxy

# First-time login to Microsoft 365 via Device Flow
m365proxy --login

# Start the proxy (SMTP + POP3)
m365proxy run

๐Ÿ“ฆ Offline Mode & Queue

When 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/

๐Ÿ“ก Architecture

  Legacy Device (SMTP) โ”€โ”€โ”€โ”€โ”€โ”
                            โ”œโ”€> SMTP Proxy โ”€โ”€> Graph API โ”€โ”€> Exchange Mailbox
  App / Printer / Camera โ”€โ”€โ”˜

  POP3 Client <โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ POP3 Proxy <โ”€โ”€โ”€ Graph API (Shared Mail)

๐Ÿ Installation

To install the proxy as a Python package:

pip install m365proxy

You can then run it via:

m365proxy

Or:

python -m m365proxy

Usage

python -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).

Options

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)

Commands

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

๐Ÿ“Œ hash requires an argument: the plain password to be hashed.


Examples

# 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 DEBUG

๐Ÿณ Docker

Hereโ€™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 -config and -token arguments.

๐ŸชŸ Windows Task Scheduler

To auto-start the proxy on Windows:

  1. Open Task Scheduler
  2. Choose Create Basic Task
  3. Trigger: At startup or Log on
  4. Action: Start a program
  5. Set Program/script: to:
    pythonw
    
    Set Add arguments: to:
    -m m365proxy -quiet
    
    Or, if using the bundled EXE, point directly to m365proxy.exe.

๐Ÿง systemd (Linux Autostart)

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.target

Then enable and start:

sudo systemctl daemon-reload
sudo systemctl enable m365proxy
sudo systemctl start m365proxy

Sample Configuration (config.json)

{
  "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.


Azure AD / Entra ID Setup

To use Microsoft Graph API, you must register your application in Microsoft Entra ID (Azure Active Directory).

1. Register Your App

  • 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)

2. Save Credentials

  • Copy the Client ID and Tenant ID into your config.json

3. Configure Permissions

  • 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

4. Enable Public Client Flow

  • Go to Authentication โ†’ Enable public client (mobile & desktop)
  • Allow device code flow

5. Authorize the Proxy

  • Run the proxy with login
  • You will be prompted to sign in once in a browser
  • After that, tokens will be refreshed automatically

Shared Mailbox Configuration

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 SendAs

Grant 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.


Additional Permissions for POP3 Access

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.ReadWrite
  • Mail.ReadWrite.Shared

These permissions allow the proxy to fetch and delete messages on behalf of the user or shared mailbox.

๐Ÿ›ก๏ธ Without Mail.ReadWrite the 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.


Disabling POP3 (Optional)

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.


Notes

  • 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/password combinations.
  • POP3 supports UIDL to avoid downloading duplicates.

License

MIT โ€” Author: sh0rch

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

m365proxy-1.2.0.tar.gz (36.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

m365proxy-1.2.0-py3-none-any.whl (38.8 kB view details)

Uploaded Python 3

File details

Details for the file m365proxy-1.2.0.tar.gz.

File metadata

  • Download URL: m365proxy-1.2.0.tar.gz
  • Upload date:
  • Size: 36.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.10

File hashes

Hashes for m365proxy-1.2.0.tar.gz
Algorithm Hash digest
SHA256 0c812160afde7881938f83443dbf7e74d967dd482cb18ca1e1a953ab2cad9bcf
MD5 ee2984ded1e4c76c67fdbe27f5358e68
BLAKE2b-256 f22fc8057a954e6886a1a8d4e6188e21d077d918e4b26fa73c64b29f6003e78c

See more details on using hashes here.

File details

Details for the file m365proxy-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: m365proxy-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 38.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.10

File hashes

Hashes for m365proxy-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1a6d5dc901863f8637ad648b3f9027904b802ca18548681fdd269e0becd2d98f
MD5 2b7a5f126a30c9cc8dc78478c14726a5
BLAKE2b-256 299e25e7b5e8f91e03b0810da859a2edbcc377269074e70471de57be59b8b109

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page