diff --git a/webapi/client.go b/webapi/client.go index b769df6..f094544 100644 --- a/webapi/client.go +++ b/webapi/client.go @@ -114,10 +114,10 @@ func badResponse(hresp *http.Response) error { // Configure webhooks to receive updates about deliveries. // // If the request is a multipart/form-data, uploaded files with the form keys -// "inlinefile" and/or "attachedfile" will be added to the message. If the uploaded -// file has content-type and/or content-id headers, they will be included. If no -// content-type is present in the request, and it can be detected, it is included -// automatically. +// "alternativefile", "inlinefile" and/or "attachedfile" will be added to the +// message. If the uploaded file has content-type and/or content-id headers, they +// will be included. If no content-type is present in the request, and it can be +// detected, it is included automatically. // // Example call with a text and html message, with an inline and an attached image: // diff --git a/webapi/webapi.go b/webapi/webapi.go index 1e5bda6..823cc67 100644 --- a/webapi/webapi.go +++ b/webapi/webapi.go @@ -115,6 +115,11 @@ type SendRequest struct { // Unless a User-Agent or X-Mailer header is present, a User-Agent is added. Headers [][2]string + // Alternative files are added as (full) alternative representation of the text + // and/or html parts. Alternative files cause a part with content-type + // "multipart/alternative" to be added to the message. Optional. + AlternativeFiles []File + // Inline files are added to the message and should be displayed by mail clients as // part of the message contents. Inline files cause a part with content-type // "multipart/related" to be added to the message. Optional. diff --git a/webapisrv/server.go b/webapisrv/server.go index 7f07553..a1daebd 100644 --- a/webapisrv/server.go +++ b/webapisrv/server.go @@ -759,8 +759,9 @@ func (s server) Send(ctx context.Context, req webapi.SendRequest) (resp webapi.S xc.Header("User-Agent", "mox/"+moxvar.Version) } - // Whether we have additional separately inline/attached file(s). + // Whether we have additional separately alternative/inline/attached file(s). mpf := reqInfo.Request.MultipartForm + formAlternative := mpf != nil && len(mpf.File["alternativefile"]) > 0 formInline := mpf != nil && len(mpf.File["inlinefile"]) > 0 formAttachment := mpf != nil && len(mpf.File["attachedfile"]) > 0 @@ -770,6 +771,7 @@ func (s server) Send(ctx context.Context, req webapi.SendRequest) (resp webapi.S // - multipart/alternative (in case we have both text and html bodies) // - text/plain (optional) // - text/html (optional) + // - alternative file, ... // - inline file, ... // - attached file, ... @@ -811,7 +813,7 @@ func (s server) Send(ctx context.Context, req webapi.SendRequest) (resp webapi.S related = xcreateMultipart("related") cur = related } - if m.Text != "" && m.HTML != "" { + if m.Text != "" && m.HTML != "" || len(req.AlternativeFiles) > 0 || formAlternative { alternative = xcreateMultipart("alternative") cur = alternative } @@ -827,10 +829,6 @@ func (s server) Send(ctx context.Context, req webapi.SendRequest) (resp webapi.S _, err := tp.Write([]byte(htmlBody)) xcheckf(err, "write html part") } - if alternative != nil { - alternative.Close() - alternative = nil - } xaddFileBase64 := func(ct string, inline bool, filename string, cid string, base64Data string) { h := textproto.MIMEHeader{} @@ -923,6 +921,18 @@ func (s server) Send(ctx context.Context, req webapi.SendRequest) (resp webapi.S xcheckf(err, "flushing uploaded file") } + cur = alternative + xaddJSONFiles(req.AlternativeFiles, true) + if mpf != nil { + for _, fh := range mpf.File["alternativefile"] { + xaddFile(fh, true) + } + } + if alternative != nil { + alternative.Close() + alternative = nil + } + cur = related xaddJSONFiles(req.InlineFiles, true) if mpf != nil { diff --git a/webapisrv/server_test.go b/webapisrv/server_test.go index faaff75..3271098 100644 --- a/webapisrv/server_test.go +++ b/webapisrv/server_test.go @@ -181,6 +181,13 @@ func TestServer(t *testing.T) { }, Extra: map[string]string{"a": "123"}, Headers: [][2]string{{"x-custom", "header"}}, + AlternativeFiles: []webapi.File{ + { + Name: "x.ics", + ContentType: "text/calendar", + Data: base64.StdEncoding.EncodeToString([]byte("ics data...")), + }, + }, InlineFiles: []webapi.File{ { Name: "x.png", @@ -228,8 +235,15 @@ func TestServer(t *testing.T) { sendReqBuf, err := json.Marshal(fdSendReq) tcheckf(t, err, "send request") mp.WriteField("request", string(sendReqBuf)) + + // One alternative file. + pw, err := mp.CreateFormFile("alternativefile", "test.ics") + tcheckf(t, err, "create alternative ics file") + _, err = fmt.Fprint(pw, "ICS...") + tcheckf(t, err, "write ics") + // Two inline PDFs. - pw, err := mp.CreateFormFile("inlinefile", "test.pdf") + pw, err = mp.CreateFormFile("inlinefile", "test.pdf") tcheckf(t, err, "create inline pdf file") _, err = fmt.Fprint(pw, "%PDF-") tcheckf(t, err, "write pdf")