From 7b22d6a5f9ff352cd546113dccc86fe5f07ac8ff Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Mon, 16 Mar 2026 12:05:12 +1300 Subject: [PATCH 1/5] Fix: Refactor webhook delay & rate limit logic to ignore endpoint response times & prevent hardcoded 1000 message limit when set to 0 (#656) --- server/webhook/webhook.go | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/server/webhook/webhook.go b/server/webhook/webhook.go index 69e1aeb..572a384 100644 --- a/server/webhook/webhook.go +++ b/server/webhook/webhook.go @@ -5,6 +5,7 @@ import ( "bytes" "encoding/json" "net/http" + "sync" "time" "github.com/axllent/mailpit/config" @@ -13,16 +14,18 @@ import ( ) var ( - // RateLimit is the minimum number of seconds between requests + // RateLimit is the minimum number of seconds between requests. + // Additional requests within this period will be ignored until + // the time has elapsed. RateLimit = 1 // Delay is the number of seconds to wait before sending each webhook request - // This can allow for other processing to complete before the webhook is triggered. + // This can allow for other processing to complete before the webhook is triggered. Delay = 0 rl rate.Sometimes - rateLimiterSet bool + once sync.Once ) // Send will post the MessageSummary to a webhook (if configured) @@ -31,23 +34,22 @@ func Send(msg any) { return } - if !rateLimiterSet { + once.Do(func() { if RateLimit > 0 { rl = rate.Sometimes{Interval: time.Duration(RateLimit) * time.Second} } else { - // run 1000 per second - ie: do not limit - rl = rate.Sometimes{First: 1000, Interval: time.Second} + // allow every request + rl = rate.Sometimes{Every: 1} } - rateLimiterSet = true - } + }) - go func() { - // Apply delay if configured - if Delay > 0 { - time.Sleep(time.Duration(Delay) * time.Second) - } + rl.Do(func() { + go func() { + // apply delay if configured + if Delay > 0 { + time.Sleep(time.Duration(Delay) * time.Second) + } - rl.Do(func() { b, err := json.Marshal(msg) if err != nil { logger.Log().Errorf("[webhook] invalid data: %s", err.Error()) @@ -79,6 +81,6 @@ func Send(msg any) { logger.Log().Warnf("[webhook] %s returned a %d status", config.WebhookURL, resp.StatusCode) return } - }) - }() + }() + }) } From 9c2359eee5227cda50206201f6086bd6489e25a2 Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Sun, 22 Mar 2026 17:40:54 +1300 Subject: [PATCH 2/5] Feature: Add filter functionality to message headers tab This implementation is based on, and resolves, #626 --- .../components/message/MessageHeaders.vue | 72 +++++++++++++++++-- 1 file changed, 67 insertions(+), 5 deletions(-) diff --git a/server/ui-src/components/message/MessageHeaders.vue b/server/ui-src/components/message/MessageHeaders.vue index bb5e9d8..c128ee2 100644 --- a/server/ui-src/components/message/MessageHeaders.vue +++ b/server/ui-src/components/message/MessageHeaders.vue @@ -14,27 +14,89 @@ export default { data() { return { headers: false, + filter: "", }; }, + computed: { + filteredHeaders() { + if (this.filter === "") { + return this.headers; + } + const searchWords = this.filter + .toLowerCase() + .split(/\s+/) + .filter((x) => x.length > 0); + + const filtered = {}; + for (const k in this.headers) { + const values = this.headers[k]; + const kLower = k.toLowerCase(); + if (searchWords.every((w) => kLower.includes(w))) { + filtered[k] = values; + } else { + const matchingValues = values.filter((v) => { + const vLower = v.toLowerCase(); + return searchWords.every((w) => vLower.includes(w)); + }); + if (matchingValues.length > 0) { + filtered[k] = matchingValues; + } + } + } + + return filtered; + }, + }, + mounted() { const uri = this.resolve("/api/v1/message/" + this.message.ID + "/headers"); this.get(uri, false, (response) => { this.headers = response.data; }); }, + + methods: { + highlight(text) { + const escaped = text.replace(/&/g, "&").replace(//g, ">"); + if (!this.filter || this.filter.trim() === "") { + return escaped; + } + const words = this.filter + .trim() + .split(/\s+/) + .filter((w) => w.length > 0) + .map((w) => w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")); + const regex = new RegExp(words.join("|"), "gi"); + return escaped.replace(regex, "$&"); + }, + }, }; -