Skip to content

bxdavies/MustMail

Repository files navigation


MustMail

MustMail is a small SMTP server that receives emails and then sends them using Microsoft Graph.
Report Bug · Request Feature · Get Support

About

As of January 2023, Microsoft disabled basic authentication for Exchange, requiring users to switch to OAuth. While basic authentication for SMTP AUTH is still available if you have "Security defaults" turned off, this application offers a solution if you want "Security Defaults" enabled and the application you are trying to send emails from does not support SMTP AUTH using OAuth, and you cannot or do not want to use direct send.

An SMTP server and mail gateway that accepts incoming email and relays it through the Microsoft Graph API using OAuth authentication. It supports SMTP authentication, TLS encryption, message storage, and provides a web interface for managing mail.

Features:

  • SMTP authentication support
  • TLS encryption (STARTTLS and implicit TLS)
  • Local mail storage
  • Web interface for browsing messages and configuring the application
  • OAuth authentication with Microsoft Graph for outbound delivery
  • Restrict allowed sender or recipient addresses
  • Send from any user or shared mailbox in your Microsoft 365 Tenant including shared mailboxes and aliases

Screenshots

Roadmap

v0.3.1

  • See if the application can support sending from aliases Implemented

v0.4.x

  • Support additional database types Implemented
  • Support additional logging sinks File and Syslog sinks implemented
  • Add health check endpoint Implemented
  • Create SMTP accounts from environment variables so the web interface is not required for initial setup Implemented

v0.5.x

  • Improve To and From restrictions, for example by supporting wildcard patterns such as *@example.com Implemented

v0.6.x

  • Retry failed email deliveries with backoff
  • Send delivery errors to a notification address instead of only writing them to the logs

v0.7.x

  • Add support for the Gmail Send API, subject to further research

v0.8.x

  • Trigger webhooks when emails are received
  • Don't require an account to already exist to store email
  • Support a mode that does not require Microsoft Graph or the Gmail Send API, allowing emails to be viewed only through the web interface

v0.9.x

  • Store emails that repeatedly fail to send and notify the notification address so an administrator can review them
  • Add an admin dashboard for email statistics and API endpoint
  • Add search in the web interface

Prerequisites

  • A Microsoft 365 Tenant.
  • A user with appropriate admin roles (Global Administrator, Privileged Role Administrator, Application Administrator, or Cloud Application Administrator) who can grant Application Mail.Send, User.Read.All and MailboxSettings.Read API permissions.
Permission Why it's needed
Mail.Send Send email through Microsoft Graph
User.Read.All Validate sender mailboxes and aliases
MailboxSettings.Read Retrieve mailbox settings and validate mailbox availability
  • OIDC provider - e.g Keycloak

High level setup

  1. Create Azure App, see the Azure App section.
  2. Configure an IDP provider, see the IDP Configuration section.
  3. Run the application, see the Running MustMail section.
  4. First login and SMTP account creation, see the First Run section.
  5. Test MustMail to confirm it is working, see the Testing section.
  6. Use MustMail to send an email, see the Usage section.

Azure App Creation

  1. Go to the 'App registrations' section in Azure.
  2. Click 'New Registration'.
  3. Enter a name and leave everything else as default.
  4. Navigate to 'API permissions' pane and click 'Add a permission'.
  5. Choose 'Microsoft Graph', then select 'Application permissions', then find Mail.Send and tick it. Do the same for User.Read.All and MailboxSettings.Read. Finally, press 'Add permissions'.
  6. Grant admin consent by clicking 'Grant admin consent for Tenant Name' (where Tenant Name is the name of your Microsoft 365 tenant). Hit 'Yes' at confirmation.
  7. Navigate to 'Certificates & secrets' pane, choose the 'Client secrets' tab, then click 'New client secret', enter a description and set expiry to 24 months or a custom value. Copy the secret value and set the environment variable Graph__ClientSecret to this value.

Tip

Set a reminder in your calendar now for 24 months' time to renew and update this secret.

  1. Copy the secret value and make note of it.

Important

The secret value is only displayed once.

  1. Navigate to the 'Overview' pane and copy the 'Application (client) ID' and set the environment variable Graph__ClientId to this value. Copy the 'Directory (tenant) ID' and set the environment variable Graph__TenantId to this value

IDP Configuration

To use MustMail you must provide an Identity provider that supports OpenID Connect (OIDC). Instructions for Keycloak and Microsoft Entra are provided but any identity provider should work.

Keycloak

Keycloak is an Open Source Identity and Access Management.This section details how to use it with MustMail.

  1. Navigate to the Administration Console, click 'Clients' from the left hand side and then click 'Create client'.
  2. Set name. Set the environment variable OpenIdConnect__ClientId to this value.

  1. Enable 'Client authentication'.

  1. Set 'Root URL', 'Valid redirect URLs' and 'Web origins'. Redirect URL should be address followed by /signin-oidc

  1. Once the client has been created, click 'Clients' from the left hand side, find the client, and switch to the Credentials tab. Copy the client secret. Set the environment variable OpenIdConnect__ClientSecret to this value.

  1. Set the environment variable OpenIdConnect__Authority to this value YOURKEYCLOAKADDRESS/realms/YOURREALMNAME/. Replacing YOURKEYCLOAKADDRESS and YOURREALMNAME respectively.

Microsoft Entra ID

Microsoft Entra ID is a cloud-based identity and access management solution. This section details how to use it with MustMail.

Tip

You can use the same App you created in Azure App Creation section. If doing this add the Redirect URI by going to the 'Authentication (Preview)' pane, clicking 'Add redirect URI', choosing 'Web' and the using the same redirect URI format in step 2.

  1. Go to the 'App registrations' section in Azure.
  2. Click 'New Registration'.
  3. Set the app name, select web in the 'Select a platform' dropdown and set the Redirect URI. Redirect URI should be address followed by /signin-oidc.

  1. Navigate to the 'Certificates and secrets' pane. Change to the 'Client secrets' tab and click 'New client secret'. Set a description and expiry date and click 'Add'. Copy the secret value and set the environment variable OpenIdConnect__ClientSecret to this value.

Important

The secret value is only displayed once.

  1. Navigate to the 'Token configuration' pane and click 'Add optional claim'. Chose ID and then tick the email checkbox. Finally click Add.

Important

You will get a popup saying "Some of these claims (email) require OpenId Connect scopes to be configured through the API permissions page or by checking the box below. Learn more". Tick the box 'Turn on the Microsoft Graph email permission (required for claims to appear in token)'.

  1. Navigate to the 'Overview' pane and copy the 'Application (client) ID'. Set the environment variable OpenIdConnect__ClientId to this value.
  2. Click 'Endpoints' and copy the 'Authority URL (Accounts in this organizational directory only)'. Set the environment variable OpenIdConnect__Authority to this value.

Generic

This section details for how to use an Identity provider not listed.

The OpenIdConnect__Authority environment variable should be set to base domain, MustMail will automatically perform OpenID discovery on /.well-known/openid-configuration

Redirect URL should be set to /signin-oidc

Client ID and Client Secret must be created in the provider and then set with the OpenIdConnect__ClientId and OpenIdConnect__ClientSecret environment variables.

MustMail will use the claim name by default to fetch the user's name, however this can be overridden with OpenIdConnect__NameClaim environment variable.

Running MustMail

Docker image (recommend)

Run MustMail in a container with these simple steps.

Warning

MustMail stores its database, certificates, and configuration in the Data directory. To avoid losing data when the container is recreated, you must mount /app/Data to persistent storage.

Docker run

Override any environment variable below to match your setup.

docker run --name MustMail \
-e Graph__TenantId=your-tenant-id \
-e Graph__ClientId=your-client-id \
-e Graph__ClientSecret=your-client-secret \
-e OpenIdConnect__Authority=https://keycloak.example.com/realms/master/ \
-e OpenIdConnect__ClientId=mustmail \
-e OpenIdConnect__ClientSecret=your-oidc-client-secret \
-v mustmail-data:/app/Data \
-p 8080:8080 \
-p 465:465 \
-p 587:587 \
-d ghcr.io/bxdavies/mustmail

Docker compose

Use Docker Compose for easier management. Fill in your values and you’re ready to go.

services:
  mustmail:
    image: ghcr.io/bxdavies/mustmail
    container_name: mustmail
    environment:
      - Graph__TenantId=your-tenant-id
      - Graph__ClientId=your-client-id
      - Graph__ClientSecret=your-client-secret
      - OpenIdConnect__Authority=https://keycloak.example.com/realms/master/
      - OpenIdConnect__ClientId=mustmail
      - OpenIdConnect__ClientSecret=your-oidc-client-secret
    volumes:
      - ./data/mustmail:/app/Data
    ports:
      - 8080:8080
      - 465:465
      - 587:587 
    restart: unless-stopped

Application will start the web interface on port 8080 by default and SMTP server on 465 and 587 using TLS and authentication

Windows

  1. Download the binary release MustMail-v0.0.0-win-x64.zip (where 0.0.0 is the latest version) from here.
  2. Extract the zip file to C:\MustMail.
  3. Open a Command Prompt window and use the following commands to set the environment variables
setx Graph__TenantId "your-tenant-id"
setx Graph__ClientId "your-client-id"
setx Graph__ClientSecret "your-client-secret"
setx OpenIdConnect__Authority "https://keycloak.example.com/realms/master/"
setx OpenIdConnect__ClientId "mustmail"
setx OpenIdConnect__ClientSecret "your-oidc-client-secret"
setx Certificate__Password "password"

  1. Launch the executable, approve the firewall prompt, and test with SmtpTest .
  2. Open 'Task Scheduler'.
  3. Click 'Import Task' and select the MustMail.xml file located at C:\MustMail.
  4. Click 'Change User or Group'.
  5. Enter your username (your folder name in C:\Users) in the textbox and click 'Check Names'. It should find your username, then click 'Ok'.
  6. Click 'OK'.
  7. Right-click on the task and press 'Run'.
  8. Test again with SmtpTest.

Application will start the web interface on port 5000 by default and SMTP server on 465 and 587 using TLS and authentication

Linux (untested)

  1. Download the binary release MustMail-v0.0.0-linux-x64.tar.gz (replace 0.0.0 with the latest version) from here.
  2. Extract the archive to /opt/MustMail (you may need sudo):
sudo mkdir -p /opt/MustMail
sudo tar -xzf MustMail-v0.0.0-linux-x64.tar.gz -C /opt/MustMail
  1. Change to the installation directory: cd /opt/MustMail.
  2. Make the executable runnable: sudo chmod +x MustMail.
  3. Create a systemd service to run MustMail once at startup after the network is online:
    1. Create the service file: sudo nano /etc/systemd/system/mustmail.service
    2. Add this:
[Unit]
Description=Run MustMail once after network is online
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
WorkingDirectory=/opt/MustMail
ExecStart=/opt/MustMail/MustMail
Restart=always
RestartSec=10

Environment="Graph__TenantId=your-tenant-id"
Environment="Graph__ClientId=your-client-id"
Environment="Graph__ClientSecret=your-client-secret"

Environment="OpenIdConnect__Authority=https://keycloak.example.com/realms/master/"
Environment="OpenIdConnect__ClientId=mustmail"
Environment="OpenIdConnect__ClientSecret=your-oidc-client-secret"

[Install]
WantedBy=multi-user.target
  1. Reload systemd to apply changes: sudo systemctl daemon-reload.
  2. Enable the service so it runs once at boot: sudo systemctl enable mustmail.service.
  3. Reboot your system or start the service manually to test: sudo systemctl start mustmail.service.
  4. Verify it ran successfully: sudo systemctl status mustmail.service
  5. Test with telnet following these instructions from StackOverflow
  6. View logs with journalctl -u mustmail -f

Application will start the web interface on port 5000 by default and SMTP server on 465 and 587 using TLS and authentication

First run

Tip

You can skip this step by setting the environment variable Bootstrap__SMTPAccounts=username:password|username2:password2 (Accounts are created only if they do not already exist)

Before we can use MustMail we need to login and create a SMTP account. Navigate to the web app, you should be redirect to your identity provider to login. The first account to sign in is automatically assigned an admin role (more users can be set to admin's from the Admin page).

From the homepage (Root or /) click the 'Admin' link below your name, on the admin page (/admin) click the 'Add new SMTP account' button and set name, password and description and click 'Save'.

Now you can use the SMTP account to send an email to MustMail.

Testing

Tip

Test your setup with a simple SMTP test tool to make sure everything’s working before going live.

Docker

If you want to quickly test MustMail, you can spin up a test SMTP client container. For example, try morawskim/swaks:

docker run --network docker_default --rm -ti morawskim/swaks  \
  --to user@examples.com \
  --from servers@examples.com \
  --server mustmail \
  --port 587 \
  --tls \
  --auth LOGIN \
  --auth-user username \
  --auth-password 'password' \
  --header "Subject: first contact"
  • --to specifies the recipient email address. Replace this with the address you want the test email to be delivered to.
  • --from specifies the sender email address. This must match a user or shared mailbox that exists in your Microsoft 365 tenant.
  • --server specifies the hostname of the MustMail SMTP server. If you are running the test container on the same Docker network, you can usually use the container name (e.g. mustmail).
  • --port specifies the SMTP port to connect to. The default submission port is 587.
  • --tls enables TLS for the connection. This is required when using STARTTLS on port 587.
  • --auth LOGIN enables SMTP authentication using the LOGIN authentication mechanism.
  • --auth-user specifies the username for SMTP authentication. This must match the name of an SMTP account configured in MustMail.
  • --auth-password specifies the password for the SMTP account configured in MustMail.
  • --header "Subject:first contact" sets the subject line for the test email.

If you’re using Docker Compose or a custom network, update --network docker_default to match your setup.

Windows

If you want to quickly test MustMail on Windows, you can use SmtpTest developed by Kab Software

Download SmtpTest from here, extract it, launch the executable and configure it according to the settings shown in the screenshot above and use it to test your SMTP configuration.

Usage

Set up your application to send emails through MustMail by configuring the SMTP settings like this:

SMTP_HOST=localhost
SMTP_PORT=587
SMTP_FROM_EMAIL=servers@example.com
SMTP_SECURE=true
SMTP_USERNAME=test
SMTP_PASSWORD=Password

Names for these settings might vary depending on your app, check its documentation if you’re not sure.

If you’re running MustMail in Docker and your app is in another container on the same network, use the container name (e.g. mustmail) instead of localhost for SMTP_HOST.

If you are letting MustMail manage the certificate (default), it will create a self-signed certificate. Most applications will validate the certificate, so the recommended approach is to add the certificate to your system's trusted certificate store, or configure the application you are using to trust the self-signed certificate. Check your application's documentation, but something like the below may be suggested:

NODE_TLS_REJECT_UNAUTHORIZED=0 # Not recommended, as this disables certificate validation globally
FORCE_TRUST_SERVER_CERT=true

Feature Details

Trust From

When enabled, MustMail trusts the SMTP From address provided by the client. When disabled, the From address is derived from the SMTP transaction.

Footer Branding

When enabled, MustMail adds the following footer to the end of outgoing emails:

---

Sent via self-hosted MustMail

Store Emails

When enabled, MustMail stores emails received by users who have signed into MustMail and are recipients of the message.

Emails are stored in the maildrop folder within the Data directory and retained for the number of days configured by RetentionDays (7 days by default). Once the retention period expires, the emails are automatically removed.

Allowed Senders / Allowed Recipients

You can control which email addresses are permitted to send through or receive mail from MustMail using the Admin page.

Both exact email addresses and wildcard patterns are supported. For example:

alerts@example.com
*@example.com

Configuration

MustMail can be configured in two ways:

  1. Environment variables
  2. appsettings.json, stored in the Data folder. By default, the Data folder is located alongside the application executable (or /app/Data when running in Docker).

Environment variables always take precedence over values set in appsettings.json.

For most users, it is recommended to configure the application through the Admin page. Changes made there are saved to appsettings.json.

A full configuration reference is provided below for advanced users.

Using Environment Variables

Any setting in appsettings.json can be converted into an environment variable by replacing : with __ (two underscores).

For example, the following setting in appsettings.json, Smtp:Host becomes the environment variable: Smtp__Host.

Required Environment Variables

For security reasons, the following settings must be provided using environment variables and cannot be loaded from appsettings.json:

  • Graph__TenantId
  • Graph__ClientId
  • Graph__ClientSecret
  • OpenIdConnect__Authority
  • OpenIdConnect__ClientId
  • OpenIdConnect__ClientSecret
  • Certificate__Password (required only when Certificate__Format is set to PFX, which is the default on Windows)

MustMail will fail to start if any required environment variables are missing.

Databases

By default MustMail uses an SQLite database stored in the Data directory. When running in Docker this directory is located at /app/Data. However if you wish you can change this path or use a completely different database. MustMail supports the following databases:

Example connection strings are provided below, set the environment variable and MustMail will create the tables.

ConnectionStrings__Sqlite=Data Source=/path/mounted/databasename.db
ConnectionStrings__Postgres=Host=postgresql;Port=5432;Database=mustmail;Username=mustmail;Password=password
ConnectionStrings__MySQL=Server=mysql;Port=3306;Database=mustmail;Uid=mustmail;Pwd=password;
ConnectionStrings__SqlServer=Server=sqlserver;Database=mustmail;User Id=mustmail;Password=password
ConnectionStrings__AzureSql=Server=tcp:myserver.database.windows.net,1433;Database=mustmail;User ID=mustmail@myserver;Password=password;Trusted_Connection=False;Encrypt=True;

More configuration options are available for each connection string. For detailed examples and provider-specific settings, see ConnectionStrings.com or click the relevant database link above.

Certificate

By default, MustMail will manage and create a self-signed certificate and store it in the data folder. Setting Certificate__Managed=False allows you to provide your own certificate files.

The certificate format is automatically detected based on the operating system:

  • On Windows, the default format is PFX
  • On Linux/macOS, the default format is PEM

You can override this with Certificate__Format=PEM or Certificate__Format=PFX.

PEM example:

Certificate__Managed=False
Certificate__Format=PEM
Certificate__PEMCertPath=
Certificate__PEMKeyPath=

PFX example:

Certificate__Managed=False
Certificate__Format=PFX
Certificate__PFXPath=

Logging

By default logs are written to:

  • Console (all platforms)

Docker users can view logs with:

docker logs mustmail

Linux users:

journalctl -u mustmail

Windows users:

If you're just running the EXE:

When running interactively, logs are written to the console window.

If you're using the scheduled task:

Configure a file logging sink in Serilog.

appsettings.json reference

{
  "AllowedHosts": "*",
  "Urls": "https://localhost:5001/;http://localhost:5000/",
  "Smtp": {
    "Host": "localhost",
    "AllowInsecure": false,
    "InsecurePort": 25,
    "ImplicitTLSPort": 465,
    "StartTLSPort": 587
  },
  "OpenIdConnect": {
    "NameClaim": "name"
  },
  "MustMail": {
    "TrustFrom": true,
    "StoreEmails": true,
    "RetentionDays": 7,
    "AllowedSenders": [],
    "AllowedRecipients": [],
    "FooterBranding": true
  },
  "Certificate": {
    "Managed": true, // Or False
    "Format": "PFX", // Or PEM  
    "PFXPath": "/home/test/certs/MustMail.pfx", // Set this for PFX
    "PEMCertPath": "/home/test/certs/cert.pem", // Set this for PEM
    "PEMKeyPath": "/home/test/certs/key.pem", // And this for PEM as well
    "CommonName": "localhost" // Only required when Managed is true 
  },
  "Serilog": {
    "Using": [
      "Serilog.Sinks.Console",
      "Serilog.Sinks.File"
    ],
    "MinimumLevel": {
      "Default": "Debug",
      "Override": {
        "Microsoft.AspNetCore.Hosting.Diagnostics": "Information", // Request logging
        "Microsoft.EntityFrameworkCore.Database.Command": "Information" // Database command logging
      }
    },
    "WriteTo": [
      {
        "Name": "Console",
        "Args": {
          "outputTemplate": "{Timestamp:O} [{Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}"
        }
      },
      {
        "Name": "File",
        "Args": {
          "path": "logs/log-.txt",
          "rollingInterval": "Day"
        }
      },
      {
        "Name": "UdpSyslog",
        "Args": {
          "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} <s:{SourceContext}>{NewLine}{Exception}",
          "host": "syslog.host",
          "port": 12345,
          "format": "RFC5424",
          "useTls": false,
          "appName": "Application",
          "facility": "Local7"
        }
      }
    ]
  }
}

On first run, the application will automatically create and populate appsettings.json in the Data folder. By default, the Data folder is located alongside the application executable (or /app/Data when running in Docker) with the following defaults:

  • Console logging enabled
  • Managed certificates enabled and saved in the Data folder
  • SQLite database stored in the Data directory
  • OpenID Connect name claim set to name

After the first run, you can modify the appsettings.json file and the application will load the configuration. For example if you wanted to add a file logging sink you would add the sink to Using and add the 'Name' and 'Args' to the WriteTo section.

Serilog configuration details can be found on their wiki here.

Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.

If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Acknowledgments

SmtpServer by Cain O'Sullivan - an SMTP Server component written in C#

Swaks by John Jetmore - Swaks is a featureful, flexible, scriptable, transaction-oriented SMTP test tool

morawskim/swaks & packer-images by Marcin Morawski - Swaks in a docker container.

SmptTest by Kab Software - SmtpTest is a small, 64bit Windows program that lets you easily test SMTP servers and relays

License

Distributed under the AGPL-3.0 License. See LICENSE.txt for more information.

About

MustMail is a simple SMTP server which receives emails and then sends them using Microsoft Graph

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors