An SMTP client and server library for Zig implementing RFC 5321.
0

Configure Feed

Select the types of activity you want to include in your feed.

zig-smtp / src / main.zig
4.4 kB 119 lines
1// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us> 2// SPDX-License-Identifier: MIT 3 4//! Demo CLI for the zsmtp library. 5//! 6//! zsmtp send <host> <port> <from> <to>... send a message read from stdin 7//! zsmtp serve <port> run a debug server on 127.0.0.1 8//! that prints received messages 9 10const std = @import("std"); 11const Io = std.Io; 12const zsmtp = @import("zsmtp"); 13 14pub fn main(init: std.process.Init) !void { 15 const arena = init.arena.allocator(); 16 const io = init.io; 17 const args = try init.minimal.args.toSlice(arena); 18 19 if (args.len >= 6 and std.mem.eql(u8, args[1], "send")) { 20 return send(io, arena, args[2], args[3], args[4], args[5..]); 21 } 22 if (args.len == 3 and std.mem.eql(u8, args[1], "serve")) { 23 return serve(io, arena, args[2]); 24 } 25 std.log.err( 26 \\usage: 27 \\ zsmtp send <host> <port> <from> <to>... (message is read from stdin) 28 \\ zsmtp serve <port> 29 , .{}); 30 std.process.exit(1); 31} 32 33fn send( 34 io: Io, 35 arena: std.mem.Allocator, 36 host_arg: []const u8, 37 port_arg: []const u8, 38 from: []const u8, 39 recipients: []const []const u8, 40) !void { 41 const host = try Io.net.HostName.init(host_arg); 42 const port = try std.fmt.parseInt(u16, port_arg, 10); 43 44 var stdin_buf: [4096]u8 = undefined; 45 var stdin: Io.File.Reader = .init(.stdin(), io, &stdin_buf); 46 const message = try stdin.interface.allocRemaining(arena, .unlimited); 47 48 const stream = try host.connect(io, port, .{ .mode = .stream }); 49 defer stream.close(io); 50 var read_buf: [4096]u8 = undefined; 51 var write_buf: [4096]u8 = undefined; 52 var stream_reader = stream.reader(io, &read_buf); 53 var stream_writer = stream.writer(io, &write_buf); 54 55 var reply_buf: [1024]u8 = undefined; 56 var client: zsmtp.Client = .init(&stream_reader.interface, &stream_writer.interface, &reply_buf); 57 58 _ = try client.greet(); 59 _ = try client.hello("localhost"); 60 client.sendMail(from, recipients, message) catch |err| { 61 if (err == error.UnexpectedReply) { 62 const reply = client.last_reply.?; 63 std.log.err("server rejected: {d} {s}", .{ reply.code, reply.text }); 64 } 65 return err; 66 }; 67 try client.quit(); 68 std.log.info("message sent to {d} recipient(s)", .{recipients.len}); 69} 70 71fn serve(io: Io, gpa: std.mem.Allocator, port_arg: []const u8) !void { 72 const port = try std.fmt.parseInt(u16, port_arg, 10); 73 const address: Io.net.IpAddress = .{ .ip4 = .loopback(port) }; 74 var listener = try address.listen(io, .{}); 75 defer listener.deinit(io); 76 std.log.info("listening on 127.0.0.1:{d}", .{port}); 77 78 var stdout_buf: [4096]u8 = undefined; 79 var stdout: Io.File.Writer = .init(.stdout(), io, &stdout_buf); 80 81 var printer: MessagePrinter = .{ .out = &stdout.interface }; 82 while (true) { 83 const stream = try listener.accept(io); 84 defer stream.close(io); 85 var read_buf: [4096]u8 = undefined; 86 var write_buf: [4096]u8 = undefined; 87 var stream_reader = stream.reader(io, &read_buf); 88 var stream_writer = stream.writer(io, &write_buf); 89 var session: zsmtp.Server = .init( 90 &stream_reader.interface, 91 &stream_writer.interface, 92 .{ .context = &printer, .vtable = &.{ .message = MessagePrinter.onMessage } }, 93 .{ .hostname = "localhost" }, 94 ); 95 session.run(gpa) catch |err| { 96 std.log.warn("session ended with error: {t}", .{err}); 97 }; 98 } 99} 100 101const MessagePrinter = struct { 102 out: *Io.Writer, 103 104 fn onMessage(context: ?*anyopaque, envelope: zsmtp.Server.Envelope, data: []const u8) zsmtp.Server.Decision { 105 const printer: *MessagePrinter = @ptrCast(@alignCast(context.?)); 106 printer.print(envelope, data) catch 107 return .{ .reject = .{ .code = 451, .text = "4.3.0 Local error" } }; 108 return .accept; 109 } 110 111 fn print(printer: *MessagePrinter, envelope: zsmtp.Server.Envelope, data: []const u8) !void { 112 try printer.out.print("--- message from <{s}> to", .{envelope.from}); 113 for (envelope.recipients) |recipient| { 114 try printer.out.print(" <{s}>", .{recipient}); 115 } 116 try printer.out.print(" ({d} bytes)\n{s}---\n", .{ data.len, data }); 117 try printer.out.flush(); 118 } 119};