An SMTP client and server library for Zig implementing RFC 5321.
2.3 kB
74 lines
1<!--
2SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>
3SPDX-License-Identifier: MIT
4-->
5
6# zsmtp
7
8An SMTP client and server library for Zig (RFC 5321).
9
10Both the client and the server run over plain `std.Io.Reader`/`std.Io.Writer`
11pairs, so they are transport-agnostic: wrap a TCP stream for real use, or
12fixed in-memory buffers in tests. Requires Zig 0.16.
13
14## Client
15
16```zig
17const zsmtp = @import("zsmtp");
18
19var reply_buf: [1024]u8 = undefined;
20var client: zsmtp.Client = .init(&stream_reader.interface, &stream_writer.interface, &reply_buf);
21
22_ = try client.greet(); // read the 220 greeting
23_ = try client.hello("my-host.example.com"); // EHLO (HELO fallback), returns extensions
24try client.sendMail("me@example.com", &.{"you@example.net"}, message);
25try client.quit();
26```
27
28Line endings in the message are normalized to CRLF and leading dots are
29stuffed automatically. On `error.UnexpectedReply`, `client.last_reply` holds
30the server's actual code and text. `mailFrom`/`rcptTo`/`sendMessage` are also
31available individually, as is `authPlain`.
32
33## Server
34
35```zig
36var session: zsmtp.Server = .init(&stream_reader.interface, &stream_writer.interface, .{
37 .context = &my_state,
38 .vtable = &.{
39 .rcptTo = onRcptTo, // optional; accept/reject each recipient
40 .message = onMessage, // required; receives envelope + message data
41 },
42}, .{ .hostname = "mx.example.com" });
43try session.run(gpa);
44```
45
46`run` serves one connection until QUIT or disconnect, enforcing command
47sequencing, recipient and message-size limits, and un-stuffing message data.
48Listening, accepting, and concurrency are up to the caller.
49
50## Demo CLI
51
52```sh
53zig build
54
55# Debug server that prints received messages to stdout:
56./zig-out/bin/zsmtp serve 2525
57
58# Send a message read from stdin:
59printf 'Subject: hi\r\n\r\nhello\r\n' | \
60 ./zig-out/bin/zsmtp send 127.0.0.1 2525 me@example.com you@example.net
61```
62
63## Status
64
65Plaintext SMTP only for now — STARTTLS/implicit TLS is the next planned step
66(the transport-agnostic design is meant to make that a drop-in layer).
67Not yet implemented: TLS, streaming (non-slice) message bodies, AUTH beyond
68PLAIN, and ESMTP parameter handling (SIZE=, BODY=) on the server side.
69
70## Tests
71
72```sh
73zig build test
74```