Skip to content

Latest commit

 

History

History
846 lines (674 loc) · 25.1 KB

File metadata and controls

846 lines (674 loc) · 25.1 KB

smtp4dev configuration can be performed via the appsettings.json file, environment variables and command-line arguments.

In the User Interface

✨Many of the basic settings can be edited in the UI

To configure, simply start up smtp4dev and click the settings icon at the top-right of the screen.

When saved, these are written to the settings file at {AppData}/smtp4dev/appsettings.json

Configuration Files

You can find the default configuration file at <installlocation>/appsettings.json. This file is included in every release and will be overwritten when you update. To avoid this, create a 'user' configuration file at {AppData}/smtp4dev/appsettings.json and make your customisations there.

{AppData} is platform dependent but normally:

  • Windows - environment variable: APPDATA
  • Linux & Mac - environment variable: XDG_CONFIG_HOME

The search path of these files is printed when smtp4dev starts up. So the easiest way to find them is to look there:

smtp4dev version 3.3.6-ci20240419116+60aff5ea69aa19c6fb9afa8573fd5f77ab40de3a
https://github.com/rnwood/smtp4dev
.NET Core runtime version: .NET 10.0

 > For help use argument --help

Install location: C:\Users\rob
DataDir: C:\Users\rob\AppData\Roaming\smtp4dev
Default settings file: C:\Users\rob\.dotnet\tools\.store\rnwood.smtp4dev\3.3.6-ci20240419116\rnwood.smtp4dev\3.3.6-ci20240419116\tools\net10.0\any\appsettings.json
User settings file: C:\Users\rob\AppData\Roaming\smtp4dev\appsettings.json

Version 3.1.2 onwards will automatically reload and apply any edits to the configuration file without restarting.

List of app settings

Note that this will vary by version of smtp4dev, so for best results, open the settings file you have!

Environment Variables

All the values from appsettings.json can be overridden by environment variables.

Set environmment variables in the format: ServerOptions__HostName.

For arrays, use the format ServerOptions__Users__0__User where Users is the property holding the array, 0 is the index of the item and User is one of the properties of that item.

Command Line Options

To see the command line options, run Rnwood.Smtp4dev(.exe) or Rnwood.Smtp4dev.Desktop(.exe) with --help.

Mailbox Configuration

smtp4dev supports multiple virtual mailboxes to organize incoming messages. This is particularly useful for testing applications that send different types of emails.

How Mailbox Routing Works

  1. Processing Order: Mailboxes are processed in the order they appear in the configuration
  2. First Match Wins: Each recipient is matched against mailbox recipient patterns, and the first match determines the destination
  3. Default Mailbox: A default mailbox with Recipients="*" (catch-all) is automatically added as the last mailbox
  4. Single Delivery: Each message is delivered only once to each mailbox
  5. No Duplication: Due to "first match wins" logic, messages go to exactly one mailbox per recipient
  6. Multiple Rules Per Mailbox: The same mailbox name can appear multiple times with different filter combinations, enabling complex routing scenarios while maintaining a single mailbox instance

Mailbox Filters

Mailboxes support multiple types of filters that can be combined:

AuthenticatedUsers (checked first):

  • Matches messages from specific authenticated users
  • Accepts an array of usernames or a single username string
  • Messages are routed to the user's configured DefaultMailbox
  • Example: "AuthenticatedUsers": ["alice", "bob"] or "AuthenticatedUsers": "alice"

SourceFilters (checked second):

  • Matches based on client hostname or IP address
  • Supports wildcards and regex patterns

HeaderFilters (checked third):

  • Matches based on message header values
  • All header filters must match for the mailbox to be selected

Recipients (checked last):

  • Matches based on recipient email addresses
  • Supports wildcards and regex patterns

Recipient Pattern Syntax

Wildcards (recommended for simple patterns):

  • *@example.com - Any user at example.com
  • user@* - Specific user at any domain
  • *sales*@* - Any address containing "sales"

Regular Expressions (for complex patterns, surround with /):

  • /.*@(sales|marketing)\.com$/ - Addresses ending with @sales.com or @marketing.com
  • /^(admin|root)@.*$/ - Addresses starting with admin@ or root@

Common Mailbox Scenarios

Example: Department-Based Routing

{
  "ServerOptions": {
    "Mailboxes": [
      {
        "Name": "Sales",
        "Recipients": "*@sales.example.com, sales@*"
      },
      {
        "Name": "Support", 
        "Recipients": "support@*, help@*"
      }
    ]
  }
}

With this configuration:

  • john@sales.example.comSales mailbox only
  • support@company.comSupport mailbox only
  • admin@company.comDefault mailbox only (no custom match)

Example: Spam Filtering

{
  "ServerOptions": {
    "Mailboxes": [
      {
        "Name": "Spam",
        "Recipients": "*spam*@*, *test*@*, *junk*@*"
      }
    ]
  }
}

With this configuration:

  • spam-user@example.comSpam mailbox only
  • test@company.comSpam mailbox only
  • regular@company.comDefault mailbox only

Advanced Configuration

Custom Default Mailbox (Optional)

If you want to customize the default mailbox behavior, you can explicitly configure it:

{
  "ServerOptions": {
    "Mailboxes": [
      {
        "Name": "Alerts",
        "Recipients": "*alert*@*, *notification*@*"
      },
      {
        "Name": "Default",
        "Recipients": "*"
      }
    ]
  }
}

Note: Explicitly configuring the default mailbox is typically unnecessary since it's automatically added with the same pattern (*).

Header-Based Message Routing

In addition to recipient-based routing, smtp4dev supports routing messages based on email headers. This is useful when:

  • Multiple applications share the same SMTP credentials but need separate mailboxes
  • You want to filter by spam scores, antivirus headers, or custom application headers
  • Messages need to be routed based on metadata that isn't in the recipient address

Header Filter Configuration

Add HeaderFilters to any mailbox configuration:

{
  "ServerOptions": {
    "Mailboxes": [
      {
        "Name": "SRS",
        "Recipients": "*",
        "HeaderFilters": [
          {
            "Header": "X-Application",
            "Pattern": "srs"
          }
        ]
      }
    ]
  }
}

How it works:

  1. All filters must match: If multiple HeaderFilters are specified, ALL must match for the message to be routed to that mailbox
  2. Header filters checked first: Before checking recipient patterns, header filters are evaluated
  3. First match wins: Once a mailbox matches (headers + recipients), no other mailboxes are checked

Header Pattern Types

Exact/Wildcard Match (Glob):

{ "Header": "X-Application", "Pattern": "srs" }
{ "Header": "X-Mailer", "Pattern": "srs-*" }

Regular Expression (case-insensitive, surround with /):

{ "Header": "X-Priority", "Pattern": "/^(high|urgent)$/" }
{ "Header": "X-Spam-Score", "Pattern": "/^[0-4]\\./" }

Header Existence Check (any value):

{ "Header": "X-Antivirus", "Pattern": ".*" }

Example Scenarios

Route by Application Identifier:

{
  "Mailboxes": [
    {
      "Name": "SRS",
      "Recipients": "*",
      "HeaderFilters": [
        { "Header": "X-Application", "Pattern": "srs" }
      ]
    },
    {
      "Name": "USOSapi",
      "Recipients": "*"
    }
  ]
}

Messages with X-Application: srs go to "SRS" mailbox, all others go to "USOSapi" mailbox.

Route Antivirus-Scanned Messages:

{
  "Mailboxes": [
    {
      "Name": "Scanned",
      "Recipients": "*",
      "HeaderFilters": [
        { "Header": "X-Antivirus", "Pattern": ".*" }
      ]
    }
  ]
}

Any message with an X-Antivirus header (regardless of value) goes to "Scanned" mailbox.

Combine Multiple Header Filters:

{
  "Mailboxes": [
    {
      "Name": "Critical-Sales",
      "Recipients": "*@sales.com",
      "HeaderFilters": [
        { "Header": "X-Priority", "Pattern": "/^(high|urgent)$/" },
        { "Header": "X-Department", "Pattern": "sales" }
      ]
    }
  ]
}

Only messages to @sales.com with BOTH X-Priority: high (or urgent) AND X-Department: sales headers.

Source-Based Message Routing

In addition to recipient and header-based routing, smtp4dev supports routing messages based on the source of the connection. This is useful when:

  • Multiple test environments send emails through the same smtp4dev instance
  • You want to compare outputs from different application stacks
  • Messages need to be stratified based on the sending host or IP address

The source is determined from:

  1. Client Hostname: The hostname provided by the client during EHLO/HELO command
  2. Client IP Address: The IP address of the connecting client

Source Filter Configuration

Add SourceFilters to any mailbox configuration:

{
  "ServerOptions": {
    "Mailboxes": [
      {
        "Name": "Legacy Stack",
        "Recipients": "*",
        "SourceFilters": [
          {
            "Pattern": "legacy-stack.dev.example.org"
          }
        ]
      },
      {
        "Name": "Modern Stack",
        "Recipients": "*",
        "SourceFilters": [
          {
            "Pattern": "modern-stack.dev.example.org"
          }
        ]
      }
    ]
  }
}

How it works:

  1. All filters must match: If multiple SourceFilters are specified, ALL must match for the message to be routed to that mailbox
  2. Source filters checked first: Before checking header filters and recipient patterns, source filters are evaluated
  3. Matches hostname or IP: The pattern is matched against both the client hostname and IP address (first successful match wins)
  4. First match wins: Once a mailbox matches (source + headers + recipients), no other mailboxes are checked

Source Pattern Types

Exact Match (hostname or IP):

{ "Pattern": "legacy-stack.dev.example.org" }
{ "Pattern": "192.168.1.100" }

Wildcard Match:

{ "Pattern": "*.dev.example.org" }
{ "Pattern": "192.168.1.*" }
{ "Pattern": "*-stack.dev.example.org" }

Regular Expression (case-insensitive, surround with /):

{ "Pattern": "/^(legacy|modern)-stack\\.dev\\.example\\.org$/" }
{ "Pattern": "/^192\\.168\\.(1|2)\\..*$/" }

Example Scenarios

Route by Test Environment:

{
  "Mailboxes": [
    {
      "Name": "Legacy Stack",
      "Recipients": "*",
      "SourceFilters": [
        { "Pattern": "legacy-stack.dev.example.org" }
      ]
    },
    {
      "Name": "Modern Stack (DB v15)",
      "Recipients": "*",
      "SourceFilters": [
        { "Pattern": "p15.dev.example.org" }
      ]
    },
    {
      "Name": "Modern Stack (DB v18)",
      "Recipients": "*",
      "SourceFilters": [
        { "Pattern": "p18.dev.example.org" }
      ]
    }
  ]
}

Messages from legacy-stack.dev.example.org go to "Legacy Stack" mailbox, messages from p15.dev.example.org go to "Modern Stack (DB v15)" mailbox, etc.

Route by IP Address Range:

{
  "Mailboxes": [
    {
      "Name": "Production Network",
      "Recipients": "*",
      "SourceFilters": [
        { "Pattern": "10.0.1.*" }
      ]
    },
    {
      "Name": "Staging Network",
      "Recipients": "*",
      "SourceFilters": [
        { "Pattern": "10.0.2.*" }
      ]
    }
  ]
}

Messages from IPs in 10.0.1.* range go to "Production Network" mailbox, messages from 10.0.2.* go to "Staging Network" mailbox.

Combine Source, Header, and Recipient Filters:

{
  "Mailboxes": [
    {
      "Name": "Critical-Legacy-Sales",
      "Recipients": "*@sales.com",
      "SourceFilters": [
        { "Pattern": "legacy-stack.dev.example.org" }
      ],
      "HeaderFilters": [
        { "Header": "X-Priority", "Pattern": "high" }
      ]
    }
  ]
}

Only messages from legacy-stack.dev.example.org to @sales.com with X-Priority: high header.

Authenticated User Routing

The AuthenticatedUsers property enables routing messages based on who sent them (the authenticated user), rather than just the recipient address. This is useful when multiple applications share the same SMTP credentials but need different routing.

Setup Users

First, configure users with their default mailboxes:

{
  "ServerOptions": {
    "Users": [
      {
        "Username": "USOSapi",
        "Password": "secret",
        "DefaultMailbox": "USOSapi"
      },
      {
        "Username": "alice",
        "Password": "secret",
        "DefaultMailbox": "TeamMailbox"
      }
    ]
  }
}

Example: Simple Authenticated User Routing

Route messages from specific authenticated users to their mailboxes:

{
  "Mailboxes": [
    {
      "Name": "USOSapi",
      "Recipients": "*",
      "AuthenticatedUsers": ["USOSapi"]
    },
    {
      "Name": "TeamMailbox",
      "Recipients": "*",
      "AuthenticatedUsers": ["alice", "bob"]
    }
  ]
}
  • Messages from user USOSapiUSOSapi mailbox
  • Messages from users alice or bobTeamMailbox mailbox
  • Messages from non-authenticated users → Default mailbox

Example: Authenticated Users with Header-Based Override

This solves the common use case where multiple applications share SMTP credentials but need different routing based on custom headers:

{
  "Mailboxes": [
    {
      "Name": "SRS",
      "Recipients": "*",
      "HeaderFilters": [
        { "Header": "X-Application", "Pattern": "srs" }
      ]
    },
    {
      "Name": "USOSapi",
      "Recipients": "*",
      "AuthenticatedUsers": ["USOSapi"]
    }
  ]
}

With user USOSapi configured with DefaultMailbox: "USOSapi":

  • Messages with X-Application: srs header → SRS mailbox (even from authenticated user USOSapi)
  • Messages from USOSapi without that header → USOSapi mailbox
  • Messages from non-authenticated users → Default mailbox

Order matters! Place specific routing rules (header filters, recipient patterns) before the AuthenticatedUsers rule to give them priority.

Example: Same Mailbox with Multiple Rules

Mailboxes can appear multiple times with different filter combinations:

{
  "Mailboxes": [
    {
      "Name": "TeamMailbox",
      "Recipients": "*@team.com",
      "AuthenticatedUsers": ["alice", "bob"]
    },
    {
      "Name": "TeamMailbox",
      "Recipients": "*",
      "HeaderFilters": [
        { "Header": "X-Team", "Pattern": "alpha" }
      ]
    }
  ]
}

This routes messages to TeamMailbox from multiple sources while maintaining a single mailbox instance:

  • Messages from alice or bob to @team.com addresses
  • Messages from anyone with X-Team: alpha header

Important Notes

  1. No Message Duplication: Due to "first match wins" logic, each recipient goes to exactly one mailbox

  2. Case Sensitivity: All pattern matching (recipients, headers, source, and authenticated users) is case-insensitive

  3. Multiple Recipients: When an email has multiple recipients, each recipient is processed independently and may go to different mailboxes

  4. Authenticated Users (DEPRECATED): The DeliverMessagesToUsersDefaultMailbox setting is deprecated. Use mailboxes with the AuthenticatedUsers property instead for better flexibility. The old setting bypasses ALL routing rules (including header filters), while the new property allows you to position authenticated user routing anywhere in the mailbox order.

  5. Performance: Wildcard patterns are generally faster than regular expressions for simple matching

  6. Header Filter Performance: Headers are only parsed when at least one mailbox has HeaderFilters configured

  7. Filter Evaluation Order: AuthenticatedUsers → Source → Headers → Recipients (all applicable filters must match for a mailbox to be selected)

Troubleshooting

Messages not appearing in expected mailbox: Verify the order of mailboxes - earlier mailboxes take precedence over later ones due to "first match wins" logic.

Regex not working: Ensure the pattern is surrounded by forward slashes (/pattern/) and test the regex with an online regex tester.

Source filter not matching: Check the SMTP logs to see what hostname/IP the client is using. Remember that patterns match against both hostname and IP address.

Deliver to Stdout Feature

smtp4dev can output raw message content to stdout for automated processing or testing scenarios. This feature is useful for CI/CD pipelines, automated testing, or integrating with other tools.

Configuration Options

DeliverToStdout

Specifies which mailboxes should have their messages output to stdout:

  • * - Deliver all messages from all mailboxes to stdout
  • mailbox1,mailbox2 - Deliver only messages from specified mailboxes (comma-separated)
  • Empty string (default) - Disable stdout delivery

Command Line: --delivertostdout="*" or --delivertostdout="Sales,Support"

Configuration File:

{
  "ServerOptions": {
    "DeliverToStdout": "*"
  }
}

ExitAfterMessages

Automatically exit the application after delivering a specified number of messages to stdout. Useful for automated testing scenarios.

Command Line: --exitafter=5

Configuration File:

{
  "ServerOptions": {
    "ExitAfterMessages": 5
  }
}

Message Format

Messages delivered to stdout are wrapped with delimiters to separate multiple messages:

--- BEGIN SMTP4DEV MESSAGE ---
<raw message content>
--- END SMTP4DEV MESSAGE ---

Logging Behavior

When using deliver to stdout, all diagnostic and application logs are automatically redirected to stderr, ensuring stdout contains only message content.

Example Usage

Capture all messages and exit after 10:

smtp4dev --delivertostdout="*" --exitafter=10 --smtpport=2525 > messages.txt 2> logs.txt

Capture only Sales mailbox messages:

smtp4dev --mailbox="Sales=*sales*@*" --delivertostdout="Sales" --smtpport=2525 > sales-messages.txt

Use in CI/CD pipeline:

# Start smtp4dev in background, send test emails, capture output
smtp4dev --delivertostdout="*" --exitafter=3 --smtpport=2525 --imapport=0 --pop3port=0 > captured-emails.txt 2>&1 &
# ... send test emails ...
# Process captured-emails.txt

Want messages in multiple mailboxes: This is not supported due to "first match wins" logic. Consider using a single mailbox with multiple recipient patterns instead.

OAuth2/XOAUTH2 Authentication

smtp4dev supports OAuth2/XOAUTH2 authentication for SMTP connections, allowing you to validate access tokens against an Identity Provider (IDP) such as Azure AD, Google, Okta, or any OpenID Connect compatible provider.

How OAuth2 Authentication Works

When a client connects using XOAUTH2 authentication:

  1. With SmtpAllowAnyCredentials=true: Any OAuth2 token is accepted without validation (default behavior, suitable for development)
  2. With SmtpAllowAnyCredentials=false and OAuth2Authority configured:
    • The access token is validated against the configured IDP
    • Token signature, expiration, issuer, and audience are verified
    • The subject claim from the token must match the provided username (case-insensitive)
    • The username must exist in the configured Users list
    • Authentication fails if validation fails, the subject doesn't match, or the user is not configured

Configuration

To enable OAuth2 token validation, configure the following settings:

OAuth2Authority (Required for validation)

The OpenID Connect authority URL for your IDP. This URL should point to the base URL where the OpenID Connect discovery document can be found.

Examples:

  • Azure AD (multi-tenant): https://login.microsoftonline.com/common/v2.0
  • Azure AD (single tenant): https://login.microsoftonline.com/{tenant-id}/v2.0
  • Google: https://accounts.google.com
  • Okta: https://{your-domain}.okta.com/oauth2/default

Command Line: --oauth2authority="https://login.microsoftonline.com/common/v2.0"

Configuration File:

{
  "ServerOptions": {
    "OAuth2Authority": "https://login.microsoftonline.com/common/v2.0"
  }
}

Environment Variable: ServerOptions__OAuth2Authority

OAuth2Audience (Optional but recommended)

The expected audience value for tokens. If specified, tokens must be issued for this audience.

Command Line: --oauth2audience="your-app-id"

Configuration File:

{
  "ServerOptions": {
    "OAuth2Audience": "api://your-application-id"
  }
}

Environment Variable: ServerOptions__OAuth2Audience

OAuth2Issuer (Optional)

The expected issuer value for tokens. If not specified, the issuer is validated using the authority's discovery document.

Command Line: --oauth2issuer="https://login.microsoftonline.com/{tenant-id}/v2.0"

Configuration File:

{
  "ServerOptions": {
    "OAuth2Issuer": "https://login.microsoftonline.com/{tenant-id}/v2.0"
  }
}

Environment Variable: ServerOptions__OAuth2Issuer

Users (Required when SmtpAllowAnyCredentials=false)

When OAuth2 authentication is used with SmtpAllowAnyCredentials=false, you must configure the allowed users. The username in the Users list must match the subject claim from the OAuth2 token (case-insensitive).

Command Line: --user="john@example.com=password"

Note: The password field is required for the Users configuration but is not used for OAuth2 authentication. You can set it to any value.

Configuration File:

{
  "ServerOptions": {
    "Users": [
      {
        "Username": "john@example.com",
        "Password": "not-used-for-oauth2"
      },
      {
        "Username": "jane@example.com",
        "Password": "not-used-for-oauth2"
      }
    ]
  }
}

Environment Variable: ServerOptions__Users__0__Username, ServerOptions__Users__0__Password

Complete Example

Azure AD Configuration:

{
  "ServerOptions": {
    "AuthenticationRequired": true,
    "SmtpAllowAnyCredentials": false,
    "OAuth2Authority": "https://login.microsoftonline.com/common/v2.0",
    "OAuth2Audience": "api://your-application-id",
    "SmtpEnabledAuthTypesWhenNotSecureConnection": "XOAUTH2",
    "SmtpEnabledAuthTypesWhenSecureConnection": "XOAUTH2",
    "Users": [
      {
        "Username": "john@example.com",
        "Password": "not-used-for-oauth2"
      }
    ]
  }
}

Google OAuth2 Configuration:

{
  "ServerOptions": {
    "AuthenticationRequired": true,
    "SmtpAllowAnyCredentials": false,
    "OAuth2Authority": "https://accounts.google.com",
    "OAuth2Audience": "your-google-client-id.apps.googleusercontent.com",
    "Users": [
      {
        "Username": "john@example.com",
        "Password": "not-used-for-oauth2"
      }
    ]
  }
}

Subject Claim Mapping

The token validator looks for the username/email in the following claims (in order):

  1. sub - Subject identifier
  2. email - Email address
  3. preferred_username - Preferred username
  4. upn - User Principal Name

The value from the first found claim must match the username provided in the XOAUTH2 authentication data.

Troubleshooting

Authentication fails with "OAuth2Authority not configured":

  • Ensure OAuth2Authority is set when SmtpAllowAnyCredentials is false
  • Verify the authority URL is correct and accessible

Authentication fails with "Token validation error":

  • Check that the token hasn't expired
  • Verify the token was issued by the configured authority
  • Ensure the audience claim matches OAuth2Audience if configured
  • Check server logs for detailed error messages

Authentication fails with "subject mismatch":

  • The subject claim in the token must match the username provided in the XOAUTH2 authentication
  • Both values are compared case-insensitively
  • Check which claim is being used (sub, email, preferred_username, or upn)

Authentication fails with "username not in configured users list":

  • When SmtpAllowAnyCredentials=false, the username must be in the configured Users list
  • Add the user to the Users configuration with any password (password is not used for OAuth2)
  • Ensure the username matches the subject claim from the token (case-insensitive)

Development vs Production

Development Mode (default):

{
  "ServerOptions": {
    "SmtpAllowAnyCredentials": true
  }
}
  • Any credentials are accepted
  • OAuth2 tokens are not validated
  • Suitable for local development and testing

Production Mode:

{
  "ServerOptions": {
    "AuthenticationRequired": true,
    "SmtpAllowAnyCredentials": false,
    "OAuth2Authority": "https://your-idp.com",
    "OAuth2Audience": "your-app-id",
    "Users": [
      {
        "Username": "allowed-user@example.com",
        "Password": "not-used-for-oauth2"
      }
    ]
  }
}
  • Credentials are validated
  • OAuth2 tokens are validated against the IDP
  • Subject must match username
  • Username must be in configured Users list

Working Example

See the OAuth2/XOAUTH2 with JHipster Registry example for a complete working demonstration of OAuth2 authentication using Docker.