1// Package webhook has data types used for webhooks about incoming and outgoing deliveries.
2//
3// See package webapi for details about the webapi and webhooks.
4//
5// Types [Incoming] and [Outgoing] represent the JSON bodies sent in the webhooks.
6// New fields may be added in the future, unrecognized fields should be ignored
7// when parsing for forward compatibility.
8package webhook
9
10import (
11 "strings"
12 "time"
13
14 "github.com/mjl-/mox/message"
15)
16
17// OutgoingEvent is an activity for an outgoing delivery. Either generated by the
18// queue, or through an incoming DSN (delivery status notification) message.
19type OutgoingEvent string
20
21// note: outgoing hook events are in ../queue/hooks.go, ../mox-/config.go, ../queue.go and ../webapi/gendoc.sh. keep in sync.
22
23// todo: in future have more events: for spam complaints, perhaps mdn's.
24
25const (
26 // Message was accepted by a next-hop server. This does not necessarily mean the
27 // message has been delivered in the mailbox of the user.
28 EventDelivered OutgoingEvent = "delivered"
29
30 // Outbound delivery was suppressed because the recipient address is on the
31 // suppression list of the account, or a simplified/base variant of the address is.
32 EventSuppressed OutgoingEvent = "suppressed"
33
34 // A delivery attempt failed but delivery will be retried again later.
35 EventDelayed OutgoingEvent = "delayed"
36
37 // Delivery of the message failed and will not be tried again. Also see the
38 // "Suppressing" field of [Outgoing].
39 EventFailed OutgoingEvent = "failed"
40
41 // Message was relayed into a system that does not generate DSNs. Should only
42 // happen when explicitly requested.
43 EventRelayed OutgoingEvent = "relayed"
44
45 // Message was accepted and is being delivered to multiple recipients (e.g. the
46 // address was an alias/list), which may generate more DSNs.
47 EventExpanded OutgoingEvent = "expanded"
48
49 // Message was removed from the queue, e.g. canceled by admin/user.
50 EventCanceled OutgoingEvent = "canceled"
51
52 // An incoming message was received that was either a DSN with an unknown event
53 // type ("action"), or an incoming non-DSN-message was received for the unique
54 // per-outgoing-message address used for sending.
55 EventUnrecognized OutgoingEvent = "unrecognized"
56)
57
58// Outgoing is the payload sent to webhook URLs for events about outgoing deliveries.
59type Outgoing struct {
60 Version int // Format of hook, currently 0.
61 Event OutgoingEvent // Type of outgoing delivery event.
62 DSN bool // If this event was triggered by a delivery status notification message (DSN).
63 Suppressing bool // If true, this failure caused the address to be added to the suppression list.
64 QueueMsgID int64 // ID of message in queue.
65 FromID string // As used in MAIL FROM, can be empty, for incoming messages.
66 MessageID string // From Message-Id header, as set by submitter or us, with enclosing <>.
67 Subject string // Of original message.
68 WebhookQueued time.Time // When webhook was first queued for delivery.
69 SMTPCode int // Optional, for errors only, e.g. 451, 550. See package smtp for definitions.
70 SMTPEnhancedCode string // Optional, for errors only, e.g. 5.1.1.
71 Error string // Error message while delivering, or from DSN from remote, if any.
72 Extra map[string]string // Extra fields set for message during submit, through webapi call or through X-Mox-Extra-* headers during SMTP submission.
73}
74
75// Incoming is the data sent to a webhook for incoming deliveries over SMTP.
76type Incoming struct {
77 Version int // Format of hook, currently 0.
78
79 // Message "From" header, typically has one address.
80 From []NameAddress
81
82 To []NameAddress
83 CC []NameAddress
84 BCC []NameAddress // Often empty, even if you were a BCC recipient.
85
86 // Optional Reply-To header, typically absent or with one address.
87 ReplyTo []NameAddress
88
89 Subject string
90
91 // Of Message-Id header, typically of the form "<random@hostname>", includes <>.
92 MessageID string
93
94 // Optional, the message-id this message is a reply to. Includes <>.
95 InReplyTo string
96
97 // Optional, zero or more message-ids this message is a reply/forward/related to.
98 // The last entry is the most recent/immediate message this is a reply to. Earlier
99 // entries are the parents in a thread. Values include <>.
100 References []string
101
102 // Time in "Date" message header, can be different from time received.
103 Date *time.Time
104
105 // Contents of text/plain and/or text/html part (if any), with "\n" line-endings,
106 // converted from "\r\n". Values are truncated to 1MB (1024*1024 bytes). Use webapi
107 // MessagePartGet to retrieve the full part data.
108 Text string
109 HTML string
110 // No files, can be large.
111
112 Structure Structure // Parsed form of MIME message.
113 Meta IncomingMeta // Details about message in storage, and SMTP transaction details.
114}
115
116type IncomingMeta struct {
117 MsgID int64 // ID of message in storage, and to use in webapi calls like MessageGet.
118 MailFrom string // Address used during SMTP "MAIL FROM" command.
119 MailFromValidated bool // Whether SMTP MAIL FROM address was SPF-validated.
120 MsgFromValidated bool // Whether address in message "From"-header was DMARC(-like) validated.
121 RcptTo string // SMTP RCPT TO address used in SMTP.
122 DKIMVerifiedDomains []string // Verified domains from DKIM-signature in message. Can be different domain than used in addresses.
123 RemoteIP string // Where the message was delivered from.
124 Received time.Time // When message was received, may be different from the Date header.
125 MailboxName string // Mailbox where message was delivered to, based on configured rules. Defaults to "Inbox".
126
127 // Whether this message was automated and should not receive automated replies.
128 // E.g. out of office or mailing list messages.
129 Automated bool
130}
131
132type NameAddress struct {
133 Name string // Optional, human-readable "display name" of the addressee.
134 Address string // Required, email address.
135}
136
137type Structure struct {
138 ContentType string // Lower case, e.g. text/plain.
139 ContentTypeParams map[string]string // Lower case keys, original case values, e.g. {"charset": "UTF-8"}.
140 ContentID string // Can be empty. Otherwise, should be a value wrapped in <>'s. For use in HTML, referenced as URI `cid:...`.
141 DecodedSize int64 // Size of content after decoding content-transfer-encoding. For text and HTML parts, this can be larger than the data returned since this size includes \r\n line endings.
142 Parts []Structure // Subparts of a multipart message, possibly recursive.
143}
144
145// PartStructure returns a Structure for a parsed message part.
146func PartStructure(p *message.Part) Structure {
147 parts := make([]Structure, len(p.Parts))
148 for i := range p.Parts {
149 parts[i] = PartStructure(&p.Parts[i])
150 }
151 s := Structure{
152 ContentType: strings.ToLower(p.MediaType + "/" + p.MediaSubType),
153 ContentTypeParams: p.ContentTypeParams,
154 ContentID: p.ContentID,
155 DecodedSize: p.DecodedSize,
156 Parts: parts,
157 }
158 // Replace nil map with empty map, for easier to use JSON.
159 if s.ContentTypeParams == nil {
160 s.ContentTypeParams = map[string]string{}
161 }
162 return s
163}
164