Email sending library for the pidgn web framework.
A pluggable email delivery library built on the adapter pattern. Ships with adapters for SMTP, SendGrid, Mailgun, and several development/testing helpers. Supports MIME multipart, attachments, rate limiting, and telemetry hooks.
- Adapter pattern -- compile-time validated interface with
init,deinit, andsend - SMTP adapter -- direct SMTP delivery with STARTTLS and authentication
- SendGrid adapter -- SendGrid v3 REST API integration
- Mailgun adapter -- Mailgun REST API with multipart form encoding
- Dev adapter -- in-memory ring buffer with a web UI for local development
- Log adapter -- prints emails to stderr, always returns success
- Test adapter -- in-memory store with assertion helpers (
allSentCount,lastSentSubject,sentToAddress) - Email composition -- to, cc, bcc, reply-to, text/HTML bodies, attachments, custom headers
- Attachments -- binary content with content type, disposition (inline or attachment), and content ID
- Rate limiting -- token-bucket rate limiter with configurable max-per-second
- Telemetry -- event hooks for
email_sending,email_sent,email_failed, andrate_limited
const pidgn_mailer = @import("pidgn_mailer");
const Mailer = pidgn_mailer.Mailer;
const SmtpAdapter = pidgn_mailer.SmtpAdapter;
const Email = pidgn_mailer.Email;
var mailer = Mailer(SmtpAdapter).init(.{
.adapter = .{
.host = "smtp.example.com",
.port = 587,
.username = "user",
.password = "pass",
.use_starttls = true,
},
});
defer mailer.deinit();
const email = Email{
.from = .{ .email = "noreply@example.com", .name = "My App" },
.to = &.{.{ .email = "user@example.com" }},
.subject = "Welcome!",
.text_body = "Thanks for signing up.",
.html_body = "<h1>Welcome!</h1><p>Thanks for signing up.</p>",
};
const result = mailer.send(email, allocator);
if (!result.success) {
std.log.err("Send failed: {s}", .{result.error_message orelse "unknown"});
}var mailer = Mailer(pidgn_mailer.SendGridAdapter).init(.{
.adapter = .{ .api_key = "SG.your-api-key" },
});
defer mailer.deinit();
_ = mailer.send(email, allocator);var mailer = Mailer(pidgn_mailer.MailgunAdapter).init(.{
.adapter = .{
.api_key = "key-your-api-key",
.domain = "mg.example.com",
},
});
defer mailer.deinit();
_ = mailer.send(email, allocator);The dev adapter stores emails in memory so you can inspect them through a web UI during local development.
var mailer = Mailer(pidgn_mailer.DevAdapter).init(.{});
defer mailer.deinit();
_ = mailer.send(email, allocator);
// Query stored emails
const count = mailer.adapter.sentCount();
const stored = mailer.adapter.getEmail(0).?;
std.debug.print("Subject: {s}\n", .{stored.getSubject()});test "sends welcome email" {
var mailer = Mailer(pidgn_mailer.TestAdapter).init(.{});
defer mailer.deinit();
_ = mailer.send(email, std.testing.allocator);
try std.testing.expectEqual(@as(usize, 1), mailer.adapter.allSentCount());
try std.testing.expectEqualStrings("Welcome!", mailer.adapter.lastSentSubject().?);
try std.testing.expect(mailer.adapter.sentToAddress("user@example.com"));
}var mailer = Mailer(SmtpAdapter).init(.{
.adapter = .{ .host = "smtp.example.com" },
.rate_limit = .{ .max_per_second = 10.0 },
});var telemetry = pidgn_mailer.Telemetry{};
telemetry.attach(&myHandler);
var mailer = Mailer(SmtpAdapter).init(.{ .adapter = .{} });
mailer.telemetry = &telemetry;| Adapter | Use Case | Config |
|---|---|---|
SmtpAdapter |
Production SMTP delivery | host, port, username, password, use_starttls |
SendGridAdapter |
SendGrid v3 API | api_key |
MailgunAdapter |
Mailgun API | api_key, domain |
DevAdapter |
Local development with web UI | (none) |
LogAdapter |
Debug logging to stderr | prefix |
TestAdapter |
Unit test assertions | (none) |
zig build # Build
zig build test # Run testsFull documentation available at docs.pidgn.indielab.link under the Mailer section.
| Package | Description |
|---|---|
| pidgn.zig | Core web framework |
| pidgn_db | Database ORM (SQLite + PostgreSQL) |
| pidgn_jobs | Background job processing |
| pidgn_mailer | Email sending |
| pidgn_template | Template engine |
| pidgn_cli | CLI tooling |
- Zig 0.16.0-dev.2535+b5bd49460 or later
MIT License -- Copyright (c) 2026 Ivan Stamenkovic