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).
The entry point that composes every resource and dependency in this sample's distributed application.
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();Projects
Section titled ProjectsMailDev.Hostingmodels a MailDev container, its web and SMTP endpoints, and a deferred connection string.MailKit.Clientregisters a scoped MailKit SMTP client, health checks, tracing, and metrics in a consuming service.NewsletterServicesends subscription and unsubscription messages through the integrations.ServiceDefaultsconfigures standard Aspire health checks and OpenTelemetry.CSharpAppHostis a compile-validated C# equivalent of the runnable TypeScript AppHost.
Aspire type system
Section titled Aspire type systemThe 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);Secure credentials
Section titled Secure credentialsAddMailDev 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.
Run the sample
Section titled Run the samplePrerequisites are .NET 10, Node.js 24 or a supported Node.js 20/22 release, Docker, and Aspire CLI 13.5.
aspire update --self --channel stagingaspire restorenpm installnpm run aspire:buildaspire runUse the newsletter service endpoint shown in the Aspire dashboard:
POST /subscribeContent-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.
Tests
Section titled Testsdotnet test MailDev.Hosting.Tests/MailDev.Hosting.Tests.csprojdotnet test MailKit.Client.Tests/MailKit.Client.Tests.csprojnpm run aspire:buildnpm run aspire:lintdotnet build CSharpAppHost/CSharpAppHost.csproj