Files
meshcore-analyzer/cmd/server/mail_webhook.go
T
CoderNemesisandCoderNemesis d9342bbebd (feat): UserManagement, Add Support for Postal Email Delivery Provider (#2154)
This PR extends email support to add another email delivery provider
called Postal for user management. It is a, open source, self hosted
email delivery and reputation management service.

It adds 2 new additional configuration settings to the
userManagement.mail object which are "postalBaseUrl" and "postalApiKey".
It also adds "postal" as a supported value for the "provider" setting.
All existing functionality including email delivery notification is
supported by Postal.

Included test cases to support the new functionality and all pipelines
pass.

---------

Co-authored-by: CoderNemesis <rfedor@comchan.net>
2026-10-11 11:28:20 +02:00

58 lines
1.9 KiB
Go

package main
import (
"io"
"log"
"net/http"
"github.com/meshcore-analyzer/mailer"
)
// handleBrevoWebhook ingests Brevo transactional events. Brevo is configured
// with auth {"type":"bearer","token":<webhookSecret>}, so it sends
// "Authorization: Bearer <secret>".
func (s *Server) handleBrevoWebhook(w http.ResponseWriter, r *http.Request) {
s.handleMailWebhook(w, r, "brevo", func(r *http.Request) bool {
return constantTimeEqual(r.Header.Get("Authorization"), "Bearer "+s.auth.set.webhookSecret)
}, mailer.ParseBrevoWebhook)
}
// handlePostalWebhook ingests Postal message events. Postal cannot add a
// header of its own, so the webhook URL carries the secret as Basic auth
// (https://postal:<secret>@host/api/mail/postal/webhook); the user name is
// not checked.
func (s *Server) handlePostalWebhook(w http.ResponseWriter, r *http.Request) {
s.handleMailWebhook(w, r, "postal", func(r *http.Request) bool {
_, pass, ok := r.BasicAuth()
return ok && constantTimeEqual(pass, s.auth.set.webhookSecret)
}, mailer.ParsePostalWebhook)
}
// handleMailWebhook is the shared webhook flow. Authenticated payloads that
// cannot be used, and unknown message ids, get 200 so the provider does not
// retry forever.
func (s *Server) handleMailWebhook(w http.ResponseWriter, r *http.Request, provider string,
authorized func(*http.Request) bool, parse func([]byte) ([]mailer.Event, error)) {
a := s.auth
if !a.allow(w, r, a.hook) {
return
}
if !authorized(r) {
writeError(w, http.StatusUnauthorized, "unauthorized")
return
}
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 256<<10))
if err != nil {
writeError(w, http.StatusBadRequest, "body too large")
return
}
evs, err := parse(body)
if err != nil {
log.Printf("[users] %s webhook: ignored payload: %s", provider, redactAddrs(err))
writeJSON(w, okResponse{OK: true})
return
}
a.ingestMailEvents(evs)
writeJSON(w, okResponse{OK: true})
}