ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
smtp.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file smtp.h
6 * @brief Outbound SMTP client (RFC 5321) - send a device email alert.
7 *
8 * A blocking one-shot: connect, greet, optional AUTH LOGIN, then MAIL FROM / RCPT TO /
9 * DATA a plain-text message and QUIT. It rides the shared outbound client transport
10 * (`pc_client`), with implicit TLS (SMTPS, typically port 465) when the config sets
11 * `tls` and PC_ENABLE_TLS is on. Zero heap; every buffer is a compile-time size
12 * (`PC_SMTP_*`). Gated by PC_ENABLE_SMTP.
13 *
14 * The dialogue itself (smtp_run) is written against a send/recv seam, so the whole
15 * protocol exchange - greeting codes, AUTH, dot-stuffing, the terminating `.` - is
16 * unit-tested on the host with a scripted mock server, no lwIP or TLS required.
17 *
18 * "SMS fallback" needs no extra code: most mobile carriers accept an email-to-SMS
19 * gateway address (e.g. `5551234567@txt.example.net`) as the recipient.
20 *
21 * @author Douglas Quigg (dstroy0)
22 * @date 2026
23 */
24
25#ifndef PROTOCORE_SMTP_H
26#define PROTOCORE_SMTP_H
27
28#include <stddef.h>
29#include <stdint.h>
30
31/** @brief Result of an SMTP send. 0 is success; every failure is a distinct negative code. */
32enum class SmtpResult : int32_t
33{
34 SMTP_OK = 0,
35 SMTP_ERR_ARG = -1, ///< a required field (host / from / to) was null or empty
36 SMTP_ERR_CONNECT = -2, ///< could not open the transport (DNS / connect)
37 SMTP_ERR_TLS = -3, ///< the TLS handshake failed (SMTPS)
38 SMTP_ERR_IO = -4, ///< a send/recv failed or the reply timed out
39 SMTP_ERR_PROTOCOL = -5, ///< the server returned an unexpected reply code
40 SMTP_ERR_AUTH = -6, ///< AUTH was rejected (bad user/password)
41 SMTP_ERR_OVERFLOW = -7, ///< a command line or the message exceeded its fixed buffer
42 SMTP_ERR_NO_STARTTLS = -8, ///< STARTTLS was required but the server did not advertise it
43};
44
45/** @brief How the connection is secured. */
46enum class SmtpSecurity : uint8_t
47{
48 SMTP_PLAIN = 0, ///< no TLS at all (port 25) - credentials and body travel in the clear.
49 SMTP_TLS = 1, ///< implicit TLS from the first byte (SMTPS, port 465).
50 SMTP_STARTTLS = 2, ///< connect in the clear, then upgrade in band (submission, port 587).
51};
52
53/**
54 * @brief Transport seam for smtp_run(): the engine sends and receives raw bytes only
55 * through these, so it can run against a real socket or a test mock.
56 *
57 * @return send: number of bytes written (must equal @p len), or <0 on error.
58 * @return recv: number of bytes read (>0), or <=0 on close / error / timeout.
59 */
60typedef int (*SmtpSendFn)(void *ctx, const uint8_t *data, size_t len);
61typedef int (*SmtpRecvFn)(void *ctx, uint8_t *buf, size_t cap);
62
63/**
64 * @brief Upgrade the live connection to TLS in place (RFC 3207), after the server's 220.
65 *
66 * Called once, mid-dialogue. On success every later send/recv on the same ctx must be
67 * encrypted - the engine keeps using the same two function pointers, so the switch belongs to
68 * the transport, not to the caller.
69 * @return true if the handshake completed.
70 */
71typedef bool (*SmtpStartTlsFn)(void *ctx);
72
73/** @brief Server address + credentials for one send. Addresses are bare (no angle brackets). */
75{
76 const char *host; ///< server hostname (also the TLS SNI name)
77 uint16_t port; ///< 25 (plain) / 587 (STARTTLS) / 465 (implicit TLS)
78 SmtpSecurity security; ///< how to secure the connection
79 const char *user; ///< AUTH LOGIN username (null or empty => skip AUTH)
80 const char *pass; ///< AUTH LOGIN password
81 const char *from; ///< envelope sender + From: header address
82 const char *helo; ///< EHLO domain to announce (null => "esp32")
83};
84
85/** @brief One plain-text message. */
87{
88 const char *to; ///< single recipient address (envelope + To: header)
89 const char *subject; ///< Subject: header (null => empty)
90 const char *body; ///< plain-text UTF-8 body; LF or CRLF line ends, dot-stuffed for you
91};
92
93/**
94 * @brief Drive the full SMTP exchange over @p send / @p recv. Pure - no lwIP or TLS -
95 * so it is host-testable with a scripted transport.
96 *
97 * With SmtpSecurity::SMTP_STARTTLS the engine issues STARTTLS after the first EHLO, calls
98 * @p starttls to upgrade the transport, and reissues EHLO (RFC 3207 sec 4.2 requires discarding
99 * the capabilities learned in the clear). If the server does not advertise STARTTLS it returns
100 * SmtpResult::SMTP_ERR_NO_STARTTLS **before** AUTH rather than continuing in the clear - a
101 * stripped STARTTLS must not silently downgrade into sending credentials in plaintext.
102 * @return SmtpResult::SMTP_OK on a delivered message, else an ::SmtpResult error.
103 */
105 SmtpStartTlsFn starttls, void *ctx);
106
107/**
108 * @brief Blocking one-shot send over the real transport (pc_client, plus TLS when
109 * `cfg->tls`). Opens the connection, runs smtp_run(), and closes.
110 * @return SmtpResult::SMTP_OK or an ::SmtpResult error. On non-Arduino (host) builds there is no
111 * lwIP, so this returns SmtpResult::SMTP_ERR_CONNECT; use smtp_run() directly in tests.
112 */
114
115#endif // PROTOCORE_SMTP_H
SmtpResult smtp_send(const SmtpConfig *cfg, const SmtpMessage *msg)
Blocking one-shot send over the real transport (pc_client, plus TLS when cfg->tls)....
SmtpResult smtp_run(const SmtpConfig *cfg, const SmtpMessage *msg, SmtpSendFn send, SmtpRecvFn recv, SmtpStartTlsFn starttls, void *ctx)
Drive the full SMTP exchange over send / recv. Pure - no lwIP or TLS - so it is host-testable with a ...
SmtpSecurity
How the connection is secured.
Definition smtp.h:47
@ SMTP_STARTTLS
connect in the clear, then upgrade in band (submission, port 587).
@ SMTP_TLS
implicit TLS from the first byte (SMTPS, port 465).
@ SMTP_PLAIN
no TLS at all (port 25) - credentials and body travel in the clear.
int(* SmtpSendFn)(void *ctx, const uint8_t *data, size_t len)
Transport seam for smtp_run(): the engine sends and receives raw bytes only through these,...
Definition smtp.h:60
SmtpResult
Result of an SMTP send. 0 is success; every failure is a distinct negative code.
Definition smtp.h:33
@ SMTP_ERR_IO
a send/recv failed or the reply timed out
@ SMTP_ERR_ARG
a required field (host / from / to) was null or empty
@ SMTP_ERR_AUTH
AUTH was rejected (bad user/password)
@ SMTP_ERR_OVERFLOW
a command line or the message exceeded its fixed buffer
@ SMTP_ERR_TLS
the TLS handshake failed (SMTPS)
@ SMTP_ERR_PROTOCOL
the server returned an unexpected reply code
@ SMTP_ERR_NO_STARTTLS
STARTTLS was required but the server did not advertise it.
@ SMTP_ERR_CONNECT
could not open the transport (DNS / connect)
int(* SmtpRecvFn)(void *ctx, uint8_t *buf, size_t cap)
Definition smtp.h:61
bool(* SmtpStartTlsFn)(void *ctx)
Upgrade the live connection to TLS in place (RFC 3207), after the server's 220.
Definition smtp.h:71
Server address + credentials for one send. Addresses are bare (no angle brackets).
Definition smtp.h:75
const char * user
AUTH LOGIN username (null or empty => skip AUTH)
Definition smtp.h:79
SmtpSecurity security
how to secure the connection
Definition smtp.h:78
const char * pass
AUTH LOGIN password.
Definition smtp.h:80
const char * from
envelope sender + From: header address
Definition smtp.h:81
uint16_t port
25 (plain) / 587 (STARTTLS) / 465 (implicit TLS)
Definition smtp.h:77
const char * host
server hostname (also the TLS SNI name)
Definition smtp.h:76
const char * helo
EHLO domain to announce (null => "esp32")
Definition smtp.h:82
One plain-text message.
Definition smtp.h:87
const char * body
plain-text UTF-8 body; LF or CRLF line ends, dot-stuffed for you
Definition smtp.h:90
const char * to
single recipient address (envelope + To: header)
Definition smtp.h:88
const char * subject
Subject: header (null => empty)
Definition smtp.h:89