smtp4dev configuration can be performed via the appsettings.json file, environment variables and command-line arguments.
✨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
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.
Note that this will vary by version of smtp4dev, so for best results, open the settings file you have!
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.
To see the command line options, run Rnwood.Smtp4dev(.exe) or Rnwood.Smtp4dev.Desktop(.exe) with --help.
smtp4dev supports multiple virtual mailboxes to organize incoming messages. This is particularly useful for testing applications that send different types of emails.
- Processing Order: Mailboxes are processed in the order they appear in the configuration
- First Match Wins: Each recipient is matched against mailbox recipient patterns, and the first match determines the destination
- Default Mailbox: A default mailbox with
Recipients="*"(catch-all) is automatically added as the last mailbox - Single Delivery: Each message is delivered only once to each mailbox
- No Duplication: Due to "first match wins" logic, messages go to exactly one mailbox per recipient
- 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
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
Wildcards (recommended for simple patterns):
*@example.com- Any user at example.comuser@*- 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@
{
"ServerOptions": {
"Mailboxes": [
{
"Name": "Sales",
"Recipients": "*@sales.example.com, sales@*"
},
{
"Name": "Support",
"Recipients": "support@*, help@*"
}
]
}
}With this configuration:
john@sales.example.com→ Sales mailbox onlysupport@company.com→ Support mailbox onlyadmin@company.com→ Default mailbox only (no custom match)
{
"ServerOptions": {
"Mailboxes": [
{
"Name": "Spam",
"Recipients": "*spam*@*, *test*@*, *junk*@*"
}
]
}
}With this configuration:
spam-user@example.com→ Spam mailbox onlytest@company.com→ Spam mailbox onlyregular@company.com→ Default mailbox only
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 (*).
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
Add HeaderFilters to any mailbox configuration:
{
"ServerOptions": {
"Mailboxes": [
{
"Name": "SRS",
"Recipients": "*",
"HeaderFilters": [
{
"Header": "X-Application",
"Pattern": "srs"
}
]
}
]
}
}How it works:
- All filters must match: If multiple
HeaderFiltersare specified, ALL must match for the message to be routed to that mailbox - Header filters checked first: Before checking recipient patterns, header filters are evaluated
- First match wins: Once a mailbox matches (headers + recipients), no other mailboxes are checked
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": ".*" }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.
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:
- Client Hostname: The hostname provided by the client during EHLO/HELO command
- Client IP Address: The IP address of the connecting client
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:
- All filters must match: If multiple
SourceFiltersare specified, ALL must match for the message to be routed to that mailbox - Source filters checked first: Before checking header filters and recipient patterns, source filters are evaluated
- Matches hostname or IP: The pattern is matched against both the client hostname and IP address (first successful match wins)
- First match wins: Once a mailbox matches (source + headers + recipients), no other mailboxes are checked
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)\\..*$/" }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.
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.
First, configure users with their default mailboxes:
{
"ServerOptions": {
"Users": [
{
"Username": "USOSapi",
"Password": "secret",
"DefaultMailbox": "USOSapi"
},
{
"Username": "alice",
"Password": "secret",
"DefaultMailbox": "TeamMailbox"
}
]
}
}Route messages from specific authenticated users to their mailboxes:
{
"Mailboxes": [
{
"Name": "USOSapi",
"Recipients": "*",
"AuthenticatedUsers": ["USOSapi"]
},
{
"Name": "TeamMailbox",
"Recipients": "*",
"AuthenticatedUsers": ["alice", "bob"]
}
]
}- Messages from user
USOSapi→USOSapimailbox - Messages from users
aliceorbob→TeamMailboxmailbox - Messages from non-authenticated users →
Defaultmailbox
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: srsheader → SRS mailbox (even from authenticated userUSOSapi) - Messages from
USOSapiwithout 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.
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
aliceorbobto@team.comaddresses - Messages from anyone with
X-Team: alphaheader
-
No Message Duplication: Due to "first match wins" logic, each recipient goes to exactly one mailbox
-
Case Sensitivity: All pattern matching (recipients, headers, source, and authenticated users) is case-insensitive
-
Multiple Recipients: When an email has multiple recipients, each recipient is processed independently and may go to different mailboxes
-
Authenticated Users (DEPRECATED): The
DeliverMessagesToUsersDefaultMailboxsetting is deprecated. Use mailboxes with theAuthenticatedUsersproperty 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. -
Performance: Wildcard patterns are generally faster than regular expressions for simple matching
-
Header Filter Performance: Headers are only parsed when at least one mailbox has
HeaderFiltersconfigured -
Filter Evaluation Order: AuthenticatedUsers → Source → Headers → Recipients (all applicable filters must match for a mailbox to be selected)
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.
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.
Specifies which mailboxes should have their messages output to stdout:
*- Deliver all messages from all mailboxes to stdoutmailbox1,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": "*"
}
}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
}
}Messages delivered to stdout are wrapped with delimiters to separate multiple messages:
--- BEGIN SMTP4DEV MESSAGE ---
<raw message content>
--- END SMTP4DEV MESSAGE ---
When using deliver to stdout, all diagnostic and application logs are automatically redirected to stderr, ensuring stdout contains only message content.
Capture all messages and exit after 10:
smtp4dev --delivertostdout="*" --exitafter=10 --smtpport=2525 > messages.txt 2> logs.txtCapture only Sales mailbox messages:
smtp4dev --mailbox="Sales=*sales*@*" --delivertostdout="Sales" --smtpport=2525 > sales-messages.txtUse 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.txtWant messages in multiple mailboxes: This is not supported due to "first match wins" logic. Consider using a single mailbox with multiple recipient patterns instead.
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.
When a client connects using XOAUTH2 authentication:
- With
SmtpAllowAnyCredentials=true: Any OAuth2 token is accepted without validation (default behavior, suitable for development) - With
SmtpAllowAnyCredentials=falseand 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
To enable OAuth2 token validation, configure the following settings:
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
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
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
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
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"
}
]
}
}The token validator looks for the username/email in the following claims (in order):
sub- Subject identifieremail- Email addresspreferred_username- Preferred usernameupn- User Principal Name
The value from the first found claim must match the username provided in the XOAUTH2 authentication data.
Authentication fails with "OAuth2Authority not configured":
- Ensure
OAuth2Authorityis set whenSmtpAllowAnyCredentialsis 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
OAuth2Audienceif 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 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
See the OAuth2/XOAUTH2 with JHipster Registry example for a complete working demonstration of OAuth2 authentication using Docker.