Skip to content

Commit

Permalink
Added send message API endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
ryan0x44 committed Apr 18, 2024
1 parent 7085690 commit ea6b58b
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 2 deletions.
127 changes: 127 additions & 0 deletions server/apiv1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"net"
"net/http"
"net/mail"
"strconv"
Expand All @@ -23,6 +24,132 @@ import (
"github.com/lithammer/shortuuid/v4"
)

// SendMessage sends a new message
func SendMessage(w http.ResponseWriter, r *http.Request) {
// swagger:route GET /api/v1/messages messages GetMessages
//
// # Send message
//
// Sends a message to the mailbox
//
// Consumes:
// - application/json
//
// Produces:
// - text/plain
//
// Schemes: http, https
//
// Responses:
// 200: OKResponse
// default: ErrorResponse

decoder := json.NewDecoder(r.Body)

data := sendMessageRequestBody{}

if err := decoder.Decode(&data); err != nil {
httpError(w, err.Error())
return
}

from := data.From
if len(from) == 0 {
httpError(w, "No valid sender address found")
return
}
_, err := mail.ParseAddress(from)
if err != nil {
httpError(w, "Invalid sender email address: "+from)
return
}

tos := data.To
if len(tos) == 0 {
httpError(w, "No valid recipient addresses found")
return
}

for _, to := range tos {
address, err := mail.ParseAddress(to)

if err != nil {
httpError(w, "Invalid recipient email address: "+to)
return
}

if config.SMTPRelayConfig.AllowedRecipientsRegexp != nil && !config.SMTPRelayConfig.AllowedRecipientsRegexp.MatchString(address.Address) {
httpError(w, "Mail address does not match allowlist: "+to)
return
}
}

subject := data.Subject
if len(subject) == 0 {
httpError(w, "No valid subject found")
return
}

bodyHTML := data.BodyHTML
if len(bodyHTML) == 0 {
httpError(w, "No valid HTML body found")
return
}

bodyText := data.BodyText
if len(bodyText) == 0 {
httpError(w, "No valid text body found")
return
}

clientIP, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
fmt.Fprintf(w, "Error parsing request RemoteAddr: %s", err)
return
}

message := bytes.NewBuffer(nil)
bodyTemplate := "\r\n" +
"--boundary\r\n" +
"Content-Type: text/plain; charset=\"UTF-8\"\r\n\r\n" +
"%s" +
"\r\n" +
"--boundary\r\n" +
"Content-Type: text/html; charset=\"UTF-8\"\r\n\r\n" +
"%s" +
"\r\n" +
"--boundary--\r\n"
message.WriteString("Content-Type: multipart/alternative; boundary=boundary\r\n")
message.WriteString(fmt.Sprintf("Subject: %s\r\n", subject))
message.WriteString(fmt.Sprintf(bodyTemplate, bodyText, bodyHTML))
msg := message.Bytes()

// update message date
msg, err = tools.UpdateMessageHeader(msg, "Date", time.Now().Format(time.RFC1123Z))
if err != nil {
httpError(w, err.Error())
return
}

// generate unique ID
uid := shortuuid.New() + "@mailpit"
// update Message-Id with unique ID
msg, err = tools.UpdateMessageHeader(msg, "Message-Id", "<"+uid+">")
if err != nil {
httpError(w, err.Error())
return
}

if err := smtpd.MailHandler(&net.IPAddr{IP: net.ParseIP(clientIP)}, from, tos, msg); err != nil {
logger.Log().Errorf("[smtp] error sending message: %s", err.Error())
httpError(w, "SMTP error: "+err.Error())
return
}

w.Header().Add("Content-Type", "text/plain")
_, _ = w.Write([]byte("ok"))
}

// GetMessages returns a paginated list of messages as JSON
func GetMessages(w http.ResponseWriter, r *http.Request) {
// swagger:route GET /api/v1/messages messages GetMessages
Expand Down
34 changes: 34 additions & 0 deletions server/apiv1/swagger.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,40 @@ import "github.com/axllent/mailpit/internal/stats"

// These structs are for the purpose of defining swagger HTTP parameters & responses

// Send request
// swagger:model sendMessageRequestBody
type sendMessageRequestBody struct {
// String of email address to send the message from
//
// required: true
// example: "[email protected]"
From string `json:"from"`

// Array of email addresses to send the message to
//
// required: true
// example: ["[email protected]", "[email protected]"]
To []string `json:"to"`

// String of email subject
//
// required: true
// example: "Hello"
Subject string `json:"subject"`

// String of email HTML body
//
// required: true
// example: "<html><body>Hello</body></html>"
BodyHTML string `json:"bodyHtml"`

// String of email text body
//
// required: true
// example: "Hello"
BodyText string `json:"bodyText"`
}

// Application information
// swagger:response InfoResponse
type infoResponse struct {
Expand Down
1 change: 1 addition & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ func apiRoutes() *mux.Router {
r := mux.NewRouter()

// API V1
r.HandleFunc(config.Webroot+"api/v1/messages", middleWareFunc(apiv1.SendMessage)).Methods("POST")
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")
Expand Down
4 changes: 2 additions & 2 deletions server/smtpd/smtpd.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ var (
DisableReverseDNS bool
)

func mailHandler(origin net.Addr, from string, to []string, data []byte) error {
func MailHandler(origin net.Addr, from string, to []string, data []byte) 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
Expand Down Expand Up @@ -207,7 +207,7 @@ func Listen() error {

logger.Log().Infof("[smtpd] starting on %s (%s)", config.SMTPListen, smtpType)

return listenAndServe(config.SMTPListen, mailHandler, authHandler)
return listenAndServe(config.SMTPListen, MailHandler, authHandler)
}

func listenAndServe(addr string, handler smtpd.Handler, authHandler smtpd.AuthHandler) error {
Expand Down

0 comments on commit ea6b58b

Please sign in to comment.