Files
listmonk/models/messages.go
Kailash Nadh 67ad4d54ce Add external recipient support to /api/tx endpoint.
This patch introduces the long pending requirement of being able to send messages
to arbitrary recipients via the `/api/tx` endpoint without them having to be
subscribers in the database.

It maintains backwards compatibility and introduces just one new field,
`subscriber_mode` to the `/api/tx` endpoint.

- `default`  - Recipients must exist as subscribers in the database.
               Pass either `subscriber_emails` or `subscriber_ids`.                                                                                                                                                                     |

- `fallback` - Only accepts `subscriber_emails` and looks up subscribers in the
               database. If not found, sends the message to the e-mail anyway.
               In the template, apart from `{{ .Subscriber.Email }}`, other
               subscriber fields such as `.Name`. will be empty.
               Use `{{ Tx.Data.* }}` instead. |

- `external` - Sends to the given `subscriber_emails` without subscriber lookup
               in the database. In the template, apart from
               `{{ .Subscriber.Email }}`, other subscriber fields such as
               `.Name`. will be empty. Use `{{ Tx.Data.* }}` instead.                                                |
2025-11-28 16:37:20 +05:30

121 lines
2.8 KiB
Go

package models
import (
"bytes"
"fmt"
"html/template"
"net/textproto"
"strings"
txttpl "text/template"
)
// Message is the message pushed to a Messenger.
type Message struct {
From string
To []string
Subject string
ContentType string
Body []byte
AltBody []byte
Headers textproto.MIMEHeader
Attachments []Attachment
Subscriber Subscriber
// Campaign is generally the same instance for a large number of subscribers.
Campaign *Campaign
// Messenger is the messenger backend to use: email|postback.
Messenger string
}
// Attachment represents a file or blob attachment that can be
// sent along with a message by a Messenger.
type Attachment struct {
Name string
Header textproto.MIMEHeader
Content []byte
}
// TxMessage subscriber modes.
const (
TxSubModeDefault = "default"
TxSubModeFallback = "fallback"
TxSubModeExternal = "external"
)
// TxMessage represents an e-mail campaign.
type TxMessage struct {
SubscriberMode string `json:"subscriber_mode"`
SubscriberEmails []string `json:"subscriber_emails"`
SubscriberIDs []int `json:"subscriber_ids"`
// Deprecated.
SubscriberEmail string `json:"subscriber_email"`
SubscriberID int `json:"subscriber_id"`
TemplateID int `json:"template_id"`
Data map[string]any `json:"data"`
FromEmail string `json:"from_email"`
Headers Headers `json:"headers"`
ContentType string `json:"content_type"`
Messenger string `json:"messenger"`
Subject string `json:"subject"`
// File attachments added from multi-part form data.
Attachments []Attachment `json:"-"`
Body []byte `json:"-"`
Tpl *template.Template `json:"-"`
SubjectTpl *txttpl.Template `json:"-"`
}
func (m *TxMessage) Render(sub Subscriber, tpl *Template) error {
data := struct {
Subscriber Subscriber
Tx *TxMessage
}{sub, m}
// Render the body.
b := bytes.Buffer{}
if err := tpl.Tpl.ExecuteTemplate(&b, BaseTpl, data); err != nil {
return err
}
m.Body = make([]byte, b.Len())
copy(m.Body, b.Bytes())
b.Reset()
// Was a subject provided in the message?
var (
subjTpl *txttpl.Template
subject = m.Subject
)
if subject != "" {
if strings.Contains(m.Subject, "{{") {
// If the subject has a template string, render that.
s, err := txttpl.New(BaseTpl).Funcs(txttpl.FuncMap(nil)).Parse(m.Subject)
if err != nil {
return fmt.Errorf("error compiling subject: %v", err)
}
subjTpl = s
}
} else {
// Use the subject from the template.
subject = tpl.Subject
subjTpl = tpl.SubjectTpl
}
// If the subject is also a template, render that.
if subjTpl != nil {
if err := subjTpl.ExecuteTemplate(&b, BaseTpl, data); err != nil {
return err
}
m.Subject = b.String()
b.Reset()
} else {
m.Subject = subject
}
return nil
}