Skip to content
DocsTry Aspire
DocsTry

MailDev and MailKit custom integrations

Aspire sample
TypeScript AppHost

Clone, run, and explore this sample

This sample demonstrates how to build custom Aspire hosting and client integrations with Aspire 13.5. The runnable AppHost is written in TypeScript and consumes the C# hosting integration through the Aspire Type System (ATS).

C#DashboardDockerHealth ChecksJavaScriptMetricsNode.jsTypeScript
AppHost

The entry point that composes every resource and dependency in this sample's distributed application.

View on GitHub
apphost.mts
import { createBuilder } from "./.aspire/modules/aspire.mjs";
const builder = await createBuilder();
const maildev = await builder.addMailDev("maildev");
await builder.addCSharpApp("newsletterservice", "./NewsletterService")
.withHttpHealthCheck({ path: "/health" })
.withExternalHttpEndpoints()
.withReference(maildev)
.waitFor(maildev);
await builder.build().run();
  • MailDev.Hosting models a MailDev container, its web and SMTP endpoints, and a deferred connection string.

  • MailKit.Client registers a scoped MailKit SMTP client, health checks, tracing, and metrics in a consuming service.

  • NewsletterService sends subscription and unsubscription messages through the integrations.

  • ServiceDefaults configures standard Aspire health checks and OpenTelemetry.

  • CSharpAppHost is a compile-validated C# equivalent of the runnable TypeScript AppHost.

The hosting integration marks MailDevResource and AddMailDev with [AspireExport]. The local project reference in aspire.config.json lets aspire restore inspect those exports and generate the TypeScript addMailDev API under .aspire/modules.

Generated files under .aspire/modules are not source files and must not be edited or committed.

"packages": {
"MailDev.Hosting": "MailDev.Hosting/MailDev.Hosting.csproj"
}

The TypeScript AppHost uses the generated API:

const maildev = await builder.addMailDev("maildev");
await builder.addCSharpApp("newsletterservice", "./NewsletterService")
.withHttpHealthCheck({ path: "/health" })
.withExternalHttpEndpoints()
.withReference(maildev)
.waitFor(maildev);

The equivalent C# AppHost code is:

var maildev = builder.AddMailDev("maildev");
builder.AddProject("newsletterservice", "../NewsletterService/NewsletterService.csproj")
.WithHttpHealthCheck("/health")
.WithExternalHttpEndpoints()
.WithReference(maildev)
.WaitFor(maildev);

AddMailDev creates a secret password parameter by default. The password is passed to MailDev through MAILDEV_INCOMING_PASS and to the newsletter service through a deferred connection string:

Endpoint=smtp://{maildev.bindings.smtp.host}:{maildev.bindings.smtp.port};Username=mail-dev;Password={maildev-password.value}

The AppHost model retains parameter and endpoint references instead of embedding a password or allocated port in source code. MailKit parses the resolved connection string, connects to SMTP, and authenticates for each service scope.

Prerequisites are .NET 10, Node.js 24 or a supported Node.js 20/22 release, Docker, and Aspire CLI 13.5.

Terminal window
aspire update --self --channel staging
aspire restore
npm install
npm run aspire:build
aspire run

Use the newsletter service endpoint shown in the Aspire dashboard:

POST /subscribe
Content-Type: application/json
{ "email": "reader@example.com" }

Open the MailDev web endpoint from the dashboard to inspect the generated message. Use POST /unsubscribe with the same payload to send the unsubscription message.

Terminal window
dotnet test MailDev.Hosting.Tests/MailDev.Hosting.Tests.csproj
dotnet test MailKit.Client.Tests/MailKit.Client.Tests.csproj
npm run aspire:build
npm run aspire:lint
dotnet build CSharpAppHost/CSharpAppHost.csproj