diff --git a/pkg/notifications/mail.go b/pkg/notifications/mail.go
index 481553f62..8c4ede991 100644
--- a/pkg/notifications/mail.go
+++ b/pkg/notifications/mail.go
@@ -26,16 +26,18 @@ import (
// Mail is a mail message
type Mail struct {
- from string
- to string
- subject string
- actionText string
- actionURL string
- greeting string
- introLines []*mailLine
- outroLines []*mailLine
- footerLines []*mailLine
- threadID string
+ from string
+ to string
+ subject string
+ actionText string
+ actionURL string
+ greeting string
+ headerLine *mailLine
+ introLines []*mailLine
+ outroLines []*mailLine
+ footerLines []*mailLine
+ threadID string
+ conversational bool
}
type mailLine struct {
@@ -101,12 +103,50 @@ func (m *Mail) HTML(line string) *Mail {
return m.appendLine(line, true)
}
+// HeaderLine sets the header line for conversational emails (e.g., "@user mentioned you")
+func (m *Mail) HeaderLine(line string) *Mail {
+ m.headerLine = &mailLine{Text: line, isHTML: true}
+ return m
+}
+
// ThreadID sets the thread ID of the mail message for email threading
func (m *Mail) ThreadID(threadID string) *Mail {
m.threadID = threadID
return m
}
+// Conversational sets the email to use conversational styling
+func (m *Mail) Conversational() *Mail {
+ m.conversational = true
+ return m
+}
+
+// IsConversational returns whether the email uses conversational styling
+func (m *Mail) IsConversational() bool {
+ return m.conversational
+}
+
+// CreateConversationalHeader creates a GitHub-style header line with avatar, action text, and task reference.
+// The action string should already contain the doer's name (e.g. "alice left a comment").
+func CreateConversationalHeader(avatarDataURI, action, taskURL, projectTitle, taskIdentifier, taskTitle string) string {
+ avatarHTML := ""
+ if avatarDataURI != "" {
+ avatarHTML = fmt.Sprintf(
+ ``,
+ avatarDataURI,
+ )
+ }
+ return fmt.Sprintf(
+ `%s%s (%s > %s) %s`,
+ avatarHTML,
+ action,
+ taskURL,
+ projectTitle,
+ taskTitle,
+ taskIdentifier,
+ )
+}
+
func (m *Mail) appendLine(line string, isHTML bool) *Mail {
if m.actionURL == "" {
m.introLines = append(m.introLines, &mailLine{
diff --git a/pkg/notifications/mail_render.go b/pkg/notifications/mail_render.go
index 7539aded5..67b254d35 100644
--- a/pkg/notifications/mail_render.go
+++ b/pkg/notifications/mail_render.go
@@ -20,6 +20,8 @@ import (
"bytes"
"embed"
templatehtml "html/template"
+ "regexp"
+ "strings"
templatetext "text/template"
"code.vikunja.io/api/pkg/config"
@@ -45,11 +47,25 @@ const mailTemplatePlain = `
{{ $line.Text }}
{{ end }}`
+const mailTemplateConversationalPlain = `
+{{ if .HeaderLinePlain }}{{ .HeaderLinePlain }}
+{{ end }}{{ range $line := .IntroLines}}
+{{ $line.Text }}
+{{ end }}
+{{ if .ActionURL }}{{ .ActionText }}:
+{{ .ActionURL }}{{end}}
+{{ range $line := .OutroLines}}
+{{ $line.Text }}
+{{ end }}
+{{ range $line := .FooterLines}}
+{{ $line.Text }}
+{{ end }}`
+
const mailTemplateHTML = `