1
0
mirror of https://github.com/axllent/mailpit.git synced 2025-03-21 21:47:19 +02:00

Merge branch 'feature/reply-to' into develop

This commit is contained in:
Ralph Slooten 2024-02-09 23:09:46 +13:00
commit a856ce0cfa
6 changed files with 89 additions and 27 deletions

View File

@ -146,15 +146,15 @@ func Store(body *[]byte) (string, error) {
from = &mail.Address{Name: env.GetHeader("From")} from = &mail.Address{Name: env.GetHeader("From")}
} }
messageID := strings.Trim(env.Root.Header.Get("Message-ID"), "<>")
obj := DBMailSummary{ obj := DBMailSummary{
From: from, From: from,
To: addressToSlice(env, "To"), To: addressToSlice(env, "To"),
Cc: addressToSlice(env, "Cc"), Cc: addressToSlice(env, "Cc"),
Bcc: addressToSlice(env, "Bcc"), Bcc: addressToSlice(env, "Bcc"),
ReplyTo: addressToSlice(env, "Reply-To"),
} }
messageID := strings.Trim(env.Root.Header.Get("Message-ID"), "<>")
created := time.Now() created := time.Now()
// use message date instead of created date // use message date instead of created date
@ -294,6 +294,10 @@ func List(start, limit int) ([]MessageSummary, error) {
em.Attachments = attachments em.Attachments = attachments
em.Read = read == 1 em.Read = read == 1
em.Snippet = snippet em.Snippet = snippet
// artificially generate ReplyTo if legacy data is missing Reply-To field
if em.ReplyTo == nil {
em.ReplyTo = []*mail.Address{}
}
results = append(results, em) results = append(results, em)
}); err != nil { }); err != nil {

View File

@ -4,6 +4,8 @@ import (
"bytes" "bytes"
"context" "context"
"database/sql" "database/sql"
"encoding/json"
"net/mail"
"os" "os"
"github.com/axllent/mailpit/internal/logger" "github.com/axllent/mailpit/internal/logger"
@ -43,6 +45,7 @@ func ReindexAll() {
ID string ID string
SearchText string SearchText string
Snippet string Snippet string
Metadata string
} }
for _, ids := range chunks { for _, ids := range chunks {
@ -63,6 +66,28 @@ func ReindexAll() {
continue continue
} }
from := &mail.Address{}
fromJSON := addressToSlice(env, "From")
if len(fromJSON) > 0 {
from = fromJSON[0]
} else if env.GetHeader("From") != "" {
from = &mail.Address{Name: env.GetHeader("From")}
}
obj := DBMailSummary{
From: from,
To: addressToSlice(env, "To"),
Cc: addressToSlice(env, "Cc"),
Bcc: addressToSlice(env, "Bcc"),
ReplyTo: addressToSlice(env, "Reply-To"),
}
MetadataJSON, err := json.Marshal(obj)
if err != nil {
logger.Log().Errorf("[message] %s", err.Error())
continue
}
searchText := createSearchText(env) searchText := createSearchText(env)
snippet := tools.CreateSnippet(env.Text, env.HTML) snippet := tools.CreateSnippet(env.Text, env.HTML)
@ -70,6 +95,7 @@ func ReindexAll() {
u.ID = id u.ID = id
u.SearchText = searchText u.SearchText = searchText
u.Snippet = snippet u.Snippet = snippet
u.Metadata = string(MetadataJSON)
updates = append(updates, u) updates = append(updates, u)
} }
@ -86,7 +112,7 @@ func ReindexAll() {
// insert mail summary data // insert mail summary data
for _, u := range updates { for _, u := range updates {
_, err = tx.Exec("UPDATE mailbox SET SearchText = ?, Snippet = ? WHERE ID = ?", u.SearchText, u.Snippet, u.ID) _, err = tx.Exec("UPDATE mailbox SET SearchText = ?, Snippet = ?, Metadata = ? WHERE ID = ?", u.SearchText, u.Snippet, u.Metadata, u.ID)
if err != nil { if err != nil {
logger.Log().Errorf("[db] %s", err.Error()) logger.Log().Errorf("[db] %s", err.Error())
continue continue

View File

@ -42,7 +42,7 @@ func Search(search string, start, limit int) ([]MessageSummary, int, error) {
var ignore string var ignore string
em := MessageSummary{} em := MessageSummary{}
if err := row.Scan(&created, &id, &messageID, &subject, &metadata, &size, &attachments, &read, &snippet, &ignore, &ignore, &ignore, &ignore); err != nil { if err := row.Scan(&created, &id, &messageID, &subject, &metadata, &size, &attachments, &read, &snippet, &ignore, &ignore, &ignore, &ignore, &ignore); err != nil {
logger.Log().Errorf("[db] %s", err.Error()) logger.Log().Errorf("[db] %s", err.Error())
return return
} }
@ -114,7 +114,7 @@ func DeleteSearch(search string) error {
var snippet string var snippet string
var ignore string var ignore string
if err := row.Scan(&created, &id, &messageID, &subject, &metadata, &size, &attachments, &read, &snippet, &ignore, &ignore, &ignore, &ignore); err != nil { if err := row.Scan(&created, &id, &messageID, &subject, &metadata, &size, &attachments, &read, &snippet, &ignore, &ignore, &ignore, &ignore, &ignore); err != nil {
logger.Log().Errorf("[db] %s", err.Error()) logger.Log().Errorf("[db] %s", err.Error())
return return
} }
@ -214,7 +214,8 @@ func searchQueryBuilder(searchString string) *sqlf.Stmt {
IFNULL(json_extract(Metadata, '$.To'), '{}') as ToJSON, IFNULL(json_extract(Metadata, '$.To'), '{}') as ToJSON,
IFNULL(json_extract(Metadata, '$.From'), '{}') as FromJSON, IFNULL(json_extract(Metadata, '$.From'), '{}') as FromJSON,
IFNULL(json_extract(Metadata, '$.Cc'), '{}') as CcJSON, IFNULL(json_extract(Metadata, '$.Cc'), '{}') as CcJSON,
IFNULL(json_extract(Metadata, '$.Bcc'), '{}') as BccJSON IFNULL(json_extract(Metadata, '$.Bcc'), '{}') as BccJSON,
IFNULL(json_extract(Metadata, '$.ReplyTo'), '{}') as ReplyToJSON
`). `).
OrderBy("m.Created DESC") OrderBy("m.Created DESC")
@ -275,6 +276,15 @@ func searchQueryBuilder(searchString string) *sqlf.Stmt {
q.Where("BccJSON LIKE ?", "%"+escPercentChar(w)+"%") q.Where("BccJSON LIKE ?", "%"+escPercentChar(w)+"%")
} }
} }
} else if strings.HasPrefix(lw, "reply-to:") {
w = cleanString(w[9:])
if w != "" {
if exclude {
q.Where("ReplyToJSON NOT LIKE ?", "%"+escPercentChar(w)+"%")
} else {
q.Where("ReplyToJSON LIKE ?", "%"+escPercentChar(w)+"%")
}
}
} else if strings.HasPrefix(lw, "subject:") { } else if strings.HasPrefix(lw, "subject:") {
w = w[8:] w = w[8:]
if w != "" { if w != "" {

View File

@ -17,9 +17,13 @@ func TestSearch(t *testing.T) {
for i := 0; i < testRuns; i++ { for i := 0; i < testRuns; i++ {
msg := enmime.Builder(). msg := enmime.Builder().
From(fmt.Sprintf("From %d", i), fmt.Sprintf("from-%d@example.com", i)). From(fmt.Sprintf("From %d", i), fmt.Sprintf("from-%d@example.com", i)).
CC(fmt.Sprintf("CC %d", i), fmt.Sprintf("cc-%d@example.com", i)).
CC(fmt.Sprintf("CC2 %d", i), fmt.Sprintf("cc2-%d@example.com", i)).
Subject(fmt.Sprintf("Subject line %d end", i)). Subject(fmt.Sprintf("Subject line %d end", i)).
Text([]byte(fmt.Sprintf("This is the email body %d <jdsauk;dwqmdqw;>.", i))). Text([]byte(fmt.Sprintf("This is the email body %d <jdsauk;dwqmdqw;>.", i))).
To(fmt.Sprintf("To %d", i), fmt.Sprintf("to-%d@example.com", i)) To(fmt.Sprintf("To %d", i), fmt.Sprintf("to-%d@example.com", i)).
To(fmt.Sprintf("To2 %d", i), fmt.Sprintf("to2-%d@example.com", i)).
ReplyTo(fmt.Sprintf("Reply To %d", i), fmt.Sprintf("reply-to-%d@example.com", i))
env, err := msg.Build() env, err := msg.Build()
if err != nil { if err != nil {
@ -44,18 +48,26 @@ func TestSearch(t *testing.T) {
for i := 1; i < 51; i++ { for i := 1; i < 51; i++ {
// search a random something that will return a single result // search a random something that will return a single result
searchIdx := rand.Intn(4) + 1 uniqueSearches := []string{
var search string fmt.Sprintf("from-%d@example.com", i),
switch searchIdx { fmt.Sprintf("from:from-%d@example.com", i),
case 1: fmt.Sprintf("to-%d@example.com", i),
search = fmt.Sprintf("from-%d@example.com", i) fmt.Sprintf("to:to-%d@example.com", i),
case 2: fmt.Sprintf("to2-%d@example.com", i),
search = fmt.Sprintf("to-%d@example.com", i) fmt.Sprintf("to:to2-%d@example.com", i),
case 3: fmt.Sprintf("cc-%d@example.com", i),
search = fmt.Sprintf("\"Subject line %d end\"", i) fmt.Sprintf("cc:cc-%d@example.com", i),
default: fmt.Sprintf("cc2-%d@example.com", i),
search = fmt.Sprintf("\"the email body %d jdsauk dwqmdqw\"", i) fmt.Sprintf("cc:cc2-%d@example.com", i),
fmt.Sprintf("reply-to-%d@example.com", i),
fmt.Sprintf("reply-to:\"reply-to-%d@example.com\"", i),
fmt.Sprintf("\"Subject line %d end\"", i),
fmt.Sprintf("subject:\"Subject line %d end\"", i),
fmt.Sprintf("\"the email body %d jdsauk dwqmdqw\"", i),
} }
searchIdx := rand.Intn(len(uniqueSearches))
search := uniqueSearches[searchIdx]
summaries, _, err := Search(search, 0, 100) summaries, _, err := Search(search, 0, 100)
if err != nil { if err != nil {
@ -63,7 +75,7 @@ func TestSearch(t *testing.T) {
t.Fail() t.Fail()
} }
assertEqual(t, len(summaries), 1, "1 search result expected") assertEqual(t, len(summaries), 1, "search result expected")
assertEqual(t, summaries[0].From.Name, fmt.Sprintf("From %d", i), "\"From\" name does not match") assertEqual(t, summaries[0].From.Name, fmt.Sprintf("From %d", i), "\"From\" name does not match")
assertEqual(t, summaries[0].From.Address, fmt.Sprintf("from-%d@example.com", i), "\"From\" address does not match") assertEqual(t, summaries[0].From.Address, fmt.Sprintf("from-%d@example.com", i), "\"From\" address does not match")

View File

@ -82,6 +82,8 @@ type MessageSummary struct {
Cc []*mail.Address Cc []*mail.Address
// Bcc addresses // Bcc addresses
Bcc []*mail.Address Bcc []*mail.Address
// Reply-To address
ReplyTo []*mail.Address
// Email subject // Email subject
Subject string Subject string
// Created time // Created time
@ -105,10 +107,11 @@ type MailboxStats struct {
// DBMailSummary struct for storing mail summary // DBMailSummary struct for storing mail summary
type DBMailSummary struct { type DBMailSummary struct {
From *mail.Address From *mail.Address
To []*mail.Address To []*mail.Address
Cc []*mail.Address Cc []*mail.Address
Bcc []*mail.Address Bcc []*mail.Address
ReplyTo []*mail.Address
} }
// AttachmentSummary returns a summary of the attachment without any binary data // AttachmentSummary returns a summary of the attachment without any binary data

View File

@ -1259,6 +1259,13 @@
"description": "Read status", "description": "Read status",
"type": "boolean" "type": "boolean"
}, },
"ReplyTo": {
"description": "Reply-To address",
"type": "array",
"items": {
"$ref": "#/definitions/Address"
}
},
"Size": { "Size": {
"description": "Message size in bytes (total)", "description": "Message size in bytes (total)",
"type": "integer", "type": "integer",