diff --git a/.chglog/CHANGELOG.tpl.md b/.chglog/CHANGELOG.tpl.md
new file mode 100755
index 0000000..5abaff8
--- /dev/null
+++ b/.chglog/CHANGELOG.tpl.md
@@ -0,0 +1,48 @@
+# Changelog
+
+Notable changes to Mailpit will be documented in this file.
+
+
+{{ if .Versions -}}
+{{ if .Unreleased.CommitGroups -}}
+## [Unreleased]
+
+{{ if .Unreleased.CommitGroups -}}
+{{ range .Unreleased.CommitGroups -}}
+### {{ .Title }}
+{{ range .Commits -}}
+- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
+{{ end }}
+{{ end }}
+{{ end -}}
+{{ end -}}
+{{ end -}}
+
+{{ range .Versions }}
+{{- if .CommitGroups -}}
+## {{ .Tag.Name }}
+
+{{ range .CommitGroups -}}
+### {{ .Title }}
+{{ range .Commits -}}
+- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
+{{ end }}
+{{ end }}
+{{ end -}}
+
+{{- if .MergeCommits -}}
+### Pull Requests
+{{ range .MergeCommits -}}
+- {{ .Header }}
+{{ end }}
+{{ end -}}
+
+{{- if .NoteGroups -}}
+{{ range .NoteGroups -}}
+### {{ .Title }}
+{{ range .Notes }}
+{{ .Body }}
+{{ end }}
+{{ end -}}
+{{ end -}}
+{{ end -}}
diff --git a/.chglog/RELEASE.tpl.md b/.chglog/RELEASE.tpl.md
new file mode 100755
index 0000000..a8ce8ae
--- /dev/null
+++ b/.chglog/RELEASE.tpl.md
@@ -0,0 +1,12 @@
+{{ if .Versions -}}
+{{ range .Versions }}
+{{- if .CommitGroups -}}
+{{ range .CommitGroups -}}
+### {{ .Title }}
+{{ range .Commits -}}
+- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
+{{ end }}
+{{ end -}}
+{{ end -}}
+{{ end -}}
+{{ end -}}
diff --git a/.chglog/config.yml b/.chglog/config.yml
new file mode 100755
index 0000000..65597c4
--- /dev/null
+++ b/.chglog/config.yml
@@ -0,0 +1,28 @@
+style: github
+template: CHANGELOG.tpl.md
+info:
+ title: CHANGELOG
+ repository_url: https://github.com/axllent/mailpit
+options:
+ commits:
+ # filters:
+ # Type:
+ # - feat
+ # - fix
+ # - perf
+ # - refactor
+ commit_groups:
+ title_maps:
+ feature: Feature
+ fix: Fix
+ # perf: Performance Improvements
+ # refactor: Code Refactoring
+ header:
+ pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$"
+ pattern_maps:
+ - Type
+ - Scope
+ - Subject
+ notes:
+ keywords:
+ - BREAKING CHANGE
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 839d03f..5dfbd6a 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -16,6 +16,14 @@ jobs:
with:
go-version: ${{ matrix.go-version }}
- uses: actions/checkout@v3
+ - uses: actions/cache@v3
+ with:
+ path: |
+ ~/.cache/go-build
+ ~/go
+ key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
+ restore-keys: |
+ ${{ runner.os }}-go-
- run: go test ./storage -v
- run: go test ./storage -bench=.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dc8a8ec..c816416 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,15 +1,34 @@
# Changelog
-## [0.0.3]
-
-- Bugfix: Update to clover-v2.0.0-alpha.2 to fix sorting
+Notable changes to Mailpit will be documented in this file.
-## [0.0.2]
+## 0.0.4
-- Unread message statistics & updates
+### Bugfix
+- Update to clover-v2.0.0-alpha.2 to fix sorting
+
+### Tests
+- Add search tests
+
+### UI
+- Add date to console log
+- Add space in To fields
+- Cater for messages without From email address
+- Minor UI & logging changes
+- Add space in To fields
+- cater for messages without From email address
-## [0.0.1-beta]
+## 0.0.3
+
+### Bugfix
+- Update to clover-v2.0.0-alpha.2 to fix sorting
+
+
+## 0.0.2
+
+### Feature
+- Unread statistics
+
-- First release
diff --git a/logger/logger.go b/logger/logger.go
index 1133213..aa0e30f 100644
--- a/logger/logger.go
+++ b/logger/logger.go
@@ -28,7 +28,7 @@ func Log() *logrus.Logger {
log.Out = os.Stdout
log.SetFormatter(&logrus.TextFormatter{
FullTimestamp: true,
- TimestampFormat: "15:04:05",
+ TimestampFormat: "2006/01/02 15:04:05",
ForceColors: true,
})
}
diff --git a/server/ui-src/App.vue b/server/ui-src/App.vue
index 7de51a1..139076e 100644
--- a/server/ui-src/App.vue
+++ b/server/ui-src/App.vue
@@ -408,7 +408,7 @@ export default {
- This will permanently delete all messages.
+ This will permanently delete {{ formatNumber(total) }} messages.
-
diff --git a/server/ui-src/templates/Message.vue b/server/ui-src/templates/Message.vue
index bee7fd1..77b2f3a 100644
--- a/server/ui-src/templates/Message.vue
+++ b/server/ui-src/templates/Message.vue
@@ -80,7 +80,8 @@ export default {
From |
- {{ message.From.Name + " <" + message.From.Address +">" }}
+ {{ message.From.Name + " " }}
+ <{{ message.From.Address }}>
[ Unknown ]
diff --git a/storage/database.go b/storage/database.go
index 78845b2..edc299a 100644
--- a/storage/database.go
+++ b/storage/database.go
@@ -185,6 +185,8 @@ func Store(mailbox string, b []byte) (string, error) {
fromData := addressToSlice(env, "From")
if len(fromData) > 0 {
from = fromData[0]
+ } else if env.GetHeader("From") != "" {
+ from = &mail.Address{Name: env.GetHeader("From")}
}
obj := CloverStore{
@@ -224,7 +226,7 @@ func Store(mailbox string, b []byte) (string, error) {
count++
if count%100 == 0 {
- logger.Log().Infof("%d messages added (%s per 100)", count, time.Since(per100start))
+ logger.Log().Infof("100 messages added in %s", time.Since(per100start))
per100start = time.Now()
}
@@ -311,7 +313,7 @@ func List(mailbox string, start, limit int) ([]data.Summary, error) {
// Search returns a summary of items mathing a search. It searched the SearchText field.
func Search(mailbox, search string, start, limit int) ([]data.Summary, error) {
- sq := fmt.Sprintf("(?i)%s", regexp.QuoteMeta(search))
+ sq := fmt.Sprintf("(?i)%s", cleanString(regexp.QuoteMeta(search)))
q, err := db.FindAll(clover.NewQuery(mailbox).
Skip(start).
Limit(limit).
@@ -393,6 +395,8 @@ func GetMessage(mailbox, id string) (*data.Message, error) {
fromData := addressToSlice(env, "From")
if len(fromData) > 0 {
from = fromData[0]
+ } else if env.GetHeader("From") != "" {
+ from = &mail.Address{Name: env.GetHeader("From")}
}
date, err := env.Date()
diff --git a/storage/database_test.go b/storage/database_test.go
index 9d473bc..c2152ff 100644
--- a/storage/database_test.go
+++ b/storage/database_test.go
@@ -1,12 +1,15 @@
package storage
import (
+ "bytes"
"fmt"
"io/ioutil"
+ "math/rand"
"testing"
"time"
"github.com/axllent/mailpit/config"
+ "github.com/jhillyerd/enmime"
)
var (
@@ -123,6 +126,78 @@ func TestRetrieveMimeEmail(t *testing.T) {
assertEqual(t, len(attachmentData.Content), msg.Attachments[0].Size, "attachment size does not match")
inlineData, err := GetAttachmentPart(DefaultMailbox, id, msg.Inline[0].PartID)
assertEqual(t, len(inlineData.Content), msg.Inline[0].Size, "inline attachment size does not match")
+
+ db.Close()
+}
+
+func TestSearch(t *testing.T) {
+ setup()
+
+ for i := 0; i < 1000; i++ {
+ msg := enmime.Builder().
+ From(fmt.Sprintf("From %d", i), fmt.Sprintf("from-%d@example.com", i)).
+ Subject(fmt.Sprintf("Subject line %d end", i)).
+ Text([]byte(fmt.Sprintf("This is the email body %d .", i))).
+ To(fmt.Sprintf("To %d", i), fmt.Sprintf("to-%d@example.com", i))
+
+ env, err := msg.Build()
+ if err != nil {
+ t.Log("error ", err)
+ t.Fail()
+ }
+
+ buf := new(bytes.Buffer)
+
+ if err := env.Encode(buf); err != nil {
+ t.Log("error ", err)
+ t.Fail()
+ }
+
+ if _, err := Store(DefaultMailbox, buf.Bytes()); err != nil {
+ t.Log("error ", err)
+ t.Fail()
+ }
+ }
+
+ for i := 1; i < 101; i++ {
+ // search a random something that will return a single result
+ searchIndx := rand.Intn(4) + 1
+ var search string
+ switch searchIndx {
+ case 1:
+ search = fmt.Sprintf("from-%d@example.com", i)
+ case 2:
+ search = fmt.Sprintf("to-%d@example.com", i)
+ case 3:
+ search = fmt.Sprintf("Subject line %d end", i)
+ default:
+ search = fmt.Sprintf("the email body %d jdsauk dwqmdqw", i)
+ }
+
+ summaries, err := Search(DefaultMailbox, search, 0, 200)
+ if err != nil {
+ t.Log("error ", err)
+ t.Fail()
+ }
+
+ assertEqual(t, len(summaries), 1, "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.Address, fmt.Sprintf("from-%d@example.com", i), "\"From\" address does not match")
+ assertEqual(t, summaries[0].To[0].Name, fmt.Sprintf("To %d", i), "\"To\" name does not match")
+ assertEqual(t, summaries[0].To[0].Address, fmt.Sprintf("to-%d@example.com", i), "\"To\" address does not match")
+ assertEqual(t, summaries[0].Subject, fmt.Sprintf("Subject line %d end", i), "\"Subject\" does not match")
+ }
+
+ // search something that will return 200 rsults
+ summaries, err := Search(DefaultMailbox, "This is the email body", 0, 200)
+ if err != nil {
+ t.Log("error ", err)
+ t.Fail()
+ }
+ assertEqual(t, len(summaries), 200, "200 search results expected")
+
+ db.Close()
}
func BenchmarkImportText(b *testing.B) {
diff --git a/storage/utils.go b/storage/utils.go
index 8cfca72..f2bbb18 100644
--- a/storage/utils.go
+++ b/storage/utils.go
@@ -42,17 +42,20 @@ func createSearchText(env *enmime.Envelope) string {
b.WriteString(a.FileName + " ")
}
- d := b.String()
-
- // remove/replace new lines
- re := regexp.MustCompile(`(\r?\n|\t|>|<|"|:|\,|;)`)
- d = re.ReplaceAllString(d, " ")
- // remove duplicate whitespace and trim
- d = strings.ToLower(strings.Join(strings.Fields(strings.TrimSpace(d)), " "))
+ d := cleanString(b.String())
return d
}
+// cleanString removed unwanted characters from stored search text and search queries
+func cleanString(str string) string {
+ // remove/replace new lines
+ re := regexp.MustCompile(`(\r?\n|\t|>|<|"|:|\,|;)`)
+ str = re.ReplaceAllString(str, " ")
+ // remove duplicate whitespace and trim
+ return strings.ToLower(strings.Join(strings.Fields(strings.TrimSpace(str)), " "))
+}
+
// Auto-prune runs every 5 minutes to automatically delete oldest messages
// if total is greater than the threshold
func pruneCron() {
|