mirror of
https://github.com/axllent/mailpit.git
synced 2025-08-13 20:04:49 +02:00
Feature: API endpoint for sending (#278)
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
package tools
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Plural returns a singular or plural of a word together with the total
|
||||
func Plural(total int, singular, plural string) string {
|
||||
@@ -9,3 +12,15 @@ func Plural(total int, singular, plural string) string {
|
||||
}
|
||||
return fmt.Sprintf("%d %s", total, plural)
|
||||
}
|
||||
|
||||
// InArray tests if a string is within an array. It is not case sensitive.
|
||||
func InArray(k string, arr []string) bool {
|
||||
k = strings.ToLower(k)
|
||||
for _, v := range arr {
|
||||
if strings.ToLower(v) == k {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
@@ -900,6 +900,19 @@ func httpError(w http.ResponseWriter, msg string) {
|
||||
fmt.Fprint(w, msg)
|
||||
}
|
||||
|
||||
// httpJSONError returns a basic error message (400 response) in JSON format
|
||||
func httpJSONError(w http.ResponseWriter, msg string) {
|
||||
w.Header().Set("Referrer-Policy", "no-referrer")
|
||||
w.Header().Set("Content-Security-Policy", config.ContentSecurityPolicy)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
e := JSONErrorMessage{
|
||||
Error: msg,
|
||||
}
|
||||
bytes, _ := json.Marshal(e)
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
_, _ = w.Write(bytes)
|
||||
}
|
||||
|
||||
// Get the start and limit based on query params. Defaults to 0, 50
|
||||
func getStartLimit(req *http.Request) (start int, limit int) {
|
||||
start = 0
|
||||
|
275
server/apiv1/send.go
Normal file
275
server/apiv1/send.go
Normal file
@@ -0,0 +1,275 @@
|
||||
package apiv1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"strings"
|
||||
|
||||
"github.com/axllent/mailpit/internal/tools"
|
||||
"github.com/axllent/mailpit/server/smtpd"
|
||||
"github.com/jhillyerd/enmime"
|
||||
)
|
||||
|
||||
// swagger:parameters SendMessage
|
||||
type sendMessageParams struct {
|
||||
// in: body
|
||||
Body *SendRequest
|
||||
}
|
||||
|
||||
// SendRequest to send a message via HTTP
|
||||
// swagger:model SendRequest
|
||||
type SendRequest struct {
|
||||
// "From" recipient
|
||||
// required: true
|
||||
From struct {
|
||||
// Optional name
|
||||
// example: John Doe
|
||||
Name string
|
||||
// Email address
|
||||
// example: john@example.com
|
||||
// required: true
|
||||
Email string
|
||||
}
|
||||
|
||||
// "To" recipients
|
||||
To []struct {
|
||||
// Optional name
|
||||
// example: Jane Doe
|
||||
Name string
|
||||
// Email address
|
||||
// example: jane@example.com
|
||||
// required: true
|
||||
Email string
|
||||
}
|
||||
|
||||
// Cc recipients
|
||||
Cc []struct {
|
||||
// Optional name
|
||||
// example: Manager
|
||||
Name string
|
||||
// Email address
|
||||
// example: manager@example.com
|
||||
// required: true
|
||||
Email string
|
||||
}
|
||||
|
||||
// Bcc recipients email addresses only
|
||||
// example: ["jack@example.com"]
|
||||
Bcc []string
|
||||
|
||||
// Optional Reply-To recipients
|
||||
ReplyTo []struct {
|
||||
// Optional name
|
||||
// example: Secretary
|
||||
Name string
|
||||
// Email address
|
||||
// example: secretary@example.com
|
||||
// required: true
|
||||
Email string
|
||||
}
|
||||
|
||||
// Subject
|
||||
// example: Mailpit message via the HTTP API
|
||||
Subject string
|
||||
|
||||
// Message body (text)
|
||||
// example: This is the text body
|
||||
Text string
|
||||
|
||||
// Message body (HTML)
|
||||
// example: <p style="font-family: arial">Mailpit is <b>awesome</b>!</p>
|
||||
HTML string
|
||||
|
||||
// Attachments
|
||||
Attachments []struct {
|
||||
// Base64-encoded string of the file content
|
||||
// required: true
|
||||
// example: VGhpcyBpcyBhIHBsYWluIHRleHQgYXR0YWNobWVudA==
|
||||
Content string
|
||||
// Filename
|
||||
// required: true
|
||||
// example: AttachedFile.txt
|
||||
Filename string
|
||||
}
|
||||
|
||||
// Mailpit tags
|
||||
// example: ["Tag 1","Tag 2"]
|
||||
Tags []string
|
||||
|
||||
// Optional headers in {"key":"value"} format
|
||||
// example: {"X-IP":"1.2.3.4"}
|
||||
Headers map[string]string
|
||||
}
|
||||
|
||||
// SendMessageConfirmation struct
|
||||
type SendMessageConfirmation struct {
|
||||
// Database ID
|
||||
// example: iAfZVVe2UQFNSG5BAjgYwa
|
||||
ID string
|
||||
}
|
||||
|
||||
// JSONErrorMessage struct
|
||||
type JSONErrorMessage struct {
|
||||
// Error message
|
||||
// example: invalid format
|
||||
Error string
|
||||
}
|
||||
|
||||
// SendMessageHandler handles HTTP requests to send a new message
|
||||
func SendMessageHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route POST /api/v1/send message SendMessage
|
||||
//
|
||||
// # Send a message
|
||||
//
|
||||
// Send a message via the HTTP API.
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: sendMessageResponse
|
||||
// default: jsonErrorResponse
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
||||
data := SendRequest{}
|
||||
|
||||
if err := decoder.Decode(&data); err != nil {
|
||||
httpJSONError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
id, err := data.Send(r.RemoteAddr)
|
||||
|
||||
if err != nil {
|
||||
httpJSONError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
bytes, _ := json.Marshal(SendMessageConfirmation{ID: id})
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
_, _ = w.Write(bytes)
|
||||
}
|
||||
|
||||
// Send will validate the message structure and attempt to send to Mailpit.
|
||||
// It returns a sending summary or an error.
|
||||
func (d SendRequest) Send(remoteAddr string) (string, error) {
|
||||
ip, _, err := net.SplitHostPort(remoteAddr)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error parsing request RemoteAddr: %s", err.Error())
|
||||
}
|
||||
|
||||
ipAddr := &net.IPAddr{IP: net.ParseIP(ip)}
|
||||
|
||||
addresses := []string{}
|
||||
|
||||
msg := enmime.Builder().
|
||||
From(d.From.Name, d.From.Email).
|
||||
Subject(d.Subject).
|
||||
Text([]byte(d.Text))
|
||||
|
||||
if d.HTML != "" {
|
||||
msg = msg.HTML([]byte(d.HTML))
|
||||
}
|
||||
|
||||
if len(d.To) > 0 {
|
||||
for _, a := range d.To {
|
||||
if _, err := mail.ParseAddress(a.Email); err == nil {
|
||||
msg = msg.To(a.Name, a.Email)
|
||||
addresses = append(addresses, a.Email)
|
||||
} else {
|
||||
return "", fmt.Errorf("invalid To address: %s", a.Email)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(d.Cc) > 0 {
|
||||
for _, a := range d.Cc {
|
||||
if _, err := mail.ParseAddress(a.Email); err == nil {
|
||||
msg = msg.CC(a.Name, a.Email)
|
||||
addresses = append(addresses, a.Email)
|
||||
} else {
|
||||
return "", fmt.Errorf("invalid Cc address: %s", a.Email)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(d.Bcc) > 0 {
|
||||
for _, e := range d.Bcc {
|
||||
if _, err := mail.ParseAddress(e); err == nil {
|
||||
msg = msg.BCC("", e)
|
||||
addresses = append(addresses, e)
|
||||
} else {
|
||||
return "", fmt.Errorf("invalid Bcc address: %s", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(d.ReplyTo) > 0 {
|
||||
for _, a := range d.ReplyTo {
|
||||
if _, err := mail.ParseAddress(a.Email); err == nil {
|
||||
msg = msg.ReplyTo(a.Name, a.Email)
|
||||
} else {
|
||||
return "", fmt.Errorf("invalid Reply-To address: %s", a.Email)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
restrictedHeaders := []string{"To", "From", "Cc", "Bcc", "Reply-To", "Date", "Subject", "Content-Type", "Mime-Version"}
|
||||
|
||||
if len(d.Tags) > 0 {
|
||||
msg = msg.Header("X-Tags", strings.Join(d.Tags, ", "))
|
||||
restrictedHeaders = append(restrictedHeaders, "X-Tags")
|
||||
}
|
||||
|
||||
if len(d.Headers) > 0 {
|
||||
for k, v := range d.Headers {
|
||||
// check header isn't in "restricted" headers
|
||||
if tools.InArray(k, restrictedHeaders) {
|
||||
return "", fmt.Errorf("cannot overwrite header: \"%s\"", k)
|
||||
}
|
||||
msg = msg.Header(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
if len(d.Attachments) > 0 {
|
||||
for _, a := range d.Attachments {
|
||||
// workaround: split string because JS readAsDataURL() returns the base64 string
|
||||
// with the mime type prefix eg: data:image/png;base64,<base64String>
|
||||
parts := strings.Split(a.Content, ",")
|
||||
content := parts[len(parts)-1]
|
||||
b, err := base64.StdEncoding.DecodeString(content)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error decoding base64 attachment \"%s\": %s", a.Filename, err.Error())
|
||||
}
|
||||
|
||||
mimeType := http.DetectContentType(b)
|
||||
msg = msg.AddAttachment(b, mimeType, a.Filename)
|
||||
}
|
||||
}
|
||||
|
||||
part, err := msg.Build()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error building message: %s", err.Error())
|
||||
}
|
||||
|
||||
var buff bytes.Buffer
|
||||
|
||||
if err := part.Encode(io.Writer(&buff)); err != nil {
|
||||
return "", fmt.Errorf("error building message: %s", err.Error())
|
||||
}
|
||||
|
||||
return smtpd.Store(ipAddr, d.From.Email, addresses, buff.Bytes())
|
||||
}
|
@@ -170,6 +170,7 @@ type htmlResponse string
|
||||
|
||||
// HTTP error response will return with a >= 400 response code
|
||||
// swagger:response ErrorResponse
|
||||
// example: invalid request
|
||||
type errorResponse string
|
||||
|
||||
// Plain text "ok" response
|
||||
@@ -179,3 +180,21 @@ type okResponse string
|
||||
// Plain JSON array response
|
||||
// swagger:response ArrayResponse
|
||||
type arrayResponse []string
|
||||
|
||||
// Confirmation message for HTTP send API
|
||||
// swagger:response sendMessageResponse
|
||||
type sendMessageResponse struct {
|
||||
// Response for sending messages via the HTTP API
|
||||
//
|
||||
// in: body
|
||||
Body SendMessageConfirmation
|
||||
}
|
||||
|
||||
// JSON error response
|
||||
// swagger:response jsonErrorResponse
|
||||
type jsonErrorResponse struct {
|
||||
// A JSON-encoded error response
|
||||
//
|
||||
// in: body
|
||||
Body JSONErrorMessage
|
||||
}
|
||||
|
@@ -127,10 +127,11 @@ func apiRoutes() *mux.Router {
|
||||
r.HandleFunc(config.Webroot+"api/v1/messages", middleWareFunc(apiv1.GetMessages)).Methods("GET")
|
||||
r.HandleFunc(config.Webroot+"api/v1/messages", middleWareFunc(apiv1.SetReadStatus)).Methods("PUT")
|
||||
r.HandleFunc(config.Webroot+"api/v1/messages", middleWareFunc(apiv1.DeleteMessages)).Methods("DELETE")
|
||||
r.HandleFunc(config.Webroot+"api/v1/tags", middleWareFunc(apiv1.GetAllTags)).Methods("GET")
|
||||
r.HandleFunc(config.Webroot+"api/v1/tags", middleWareFunc(apiv1.SetMessageTags)).Methods("PUT")
|
||||
r.HandleFunc(config.Webroot+"api/v1/search", middleWareFunc(apiv1.Search)).Methods("GET")
|
||||
r.HandleFunc(config.Webroot+"api/v1/search", middleWareFunc(apiv1.DeleteSearch)).Methods("DELETE")
|
||||
r.HandleFunc(config.Webroot+"api/v1/send", middleWareFunc(apiv1.SendMessageHandler)).Methods("POST")
|
||||
r.HandleFunc(config.Webroot+"api/v1/tags", middleWareFunc(apiv1.GetAllTags)).Methods("GET")
|
||||
r.HandleFunc(config.Webroot+"api/v1/tags", middleWareFunc(apiv1.SetMessageTags)).Methods("PUT")
|
||||
r.HandleFunc(config.Webroot+"api/v1/message/{id}/part/{partID}", middleWareFunc(apiv1.DownloadAttachment)).Methods("GET")
|
||||
r.HandleFunc(config.Webroot+"api/v1/message/{id}/part/{partID}/thumb", middleWareFunc(apiv1.Thumbnail)).Methods("GET")
|
||||
r.HandleFunc(config.Webroot+"api/v1/message/{id}/headers", middleWareFunc(apiv1.GetHeaders)).Methods("GET")
|
||||
|
@@ -202,6 +202,106 @@ func TestAPIv1Search(t *testing.T) {
|
||||
assertSearchEqual(t, ts.URL+"/api/v1/search", "!tag:\"Test tag 023\"", 99)
|
||||
}
|
||||
|
||||
func TestAPIv1Send(t *testing.T) {
|
||||
setup()
|
||||
defer storage.Close()
|
||||
|
||||
r := apiRoutes()
|
||||
|
||||
ts := httptest.NewServer(r)
|
||||
defer ts.Close()
|
||||
|
||||
jsonData := `{
|
||||
"From": {
|
||||
"Email": "john@example.com",
|
||||
"Name": "John Doe"
|
||||
},
|
||||
"To": [
|
||||
{
|
||||
"Email": "jane@example.com",
|
||||
"Name": "Jane Doe"
|
||||
}
|
||||
],
|
||||
"Cc": [
|
||||
{
|
||||
"Email": "manager1@example.com",
|
||||
"Name": "Manager 1"
|
||||
},
|
||||
{
|
||||
"Email": "manager2@example.com",
|
||||
"Name": "Manager 2"
|
||||
}
|
||||
],
|
||||
"Bcc": ["jack@example.com"],
|
||||
"Headers": {
|
||||
"X-IP": "1.2.3.4"
|
||||
},
|
||||
"Subject": "Mailpit message via the HTTP API",
|
||||
"Text": "This is the text body",
|
||||
"HTML": "<p style=\"font-family: arial\">Mailpit is <b>awesome</b>!</p>",
|
||||
"Attachments": [
|
||||
{
|
||||
"Content": "VGhpcyBpcyBhIHBsYWluIHRleHQgYXR0YWNobWVudA==",
|
||||
"Filename": "Attached File.txt"
|
||||
}
|
||||
],
|
||||
"ReplyTo": [
|
||||
{
|
||||
"Email": "secretary@example.com",
|
||||
"Name": "Secretary"
|
||||
}
|
||||
],
|
||||
"Tags": [
|
||||
"Tag 1",
|
||||
"Tag 2"
|
||||
]
|
||||
}`
|
||||
|
||||
t.Log("Sending message via HTTP API")
|
||||
b, err := clientPost(ts.URL+"/api/v1/send", jsonData)
|
||||
if err != nil {
|
||||
t.Errorf("Expected nil, received %s", err.Error())
|
||||
}
|
||||
|
||||
resp := apiv1.SendMessageConfirmation{}
|
||||
|
||||
if err := json.Unmarshal(b, &resp); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("Fetching response for message %s", resp.ID)
|
||||
msg, err := fetchMessage(ts.URL + "/api/v1/message/" + resp.ID)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
|
||||
t.Logf("Testing response for message %s", resp.ID)
|
||||
assertEqual(t, `Mailpit message via the HTTP API`, msg.Subject, "wrong subject")
|
||||
assertEqual(t, `This is the text body`, msg.Text, "wrong text")
|
||||
assertEqual(t, `<p style="font-family: arial">Mailpit is <b>awesome</b>!</p>`, msg.HTML, "wrong HTML")
|
||||
assertEqual(t, `"John Doe" <john@example.com>`, msg.From.String(), "wrong HTML")
|
||||
assertEqual(t, 1, len(msg.To), "wrong To count")
|
||||
assertEqual(t, `"Jane Doe" <jane@example.com>`, msg.To[0].String(), "wrong To address")
|
||||
assertEqual(t, 2, len(msg.Cc), "wrong Cc count")
|
||||
assertEqual(t, `"Manager 1" <manager1@example.com>`, msg.Cc[0].String(), "wrong Cc address")
|
||||
assertEqual(t, `"Manager 2" <manager2@example.com>`, msg.Cc[1].String(), "wrong Cc address")
|
||||
assertEqual(t, 1, len(msg.Bcc), "wrong Bcc count")
|
||||
assertEqual(t, `<jack@example.com>`, msg.Bcc[0].String(), "wrong Bcc address")
|
||||
assertEqual(t, 1, len(msg.ReplyTo), "wrong Reply-To count")
|
||||
assertEqual(t, `"Secretary" <secretary@example.com>`, msg.ReplyTo[0].String(), "wrong Reply-To address")
|
||||
assertEqual(t, 2, len(msg.Tags), "wrong Tags count")
|
||||
assertEqual(t, `Tag 1,Tag 2`, strings.Join(msg.Tags, ","), "wrong Tags")
|
||||
assertEqual(t, 1, len(msg.Attachments), "wrong Attachment count")
|
||||
assertEqual(t, `Attached File.txt`, msg.Attachments[0].FileName, "wrong Attachment name")
|
||||
|
||||
attachmentBytes, err := clientGet(ts.URL + "/api/v1/message/" + resp.ID + "/part/" + msg.Attachments[0].PartID)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
assertEqual(t, `This is a plain text attachment`, string(attachmentBytes), "wrong Attachment content")
|
||||
}
|
||||
|
||||
func setup() {
|
||||
logger.NoLogging = true
|
||||
config.MaxMessages = 0
|
||||
@@ -288,7 +388,21 @@ func insertEmailData(t *testing.T) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchMessage(url string) (storage.Message, error) {
|
||||
m := storage.Message{}
|
||||
|
||||
data, err := clientGet(url)
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &m); err != nil {
|
||||
return m, err
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func fetchMessages(url string) (apiv1.MessagesSummary, error) {
|
||||
@@ -372,6 +486,31 @@ func clientPut(url, body string) ([]byte, error) {
|
||||
return data, err
|
||||
}
|
||||
|
||||
func clientPost(url, body string) ([]byte, error) {
|
||||
client := new(http.Client)
|
||||
|
||||
b := strings.NewReader(body)
|
||||
req, err := http.NewRequest("POST", url, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode)
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
func assertEqual(t *testing.T, a interface{}, b interface{}, message string) {
|
||||
if a == b {
|
||||
return
|
||||
|
@@ -23,7 +23,15 @@ var (
|
||||
DisableReverseDNS bool
|
||||
)
|
||||
|
||||
// MailHandler handles the incoming message to store in the database
|
||||
func mailHandler(origin net.Addr, from string, to []string, data []byte) error {
|
||||
_, err := Store(origin, from, to, data)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Store will attempt to save a message to the database
|
||||
func Store(origin net.Addr, from string, to []string, data []byte) (string, error) {
|
||||
if !config.SMTPStrictRFCHeaders {
|
||||
// replace all <CR><CR><LF> (\r\r\n) with <CR><LF> (\r\n)
|
||||
// @see https://github.com/axllent/mailpit/issues/87 & https://github.com/axllent/mailpit/issues/153
|
||||
@@ -34,7 +42,7 @@ func mailHandler(origin net.Addr, from string, to []string, data []byte) error {
|
||||
if err != nil {
|
||||
logger.Log().Errorf("[smtpd] error parsing message: %s", err.Error())
|
||||
stats.LogSMTPRejected()
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
// check / set the Return-Path based on SMTP from
|
||||
@@ -70,7 +78,7 @@ func mailHandler(origin net.Addr, from string, to []string, data []byte) error {
|
||||
if storage.MessageIDExists(messageID) {
|
||||
logger.Log().Debugf("[smtpd] duplicate message found, ignoring %s", messageID)
|
||||
stats.LogSMTPIgnored()
|
||||
return nil
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,10 +125,10 @@ func mailHandler(origin net.Addr, from string, to []string, data []byte) error {
|
||||
logger.Log().Debugf("[smtpd] added missing addresses to Bcc header: %s", strings.Join(missingAddresses, ", "))
|
||||
}
|
||||
|
||||
_, err = storage.Store(&data)
|
||||
id, err := storage.Store(&data)
|
||||
if err != nil {
|
||||
logger.Log().Errorf("[db] error storing message: %s", err.Error())
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
stats.LogSMTPAccepted(len(data))
|
||||
@@ -130,7 +138,7 @@ func mailHandler(origin net.Addr, from string, to []string, data []byte) error {
|
||||
subject := msg.Header.Get("Subject")
|
||||
logger.Log().Debugf("[smtpd] received (%s) from:%s subject:%q", cleanIP(origin), from, subject)
|
||||
|
||||
return nil
|
||||
return id, err
|
||||
}
|
||||
|
||||
func authHandler(remoteAddr net.Addr, mechanism string, username []byte, password []byte, _ []byte) (bool, error) {
|
||||
|
@@ -606,6 +606,43 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/send": {
|
||||
"post": {
|
||||
"description": "Send a message via the HTTP API.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"tags": [
|
||||
"message"
|
||||
],
|
||||
"summary": "Send a message",
|
||||
"operationId": "SendMessage",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/SendRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/sendMessageResponse"
|
||||
},
|
||||
"default": {
|
||||
"$ref": "#/responses/jsonErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/tags": {
|
||||
"get": {
|
||||
"description": "Returns a JSON array of all unique message tags.",
|
||||
@@ -1083,6 +1120,18 @@
|
||||
"x-go-name": "Warning",
|
||||
"x-go-package": "github.com/axllent/mailpit/internal/htmlcheck"
|
||||
},
|
||||
"JSONErrorMessage": {
|
||||
"description": "JSONErrorMessage struct",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Error": {
|
||||
"description": "Error message",
|
||||
"type": "string",
|
||||
"example": "invalid format"
|
||||
}
|
||||
},
|
||||
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||
},
|
||||
"Link": {
|
||||
"description": "Link struct",
|
||||
"type": "object",
|
||||
@@ -1375,6 +1424,182 @@
|
||||
},
|
||||
"x-go-package": "github.com/axllent/mailpit/internal/spamassassin"
|
||||
},
|
||||
"SendMessageConfirmation": {
|
||||
"description": "SendMessageConfirmation struct",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ID": {
|
||||
"description": "Database ID",
|
||||
"type": "string",
|
||||
"example": "iAfZVVe2UQFNSG5BAjgYwa"
|
||||
}
|
||||
},
|
||||
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||
},
|
||||
"SendRequest": {
|
||||
"description": "SendRequest to send a message via HTTP",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"From"
|
||||
],
|
||||
"properties": {
|
||||
"Attachments": {
|
||||
"description": "Attachments",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Content",
|
||||
"Filename"
|
||||
],
|
||||
"properties": {
|
||||
"Content": {
|
||||
"description": "Base64-encoded string of the file content",
|
||||
"type": "string",
|
||||
"example": "VGhpcyBpcyBhIHBsYWluIHRleHQgYXR0YWNobWVudA=="
|
||||
},
|
||||
"Filename": {
|
||||
"description": "Filename",
|
||||
"type": "string",
|
||||
"example": "AttachedFile.txt"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Bcc": {
|
||||
"description": "Bcc recipients email addresses only",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"jack@example.com"
|
||||
]
|
||||
},
|
||||
"Cc": {
|
||||
"description": "Cc recipients",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Email"
|
||||
],
|
||||
"properties": {
|
||||
"Email": {
|
||||
"description": "Email address",
|
||||
"type": "string",
|
||||
"example": "manager@example.com"
|
||||
},
|
||||
"Name": {
|
||||
"description": "Optional name",
|
||||
"type": "string",
|
||||
"example": "Manager"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"From": {
|
||||
"description": "\"From\" recipient",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Email"
|
||||
],
|
||||
"properties": {
|
||||
"Email": {
|
||||
"description": "Email address",
|
||||
"type": "string",
|
||||
"example": "john@example.com"
|
||||
},
|
||||
"Name": {
|
||||
"description": "Optional name",
|
||||
"type": "string",
|
||||
"example": "John Doe"
|
||||
}
|
||||
}
|
||||
},
|
||||
"HTML": {
|
||||
"description": "Message body (HTML)",
|
||||
"type": "string",
|
||||
"example": "\u003cp style=\"font-family: arial\"\u003eMailpit is \u003cb\u003eawesome\u003c/b\u003e!\u003c/p\u003e"
|
||||
},
|
||||
"Headers": {
|
||||
"description": "Optional headers in {\"key\":\"value\"} format",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": {
|
||||
"X-IP": "1.2.3.4"
|
||||
}
|
||||
},
|
||||
"ReplyTo": {
|
||||
"description": "Optional Reply-To recipients",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Email"
|
||||
],
|
||||
"properties": {
|
||||
"Email": {
|
||||
"description": "Email address",
|
||||
"type": "string",
|
||||
"example": "secretary@example.com"
|
||||
},
|
||||
"Name": {
|
||||
"description": "Optional name",
|
||||
"type": "string",
|
||||
"example": "Secretary"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Subject": {
|
||||
"description": "Subject",
|
||||
"type": "string",
|
||||
"example": "Mailpit message via the HTTP API"
|
||||
},
|
||||
"Tags": {
|
||||
"description": "Mailpit tags",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"Tag 1",
|
||||
"Tag 2"
|
||||
]
|
||||
},
|
||||
"Text": {
|
||||
"description": "Message body (text)",
|
||||
"type": "string",
|
||||
"example": "This is the text body"
|
||||
},
|
||||
"To": {
|
||||
"description": "\"To\" recipients",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Email"
|
||||
],
|
||||
"properties": {
|
||||
"Email": {
|
||||
"description": "Email address",
|
||||
"type": "string",
|
||||
"example": "jane@example.com"
|
||||
},
|
||||
"Name": {
|
||||
"description": "Optional name",
|
||||
"type": "string",
|
||||
"example": "Jane Doe"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||
},
|
||||
"SpamAssassinResponse": {
|
||||
"description": "Result is a SpamAssassin result",
|
||||
"type": "object",
|
||||
@@ -1582,6 +1807,18 @@
|
||||
"schema": {
|
||||
"$ref": "#/definitions/WebUIConfiguration"
|
||||
}
|
||||
},
|
||||
"jsonErrorResponse": {
|
||||
"description": "JSON error response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/JSONErrorMessage"
|
||||
}
|
||||
},
|
||||
"sendMessageResponse": {
|
||||
"description": "Confirmation message for HTTP send API",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/SendMessageConfirmation"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user