SmtpServer is a C# SMTP server you embed instead of configure
A SMTP Server component written in C#
At a glance
- What is it?
- SmtpServer is a lightweight .NET SMTP server implementation that runs inside your own process, with STARTTLS, pipelining and authentication, and where message storage, mailbox filtering and user authentication are three interfaces you implement. It is a component for developers, not a mail daemon you point at a domain.
- Who is it for?
- SmtpServer suits a .NET developer who needs a real SMTP endpoint inside an application, such as a testing tool, a capture proxy or an internal relay, because the whole server is a constructor call and the three behaviours that matter are interfaces you write. It is the wrong choice for a public mail server, since it ships no queue, no relay, no spam policy and no DKIM.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Activity is slowing. The repository last received commits 6 months ago.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A server that runs inside your process
SmtpServer is described as a lightweight but capable SMTP server implementation in C#, built entirely in .NET and leveraging the Task Parallel Library for performance. The package is on NuGet as SmtpServer, and the licence is MIT.
The thing to understand about the design is that this is not a daemon you configure. It is a component you construct in code. Starting a server takes a builder for the options, a server object, and an await on the start call:
var options = new SmtpServerOptionsBuilder()
.ServerName("localhost")
.Port(25, 587)
.Build();
var smtpServer = new SmtpServer.SmtpServer(options, ServiceProvider.Default);
await smtpServer.StartAsync(CancellationToken.None);The Port call takes two values, so a server can listen on more than one port in a single instance, and the ServerName is what the server announces itself as in the greeting.
The intended users are developers who need a functioning SMTP endpoint for something: a test harness that sends real mail, a capture service, an internal relay. The distinction from a mail transfer agent is the point. Nothing in the library decides where a message goes, because that decision is delegated to code you write.
Three interfaces decide what the server does
The README names three hooks that can be implemented: IMessageStore, IMailboxFilter and IUserAuthenticator. Everything else in the library is the protocol handling.
IMessageStore is where a delivered message ends up. The sample implementation in the README shows the shape of the contract, saving the raw bytes into a MemoryStream and then loading them as a MimeKit message:
public override async Task<SmtpResponse> SaveAsync(ISessionContext context, IMessageTransaction transaction, ReadOnlySequence<byte> buffer, CancellationToken cancellationToken)
{
await using var stream = new MemoryStream();
var position = buffer.GetPosition(0);
while (buffer.TryGet(ref position, out var memory))
{
await stream.WriteAsync(memory, cancellationToken);
}The buffer arrives as a ReadOnlySequence of bytes, so the store is handed the message as it was received rather than as a decoded object, and parsing is left to the implementer.
IMailboxFilter decides who may talk to whom. It has two methods, one for the sender side and one for the recipient side, and each returns a MailboxFilterResult. The sample rejects anything not from a specific host with NoPermanently, which is a rejection the client is told is final rather than temporary.
IUserAuthenticator does the credential check, and the sample implementation is deliberately trivial, returning true when the user name is longer than four characters. A real implementation is where you would connect to your own user store.
The extension list is short and conventional
The supported ESMTP extensions are listed explicitly, and the list is worth reading because it tells you what a client can negotiate.
STARTTLS is there, so a client can upgrade the connection to TLS after the greeting. SIZE advertises the maximum message size the server will accept. PIPELINING lets a client send several commands without waiting for a reply to each one, which is how a busy client behaves. 8BITMIME allows 8-bit content in message bodies, and SMTPUTF8 allows non ASCII addresses.
Authentication is listed as AUTH PLAIN LOGIN, which covers the two mechanisms that a client will use with a username and a password and that need no extra round trips. Notably absent from the list are the mechanisms that require challenge and response handshakes, so a client configured only for those will not authenticate here.
The combination is a sensible modern subset. A client that wants a secure connection gets STARTTLS, a client that wants to know the size limit gets SIZE, and a client that wants to send a message with an internationalised address gets SMTPUTF8.
Binding certificates and endpoints through the builder
The full example in the README shows the builder configuring an endpoint directly rather than through the shorthand Port call:
var options = new SmtpServerOptionsBuilder()
.ServerName("localhost")
.Endpoint(builder =>
builder
.Port(9025, true)
.AllowUnsecureAuthentication(false)
.Certificate(CreateCertificate()))
.Build();Three things are configured here that the short example leaves out. The port is 9025 rather than 25 or 587, and the boolean beside it marks the endpoint as secure. Unsecure authentication is switched off, so credentials are not accepted in the clear. And a certificate is attached, which is what STARTTLS negotiates with.
The certificate helper in the sample reads a PFX file from disk, and the README points out that creating an X509Certificate for testing means running MAKECERT.EXE and then PVK2PFX.EXE, with a link to a tutorial on digitallycreated.net. That is the Windows tooling era of the project.
The services themselves are then registered on a service provider and handed to the server constructor, so the same three interfaces are resolved per session rather than shared.
Per session instances through the factory pattern
The sample filter and sample authenticator each implement two interfaces, their own behaviour interface and a matching factory. The factory method, CreateInstance, takes an ISessionContext and returns a new instance.
That is the mechanism that makes the design work across sessions and across connections. A filter and an authenticator hold mutable state during a session, and the factory gives each session a fresh copy, so a decision made in one connection cannot leak into another. The server can therefore be a long lived object with a stable configuration while the per connection state stays isolated.
The ISessionContext parameter gives the implementation access to information about the session it is running in, and it is the same context type that the message store receives in its SaveAsync call, so an implementation that needs session details has one consistent place to get them from.
The filter interface is also where a server operator would enforce a policy, and the sample shows the two-axis shape of that decision: one method for the from address, one for the to address, each returning a yes or no result, with the negative case distinguished as permanent rather than temporary.
Two example projects and a repository built for a single job
The top level of the repository is compact. There is a src directory with the implementation, an examples directory, a CHANGELOG.md, a LICENSE, a README and a.github directory.
The examples directory holds two projects, SampleApp and WorkerService. A sample application is the obvious one to read, and a worker service example is the more interesting of the pair, because an SMTP server inside a background worker is a shape you actually deploy: the process runs the server and does nothing else visible.
That is the deployment story in miniature. Because the server is an object rather than a service, you decide whether it is a web application, a worker, or a process that starts the listener and then goes quiet, and the process model of the host framework applies to it.
Version 8.0.1 was tagged on 2020-11-03, 7.2.0 on 2020-06-21 and 7.1.0 on 2020-05-28. The repository is not archived and the last push was on 2026-04-18, so work continues on the master branch even though no version has been cut in several years.
What this is not, and when it is the wrong tool
The honest assessment is that SmtpServer is a protocol library, not a mail server product. The README describes an SMTP server implementation, and the implementation is exactly that: the protocol, the extensions and the hooks.
There is no message queue with retry and backoff, no relay to another host, no spam or abuse policy beyond the filter you write, no DKIM signing, no per user quota, no log analysis, and no TLS certificate management. If your requirement is to receive mail for a domain and route it into a ticket queue, this library gives you the receiving half and leaves the rest to you.
The reason to accept that is also the reason to choose it. When you need a controlled SMTP endpoint for a test, a local capture of what a client actually sends, or a relay inside an existing .NET service, embedding a few hundred lines of protocol handling is far less work than configuring and hardening a mail daemon, and the three interfaces let you keep every decision in your own codebase.
The MIT licence keeps the legal side uncomplicated for most uses, and any specific compliance question belongs with your own counsel.
Editorial conclusion
SmtpServer suits a .NET developer who needs a real SMTP endpoint inside an application, such as a testing tool, a capture proxy or an internal relay, because the whole server is a constructor call and the three behaviours that matter are interfaces you write. It is the wrong choice for a public mail server, since it ships no queue, no relay, no spam policy and no DKIM. Before adopting it, decide where messages go by implementing IMessageStore, decide who may send and receive with IMailboxFilter, and check the CHANGELOG, since the newest tag is 8.0.1 from 2020-11-03 and the last push was on 2026-04-18.
Frequently asked questions
How do I install SmtpServer?
The package is on NuGet, and the README gives the install as install-package SmtpServer from the Package Manager Console. The source is on GitHub at cosullivan/SmtpServer, and the project is MIT licensed.
Which ESMTP extensions does SmtpServer support?
The README lists STARTTLS, SIZE, PIPELINING, 8BITMIME, SMTPUTF8 and AUTH PLAIN LOGIN. Notably absent are the challenge and response authentication mechanisms.
Where do incoming messages go in SmtpServer?
You decide, by implementing the IMessageStore hook. The sample store writes the received bytes into a MemoryStream and then loads them as a MimeKit MimeMessage, and it returns SmtpResponse.Ok. The two other hooks are IMailboxFilter for who may send and receive, and IUserAuthenticator for credentials.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/cosullivan-smtpserver)Was this useful?