1// NOTE: DO NOT EDIT, this file is generated by gendoc.sh.
2
3/*
4Package webapi implements a simple HTTP/JSON-based API for interacting with
5email, and webhooks for notifications about incoming and outgoing deliveries,
6including delivery failures.
7
8# Overview
9
10The webapi can be used to compose and send outgoing messages. The HTTP/JSON
11API is often easier to use for developers since it doesn't require separate
12libraries and/or having (detailed) knowledge about the format of email messages
13("Internet Message Format"), or the SMTP protocol and its extensions.
14
15Webhooks can be configured per account, and help with automated processing of
16incoming email, and with handling delivery failures/success. Webhooks are
17often easier to use for developers than monitoring a mailbox with IMAP and
18processing new incoming email and delivery status notification (DSN) messages.
19
20# Webapi
21
22The webapi has a base URL at /webapi/v0/ by default, but configurable, which
23serves an introduction that points to this documentation and lists the API
24methods available.
25
26An HTTP POST to /webapi/v0/<method> calls a method. The form can be either
27"application/x-www-form-urlencoded" or "multipart/form-data". Form field
28"request" must contain the request parameters, encoded as JSON.
29
30HTTP basic authentication is required for calling methods, with an email address
31as user name. Use a login address configured for "unique SMTP MAIL FROM"
32addresses ("FromIDLoginAddresses" in the account configuration), and configure
33an interval to "keep retired messages delivered from the queue". This allows
34incoming DSNs to be matched to the original outgoing messages, and enables
35automatic suppression list management.
36
37HTTP response status 200 OK indicates a successful method call, status 400
38indicates an error. The response body of an error is a JSON object with a
39human-readable "Message" field, and a "Code" field for programmatic handling
40(common codes: "user" or user-induced errors, "server" for server-caused
41errors). Most successful calls return a JSON object, but some return data
42(e.g. a raw message or an attachment of a message). See [Methods] for the
43methods and and [Client] for their documentation. The first element of their
44return values indicate their JSON object type or io.ReadCloser for non-JSON
45data. The request and response types are converted from/to JSON. Optional and
46missing/empty fields/values are converted into Go zero values: zero for
47numbers, empty strings, empty lists and empty objects. New fields may be added
48in response objects in future versions, parsers should ignore unrecognized
49fields.
50
51An HTTP GET to a method URL serves an HTML page showing example
52request/response JSON objects in a form and a button to call the method.
53
54# Webhooks
55
56Webhooks for outgoing delivery events and incoming deliveries are configured
57per account.
58
59A webhook is delivered by an HTTP POST with headers "X-Mox-Webhook-ID" (unique
60ID of webhook) and "X-Mox-Webhook-Attempt" (number of delivery attempts,
61starting at 1), and a JSON body with the webhook data. Failing webhook
62deliveries are retried with backoff, each time doubling the interval between
63attempts, at 1m, 2m, 4m, 7.5m, 15m and unwards, until the last attempt after a
6416h wait period.
65
66See [webhook.Outgoing] for the fields in a webhook for outgoing deliveries, and
67in particular [webhook.OutgoingEvent] for the types of events.
68
69Only the latest event for the delivery of a particular outgoing message will be
70delivered, any webhooks for that message still in the queue (after failure to
71deliver) are retired as superseded when a new event occurs.
72
73Webhooks for incoming deliveries are configured separately from outgoing
74deliveries. Incoming DSNs for previously sent messages do not cause a webhook
75to the webhook URL for incoming messages, only to the webhook URL for outgoing
76delivery events. The incoming webhook JSON payload contains the message
77envelope (parsed To, Cc, Bcc, Subject and more headers), the MIME structure,
78and the contents of the first text and HTML parts. See [webhook.Incoming] for
79the fields in the JSON object. The full message and individual parts, including
80attachments, can be retrieved using the webapi.
81
82# Transactional email
83
84When sending transactional emails, potentially to many recipients, it is
85important to process delivery failure notifications. If messages are rejected,
86or email addresses no longer exist, you should stop sending email to those
87addresses. If you try to keep sending, the receiving mail servers may consider
88that spammy behaviour and blocklist your mail server.
89
90Automatic suppression list management already prevents most repeated sending
91attempts. The webhooks make it easy to receive failure notifications.
92
93To keep spam complaints about your messages to a minimum, include links to
94unsubscribe from future messages without requiring further actions from the
95user, such as logins. Include an unsubscribe link in the footer, and include
96List-* message headers, such as List-Id, List-Unsubscribe and
97List-Unsubscribe-Post.
98
99# Webapi examples
100
101Below are examples for making webapi calls to a locally running "mox
102localserve" with its default credentials.
103
104Send a basic message:
105
106 $ curl --user mox@localhost:moxmoxmox \
107 --data request='{"To": [{"Address": "mox@localhost"}], "Text": "hi ☺"}' \
108 http://localhost:1080/webapi/v0/Send
109 {
110 "MessageID": "<kVTha0Q-a5Zh1MuTh5rUjg@localhost>",
111 "Submissions": [
112 {
113 "Address": "mox@localhost",
114 "QueueMsgID": 10010,
115 "FromID": "ZfV16EATHwKEufrSMo055Q"
116 }
117 ]
118 }
119
120Send a message with files both from form upload and base64 included in JSON:
121
122 $ curl --user mox@localhost:moxmoxmox \
123 --form request='{"To": [{"Address": "mox@localhost"}], "Subject": "hello", "Text": "hi ☺", "HTML": "<img src=\"cid:hi\" />", "AttachedFiles": [{"Name": "img.png", "ContentType": "image/png", "Data": "bWFkZSB5b3UgbG9vayE="}]}' \
124 --form 'inlinefile=@hi.png;headers="Content-ID: <hi>"' \
125 --form attachedfile=@mox.png \
126 http://localhost:1080/webapi/v0/Send
127 {
128 "MessageID": "<eZ3OEEA2odXovovIxHE49g@localhost>",
129 "Submissions": [
130 {
131 "Address": "mox@localhost",
132 "QueueMsgID": 10011,
133 "FromID": "yWiUQ6mvJND8FRPSmc9y5A"
134 }
135 ]
136 }
137
138Get a message in parsed form:
139
140 $ curl --user mox@localhost:moxmoxmox --data request='{"MsgID": 424}' http://localhost:1080/webapi/v0/MessageGet
141 {
142 "Message": {
143 "From": [
144 {
145 "Name": "mox",
146 "Address": "mox@localhost"
147 }
148 ],
149 "To": [
150 {
151 "Name": "",
152 "Address": "mox@localhost"
153 }
154 ],
155 "CC": [],
156 "BCC": [],
157 "ReplyTo": [],
158 "MessageID": "<84vCeme_yZXyDzjWDeYBpg@localhost>",
159 "References": [],
160 "Date": "2024-04-04T14:29:42+02:00",
161 "Subject": "hello",
162 "Text": "hi \u263a\n",
163 "HTML": ""
164 },
165 "Structure": {
166 "ContentType": "multipart/mixed",
167 "ContentTypeParams": {
168 "boundary": "0ee72dc30dbab2ca6f7a363844a10a9f6111fc6dd31b8ff0b261478c2c48"
169 },
170 "ContentID": "",
171 "DecodedSize": 0,
172 "Parts": [
173 {
174 "ContentType": "multipart/related",
175 "ContentTypeParams": {
176 "boundary": "b5ed0977ee2b628040f394c3f374012458379a4f3fcda5036371d761c81d"
177 },
178 "ContentID": "",
179 "DecodedSize": 0,
180 "Parts": [
181 {
182 "ContentType": "multipart/alternative",
183 "ContentTypeParams": {
184 "boundary": "3759771adede7bd191ef37f2aa0e49ff67369f4000c320f198a875e96487"
185 },
186 "ContentID": "",
187 "DecodedSize": 0,
188 "Parts": [
189 {
190 "ContentType": "text/plain",
191 "ContentTypeParams": {
192 "charset": "utf-8"
193 },
194 "ContentID": "",
195 "DecodedSize": 8,
196 "Parts": []
197 },
198 {
199 "ContentType": "text/html",
200 "ContentTypeParams": {
201 "charset": "us-ascii"
202 },
203 "ContentID": "",
204 "DecodedSize": 22,
205 "Parts": []
206 }
207 ]
208 },
209 {
210 "ContentType": "image/png",
211 "ContentTypeParams": {},
212 "ContentID": "<hi>",
213 "DecodedSize": 19375,
214 "Parts": []
215 }
216 ]
217 },
218 {
219 "ContentType": "image/png",
220 "ContentTypeParams": {},
221 "ContentID": "",
222 "DecodedSize": 14,
223 "Parts": []
224 },
225 {
226 "ContentType": "image/png",
227 "ContentTypeParams": {},
228 "ContentID": "",
229 "DecodedSize": 7766,
230 "Parts": []
231 }
232 ]
233 },
234 "Meta": {
235 "Size": 38946,
236 "DSN": false,
237 "Flags": [
238 "$notjunk",
239 "\seen"
240 ],
241 "MailFrom": "",
242 "MailFromValidated": false,
243 "MsgFrom": "",
244 "MsgFromValidated": false,
245 "DKIMVerifiedDomains": [],
246 "RemoteIP": "",
247 "MailboxName": "Inbox"
248 }
249 }
250
251Errors (with a 400 bad request HTTP status response) include a human-readable
252message and a code for programmatic use:
253
254 $ curl --user mox@localhost:moxmoxmox --data request='{"MsgID": 999}' http://localhost:1080/webapi/v0/MessageGet
255 {
256 "Code": "notFound",
257 "Message": "message not found"
258 }
259
260Get a raw, unparsed message, as bytes:
261
262 $ curl --user mox@localhost:moxmoxmox --data request='{"MsgID": 123}' http://localhost:1080/webapi/v0/MessageRawGet
263 [message as bytes in raw form]
264
265Mark a message as read and set flag "custom":
266
267 $ curl --user mox@localhost:moxmoxmox --data request='{"MsgID": 424, "Flags": ["\\Seen", "custom"]}' http://localhost:1080/webapi/v0/MessageFlagsAdd
268 {}
269
270# Webhook examples
271
272A webhook is delivered by an HTTP POST, wich headers X-Mox-Webhook-ID and
273X-Mox-Webhook-Attempt and a JSON body with the data. To simulate a webhook call
274for incoming messages, use:
275
276 curl -H 'X-Mox-Webhook-ID: 123' -H 'X-Mox-Webhook-Attempt: 1' --json '{...}' http://localhost/yourapp
277
278Example webhook HTTP POST JSON body for successful outgoing delivery:
279
280 {
281 "Version": 0,
282 "Event": "delivered",
283 "DSN": false,
284 "Suppressing": false,
285 "QueueMsgID": 101,
286 "FromID": "MDEyMzQ1Njc4OWFiY2RlZg",
287 "MessageID": "<QnxzgulZK51utga6agH_rg@mox.example>",
288 "Subject": "subject of original message",
289 "WebhookQueued": "2024-03-27T00:00:00Z",
290 "SMTPCode": 250,
291 "SMTPEnhancedCode": "",
292 "Error": "",
293 "Extra": {}
294 }
295
296Example webhook HTTP POST JSON body for failed delivery based on incoming DSN
297message, with custom extra data fields (from original submission), and adding address to the suppression list:
298
299 {
300 "Version": 0,
301 "Event": "failed",
302 "DSN": true,
303 "Suppressing": true,
304 "QueueMsgID": 102,
305 "FromID": "MDEyMzQ1Njc4OWFiY2RlZg",
306 "MessageID": "<QnxzgulZK51utga6agH_rg@mox.example>",
307 "Subject": "subject of original message",
308 "WebhookQueued": "2024-03-27T00:00:00Z",
309 "SMTPCode": 554,
310 "SMTPEnhancedCode": "5.4.0",
311 "Error": "timeout connecting to host",
312 "Extra": {
313 "userid": "456"
314 }
315 }
316
317Example JSON body for webhooks for incoming delivery of basic message:
318
319 {
320 "Version": 0,
321 "From": [
322 {
323 "Name": "",
324 "Address": "mox@localhost"
325 }
326 ],
327 "To": [
328 {
329 "Name": "",
330 "Address": "mjl@localhost"
331 }
332 ],
333 "CC": [],
334 "BCC": [],
335 "ReplyTo": [],
336 "Subject": "hi",
337 "MessageID": "<QnxzgulZK51utga6agH_rg@mox.example>",
338 "InReplyTo": "",
339 "References": [],
340 "Date": "2024-03-27T00:00:00Z",
341 "Text": "hello world ☺\n",
342 "HTML": "",
343 "Structure": {
344 "ContentType": "text/plain",
345 "ContentTypeParams": {
346 "charset": "utf-8"
347 },
348 "ContentID": "",
349 "DecodedSize": 17,
350 "Parts": []
351 },
352 "Meta": {
353 "MsgID": 201,
354 "MailFrom": "mox@localhost",
355 "MailFromValidated": false,
356 "MsgFromValidated": true,
357 "RcptTo": "mjl@localhost",
358 "DKIMVerifiedDomains": [
359 "localhost"
360 ],
361 "RemoteIP": "127.0.0.1",
362 "Received": "2024-03-27T00:00:03Z",
363 "MailboxName": "Inbox",
364 "Automated": false
365 }
366 }
367*/
368package webapi
369
370// NOTE: DO NOT EDIT, this file is generated by gendoc.sh.
371