24 Apr. 2025
SMTP Server mit C#
Intro
Ein Mail-Server kann als Bridge zwischen zwei Systemem funktionieren. Dabei kann mittels Mail-Client eine Email an eine Adresse verschickt werden und der Anhang und die Mail im Dateisystem gespeichert werden. Oder ein KI System sortiert die Dateien in die passenden Ordner.
Nach langer Suche habe ich eine kleine Bibliothek namens SmtpServer gefunden. Das Elegante hieran ist, dass man mittels Listener-Pattern einen oder mehrere Klassen zum Abarbeiten der Anfragen in eine Pipeline hängen kann.
Server starten
Es werden nur wenige Zeilen Code benötigt, um den Server z.B. in einer Konsolenapplikation zu starten.
static async Task Main(string[] args)
{
Console.WriteLine("Starting SMTP Server");
var options = new SmtpServerOptionsBuilder()
.ServerName("localhost")
.Port(25)
.Build();
//This is used to intercept the processing and inject hooks
var serviceProvider = new ServiceProvider();
//Let us start the server and wait
var smtpServer = new SmtpServer.SmtpServer(options, serviceProvider);
await smtpServer.StartAsync(CancellationToken.None);
}
Der Server ist jetzt gestartet und empfängt Emails. Testen kann man das mit einem einfach PowerShell
Befehl.
Send-MailMessage -To foo@bar.de -Subject "Hello User" -Body "This is the message" -SmtpServer "localhost" -From "bar@foo.de"
Es erscheint nich nichts, weil wir keinen Listener implementiert haben.
Email lesen und abarbeiten
Für einen Listener muss eine Klasse von Typ MessageStore
ableiten und die Methode SaveAsync()
implementieren. In meinem Fall nehme ich eine kleine Bibliothek namens MimeKit
zur Hilfe, um die Anhänge zu lesen.
internal class SampleMessageStore : MessageStore
{
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);
}
stream.Position = 0;
var message = await MimeKit.MimeMessage.LoadAsync(stream, cancellationToken);
Console.WriteLine("Mail received: " + message.Subject);
foreach (var attachment in message.Attachments)
{
Console.WriteLine($"...Attachment found: {(attachment as MimePart)?.FileName} - {(attachment as MimePart)?.ContentType.MimeType}");
}
return SmtpResponse.Ok;
}
}
Anschließend muss die Klasse im ServiceProvider
registriert werden.
//This is used to intercept the processing and inject hooks
var serviceProvider = new ServiceProvider();
serviceProvider.Add(new SampleMessageStore());
Und nun erscheint eine kurze Ausgabe, wenn eine Email angekommen ist. Hierfür senden wir noch zwei Attachments mit.
Send-MailMessage -To foo@bar.de -Subject "Hello User" -Body "This is the message" -SmtpServer "localhost" \
-From "bar@foo.de" -Attachments .\README.md,.\SimpleSmtpServer\Program.cs