Skip to content

Commit

Permalink
Merge branch 'release/v1.11.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
axllent committed Dec 16, 2023
2 parents a8d5887 + c0be3da commit c0e939f
Show file tree
Hide file tree
Showing 9 changed files with 457 additions and 245 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

Notable changes to Mailpit will be documented in this file.

## [v1.11.1]

### Fix
- Fix regression to support for search query params to all `/latest` endpoints ([#206](https://github.com/axllent/mailpit/issues/206))

### Libs
- Update node modules
- Update Go modules

### Testing
- Add new `ingest` subcommand to import an email file or maildir folder over SMTP

### UI
- Allow multiple tags to be searched using Ctrl-click ([#216](https://github.com/axllent/mailpit/issues/216))


## [v1.11.0]

### API
Expand Down
154 changes: 154 additions & 0 deletions cmd/ingest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package cmd

import (
"bytes"
"io"
"net/mail"
"net/smtp"
"os"
"path/filepath"
"strings"
"time"

"github.com/axllent/mailpit/internal/logger"
sendmail "github.com/axllent/mailpit/sendmail/cmd"
"github.com/spf13/cobra"
"golang.org/x/text/language"
"golang.org/x/text/message"
)

var (
ingestRecent int
)

// ingestCmd represents the ingest command
var ingestCmd = &cobra.Command{
Use: "ingest <file|folder> ...[file|folder]",
Short: "Ingest a file or folder of emails for testing",
Long: `Ingest a file or folder of emails for testing.
This command will scan the folder for emails and deliver them via SMTP to a running
Mailpit server. Each email must be a separate file (eg: Maildir format, not mbox).
The --recent flag will only consider files with a modification date within the last X days.`,
// Hidden: true,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
var count int
var total int
var per100start = time.Now()
p := message.NewPrinter(language.English)

for _, a := range args {
err := filepath.Walk(a,
func(path string, info os.FileInfo, err error) error {
if err != nil {
logger.Log().Error(err)
return nil
}
if !isFile(path) {
return nil
}

info.ModTime()

if ingestRecent > 0 && time.Now().Sub(info.ModTime()) > time.Duration(ingestRecent)*24*time.Hour {
return nil
}

f, err := os.Open(filepath.Clean(path))
if err != nil {
logger.Log().Errorf("%s: %s", path, err.Error())
return nil
}
defer f.Close() // #nosec

body, err := io.ReadAll(f)
if err != nil {
logger.Log().Errorf("%s: %s", path, err.Error())
return nil
}

msg, err := mail.ReadMessage(bytes.NewReader(body))
if err != nil {
logger.Log().Errorf("error parsing message body: %s", err.Error())
return nil
}

recipients := []string{}
// get all recipients in To, Cc and Bcc
if to, err := msg.Header.AddressList("To"); err == nil {
for _, a := range to {
recipients = append(recipients, a.Address)
}
}
if cc, err := msg.Header.AddressList("Cc"); err == nil {
for _, a := range cc {
recipients = append(recipients, a.Address)
}
}
if bcc, err := msg.Header.AddressList("Bcc"); err == nil {
for _, a := range bcc {
recipients = append(recipients, a.Address)
}
}

if sendmail.FromAddr == "" {
if fromAddresses, err := msg.Header.AddressList("From"); err == nil {
sendmail.FromAddr = fromAddresses[0].Address
}
}

if len(recipients) == 0 {
// Bcc
recipients = []string{sendmail.FromAddr}
}

returnPath := strings.Trim(msg.Header.Get("Return-Path"), "<>")
if returnPath == "" {
if fromAddresses, err := msg.Header.AddressList("From"); err == nil {
returnPath = fromAddresses[0].Address
}
}

err = smtp.SendMail(sendmail.SMTPAddr, nil, returnPath, recipients, body)
if err != nil {
logger.Log().Errorf("error sending mail: %s", err.Error())
logger.Log().Errorf(path)
return nil
}

count++
total++
if count%100 == 0 {
formatted := p.Sprintf("%d", total)
logger.Log().Infof("[%s] 100 messages in %s", formatted, time.Since(per100start))

per100start = time.Now()
}

return nil
})
if err != nil {
logger.Log().Error(err)
}
}

},
}

func init() {
rootCmd.AddCommand(ingestCmd)

ingestCmd.Flags().StringVarP(&sendmail.SMTPAddr, "smtp-addr", "S", sendmail.SMTPAddr, "SMTP server address")
ingestCmd.Flags().IntVarP(&ingestRecent, "recent", "r", 0, "Only ingest messages from the last X days (default all)")
}

// IsFile returns if a path is a file
func isFile(path string) bool {
info, err := os.Stat(path)
if os.IsNotExist(err) || !info.Mode().IsRegular() {
return false
}

return true
}
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ require (
golang.org/x/text v0.14.0
golang.org/x/time v0.5.0
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.27.0
modernc.org/sqlite v1.28.0
)

require (
Expand Down Expand Up @@ -60,7 +60,7 @@ require (
lukechampine.com/uint128 v1.3.0 // indirect
modernc.org/cc/v3 v3.41.0 // indirect
modernc.org/ccgo/v3 v3.16.15 // indirect
modernc.org/libc v1.37.4 // indirect
modernc.org/libc v1.37.6 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/opt v0.1.3 // indirect
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -218,16 +218,16 @@ modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0=
modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/libc v1.37.4 h1:LJpf4AxvDxYnHKDBKmgWE/mp4m3GEhCZBDEg7kivOaE=
modernc.org/libc v1.37.4/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw=
modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.27.0 h1:MpKAHoyYB7xqcwnUwkuD+npwEa0fojF0B5QRbN+auJ8=
modernc.org/sqlite v1.27.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
Expand Down
21 changes: 16 additions & 5 deletions internal/storage/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/mail"
"os"
"os/signal"
Expand Down Expand Up @@ -469,15 +470,25 @@ func GetAttachmentPart(id, partID string) (*enmime.Part, error) {
}

// LatestID returns the latest message ID
func LatestID() (string, error) {
//
// If a query argument is set in the request the function will return the
// latest message matching the search
func LatestID(r *http.Request) (string, error) {
messages := []MessageSummary{}
var err error

messages, err = List(0, 1)
if err != nil {
return "", err
search := strings.TrimSpace(r.URL.Query().Get("query"))
if search != "" {
messages, _, err = Search(search, 0, 1)
if err != nil {
return "", err
}
} else {
messages, err = List(0, 1)
if err != nil {
return "", err
}
}

if len(messages) == 0 {
return "", errors.New("Message not found")
}
Expand Down
Loading

0 comments on commit c0e939f

Please sign in to comment.