Skip to content

Commit e1dfc2a

Browse files
committed
Centralize campaign From header templating logic.
1 parent 5c33a0d commit e1dfc2a

5 files changed

Lines changed: 42 additions & 67 deletions

File tree

cmd/campaigns.go

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -663,27 +663,9 @@ func (a *App) sendTestMessage(sub models.Subscriber, camp *models.Campaign) erro
663663
func (a *App) validateCampaignFields(c campReq) (campReq, error) {
664664
if c.FromEmail == "" {
665665
c.FromEmail = a.cfg.FromEmail
666-
} else {
667-
fromEmail := c.FromEmail
668-
669-
// If the From header contains a template, render it against the
670-
// dummy subscriber so the resolved address can be validated.
671-
if strings.Contains(fromEmail, "{{") {
672-
testCamp := models.Campaign{FromEmail: fromEmail}
673-
if err := testCamp.CompileTemplate(a.manager.TemplateFuncs(&testCamp)); err != nil {
674-
return c, errors.New(a.i18n.Ts("campaigns.fieldInvalidFromEmail") + ": " + err.Error())
675-
}
676-
msg, err := a.manager.NewCampaignMessage(&testCamp, dummySubscriber)
677-
if err != nil {
678-
return c, errors.New(a.i18n.Ts("campaigns.fieldInvalidFromEmail") + ": " + err.Error())
679-
}
680-
fromEmail = msg.From()
681-
}
682-
683-
if !reFromAddress.Match([]byte(fromEmail)) {
684-
if _, err := a.importer.SanitizeEmail(fromEmail); err != nil {
685-
return c, errors.New(a.i18n.T("campaigns.fieldInvalidFromEmail"))
686-
}
666+
} else if !models.HasTplExpr(c.FromEmail) && !reFromAddress.Match([]byte(c.FromEmail)) {
667+
if _, err := a.importer.SanitizeEmail(c.FromEmail); err != nil {
668+
return c, errors.New(a.i18n.T("campaigns.fieldInvalidFromEmail"))
687669
}
688670
}
689671

internal/manager/message.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,6 @@ func (m *CampaignMessage) Subject() string {
101101
return m.subject
102102
}
103103

104-
// From returns the rendered From header.
105-
func (m *CampaignMessage) From() string {
106-
return m.from
107-
}
108-
109104
// Body returns a copy of the message body.
110105
func (m *CampaignMessage) Body() []byte {
111106
out := make([]byte, len(m.body))

models/campaigns.go

Lines changed: 36 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -140,34 +140,15 @@ func (camps Campaigns) LoadStats(stmt *sqlx.Stmt) error {
140140
// CompileTemplate compiles a campaign body template into its base
141141
// template and sets the resultant template to Campaign.Tpl.
142142
func (c *Campaign) CompileTemplate(f template.FuncMap) error {
143-
// If the subject line has a template string, compile it.
144-
if hasTplExpr(c.Subject) {
145-
subj := c.Subject
146-
for _, r := range regTplFuncs {
147-
subj = r.regExp.ReplaceAllString(subj, r.replace)
148-
}
149-
150-
var txtFuncs map[string]any = f
151-
subjTpl, err := txttpl.New(ContentTpl).Funcs(txtFuncs).Parse(subj)
152-
if err != nil {
153-
return fmt.Errorf("error compiling subject: %v", err)
154-
}
155-
c.SubjectTpl = subjTpl
143+
var err error
144+
c.SubjectTpl, err = compileTxtTpl("subject", c.Subject, f)
145+
if err != nil {
146+
return err
156147
}
157148

158-
// If the From header has a template string, compile it.
159-
if hasTplExpr(c.FromEmail) {
160-
from := c.FromEmail
161-
for _, r := range regTplFuncs {
162-
from = r.regExp.ReplaceAllString(from, r.replace)
163-
}
164-
165-
var txtFuncs map[string]any = f
166-
fromTpl, err := txttpl.New(ContentTpl).Funcs(txtFuncs).Parse(from)
167-
if err != nil {
168-
return fmt.Errorf("error compiling from: %v", err)
169-
}
170-
c.FromEmailTpl = fromTpl
149+
c.FromEmailTpl, err = compileTxtTpl("from", c.FromEmail, f)
150+
if err != nil {
151+
return err
171152
}
172153

173154
// Compile the base template.
@@ -213,7 +194,7 @@ func (c *Campaign) CompileTemplate(f template.FuncMap) error {
213194
}
214195
c.Tpl = out
215196

216-
if hasTplExpr(c.AltBody.String) {
197+
if HasTplExpr(c.AltBody.String) {
217198
b := c.AltBody.String
218199
for _, r := range regTplFuncs {
219200
b = r.regExp.ReplaceAllString(b, r.replace)
@@ -228,7 +209,7 @@ func (c *Campaign) CompileTemplate(f template.FuncMap) error {
228209
// Compile any header values that contain template expressions.
229210
for _, set := range c.Headers {
230211
for _, val := range set {
231-
if hasTplExpr(val) {
212+
if HasTplExpr(val) {
232213
c.HeaderTpls = make([]map[string]*txttpl.Template, len(c.Headers))
233214
break
234215
}
@@ -242,25 +223,36 @@ func (c *Campaign) CompileTemplate(f template.FuncMap) error {
242223
for i, set := range c.Headers {
243224
c.HeaderTpls[i] = make(map[string]*txttpl.Template, len(set))
244225
for hdr, val := range set {
245-
if !hasTplExpr(val) {
246-
continue
247-
}
248-
tpl, err := txttpl.New(ContentTpl).Funcs(txtFuncs).Parse(val)
226+
tpl, err := compileTxtTpl(fmt.Sprintf("header %q", hdr), val, txtFuncs)
249227
if err != nil {
250-
return fmt.Errorf("error compiling header %q: %v", hdr, err)
228+
return err
229+
}
230+
if tpl != nil {
231+
c.HeaderTpls[i][hdr] = tpl
251232
}
252-
c.HeaderTpls[i][hdr] = tpl
253233
}
254234
}
255235
}
256236

257237
return nil
258238
}
259239

260-
// hasTplExpr checks whether a given string has a Go template expression with {{ and }}.
261-
func hasTplExpr(s string) bool {
262-
_, after, ok := strings.Cut(s, "{{")
263-
return ok && strings.Contains(after, "}}")
240+
func compileTxtTpl(label, val string, f template.FuncMap) (*txttpl.Template, error) {
241+
if !HasTplExpr(val) {
242+
return nil, nil
243+
}
244+
245+
for _, r := range regTplFuncs {
246+
val = r.regExp.ReplaceAllString(val, r.replace)
247+
}
248+
249+
var txtFuncs map[string]any = f
250+
tpl, err := txttpl.New(ContentTpl).Funcs(txtFuncs).Parse(val)
251+
if err != nil {
252+
return nil, fmt.Errorf("error compiling %s: %v", label, err)
253+
}
254+
255+
return tpl, nil
264256
}
265257

266258
// ConvertContent converts a campaign's body from one format to another,
@@ -286,3 +278,9 @@ func (c *Campaign) ConvertContent(from, to string) (string, error) {
286278

287279
return out, nil
288280
}
281+
282+
// HasTplExpr checks whether a given string has a Go template expression with {{ and }}.
283+
func HasTplExpr(s string) bool {
284+
_, after, ok := strings.Cut(s, "{{")
285+
return ok && strings.Contains(after, "}}")
286+
}

models/messages.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func (m *TxMessage) Render(sub Subscriber, tpl *Template, funcs txttpl.FuncMap)
8686
b.Reset()
8787

8888
// Render alt body if it has any templating strings.
89-
if m.AltBody != "" && hasTplExpr(m.AltBody) {
89+
if m.AltBody != "" && HasTplExpr(m.AltBody) {
9090
t, err := txttpl.New(BaseTpl).Funcs(funcs).Parse(m.AltBody)
9191
if err != nil {
9292
return fmt.Errorf("error compiling alt body: %v", err)
@@ -104,7 +104,7 @@ func (m *TxMessage) Render(sub Subscriber, tpl *Template, funcs txttpl.FuncMap)
104104
subject = m.Subject
105105
)
106106
if subject != "" {
107-
if hasTplExpr(m.Subject) {
107+
if HasTplExpr(m.Subject) {
108108
// If the subject has a template string, render that.
109109
s, err := txttpl.New(BaseTpl).Funcs(funcs).Parse(m.Subject)
110110
if err != nil {

models/templates.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func (t *Template) Compile(f template.FuncMap) error {
4444
t.Tpl = tpl
4545

4646
// If the subject line has a template string, compile it.
47-
if hasTplExpr(t.Subject) {
47+
if HasTplExpr(t.Subject) {
4848
subj := t.Subject
4949

5050
subjTpl, err := txttpl.New(BaseTpl).Funcs(txttpl.FuncMap(f)).Parse(subj)

0 commit comments

Comments
 (0)