1package smtpclient_test
2
3import (
4 "context"
5 "crypto/tls"
6 "log"
7 "log/slog"
8 "net"
9 "slices"
10 "strings"
11
12 "github.com/mjl-/mox/dns"
13 "github.com/mjl-/mox/sasl"
14 "github.com/mjl-/mox/smtpclient"
15)
16
17func ExampleClient() {
18 // Submit a message to an SMTP server, with authentication. The SMTP server is
19 // responsible for getting the message delivered.
20
21 // Make TCP connection to submission server.
22 conn, err := net.Dial("tcp", "submit.example.org:465")
23 if err != nil {
24 log.Fatalf("dial submission server: %v", err)
25 }
26 defer conn.Close()
27
28 // Initialize the SMTP session, with a EHLO, STARTTLS and authentication.
29 // Verify the server TLS certificate with PKIX/WebPKI.
30 ctx := context.Background()
31 tlsVerifyPKIX := true
32 opts := smtpclient.Opts{
33 Auth: func(mechanisms []string, cs *tls.ConnectionState) (sasl.Client, error) {
34 // If the server is known to support a SCRAM PLUS variant, you should only use
35 // that, detecting and preventing authentication mechanism downgrade attacks
36 // through TLS channel binding.
37 username := "mjl"
38 password := "test1234"
39
40 // Prefer strongest authentication mechanism, allow up to older CRAM-MD5.
41 if cs != nil && slices.Contains(mechanisms, "SCRAM-SHA-256-PLUS") {
42 return sasl.NewClientSCRAMSHA256PLUS(username, password, *cs), nil
43 }
44 if slices.Contains(mechanisms, "SCRAM-SHA-256") {
45 return sasl.NewClientSCRAMSHA256(username, password, true), nil
46 }
47 if cs != nil && slices.Contains(mechanisms, "SCRAM-SHA-1-PLUS") {
48 return sasl.NewClientSCRAMSHA1PLUS(username, password, *cs), nil
49 }
50 if slices.Contains(mechanisms, "SCRAM-SHA-1") {
51 return sasl.NewClientSCRAMSHA1(username, password, true), nil
52 }
53 if slices.Contains(mechanisms, "CRAM-MD5") {
54 return sasl.NewClientCRAMMD5(username, password), nil
55 }
56 // No mutually supported mechanism found, connection will fail.
57 return nil, nil
58 },
59 }
60 localname := dns.Domain{ASCII: "localhost"}
61 remotename := dns.Domain{ASCII: "submit.example.org"}
62 client, err := smtpclient.New(ctx, slog.Default(), conn, smtpclient.TLSImmediate, tlsVerifyPKIX, localname, remotename, opts)
63 if err != nil {
64 log.Fatalf("initialize smtp to submission server: %v", err)
65 }
66 defer client.Close()
67
68 // Send the message to the server, which will add it to its queue.
69 req8bitmime := false // ASCII-only, so 8bitmime not required.
70 reqSMTPUTF8 := false // No UTF-8 headers, so smtputf8 not required.
71 requireTLS := false // Not supported by most servers at the time of writing.
72 msg := "From: <mjl@example.org>\r\nTo: <other@example.org>\r\nSubject: hi\r\n\r\nnice to test you.\r\n"
73 err = client.Deliver(ctx, "mjl@example.org", "other@example.com", int64(len(msg)), strings.NewReader(msg), req8bitmime, reqSMTPUTF8, requireTLS)
74 if err != nil {
75 log.Fatalf("submit message to smtp server: %v", err)
76 }
77
78 // Message has been submitted.
79}
80