add !pm autoreply
This commit is contained in:
@@ -36,6 +36,7 @@ so you can use it to send emails from your apps and scripts as well.
|
|||||||
- [x] Send a message to matrix room with special format to send a new email, even to multiple email addresses at once
|
- [x] Send a message to matrix room with special format to send a new email, even to multiple email addresses at once
|
||||||
- [x] Reply to matrix thread sends reply into email thread
|
- [x] Reply to matrix thread sends reply into email thread
|
||||||
- [x] Email signatures
|
- [x] Email signatures
|
||||||
|
- [x] Email autoreply / autoresponder for new email threads
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
@@ -121,6 +122,7 @@ If you want to change them - check available options in the help message (`!pm h
|
|||||||
|
|
||||||
> The following section is visible to the mailbox owners only
|
> The following section is visible to the mailbox owners only
|
||||||
|
|
||||||
|
* **`!pm autoreply`** - Get or set autoreply of the room (markdown supported) that will be sent on any new incoming email thread
|
||||||
* **`!pm signature`** - Get or set signature of the room (markdown supported)
|
* **`!pm signature`** - Get or set signature of the room (markdown supported)
|
||||||
* **`!pm nosend`** - Get or set `nosend` of the room (`true` - disable email sending; `false` - enable email sending)
|
* **`!pm nosend`** - Get or set `nosend` of the room (`true` - disable email sending; `false` - enable email sending)
|
||||||
* **`!pm noreplies`** - Get or set `noreplies` of the room (`true` - ignore matrix replies; `false` - parse matrix replies)
|
* **`!pm noreplies`** - Get or set `noreplies` of the room (`true` - ignore matrix replies; `false` - parse matrix replies)
|
||||||
|
|||||||
@@ -99,6 +99,12 @@ func (b *Bot) initCommands() commandList {
|
|||||||
allowed: b.allowOwner,
|
allowed: b.allowOwner,
|
||||||
},
|
},
|
||||||
{allowed: b.allowOwner, description: "mailbox options"}, // delimiter
|
{allowed: b.allowOwner, description: "mailbox options"}, // delimiter
|
||||||
|
{
|
||||||
|
key: config.RoomAutoreply,
|
||||||
|
description: "Get or set autoreply of the room (markdown supported) that will be send for any new incoming email thread",
|
||||||
|
sanitizer: func(s string) string { return s },
|
||||||
|
allowed: b.allowOwner,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: config.RoomSignature,
|
key: config.RoomSignature,
|
||||||
description: "Get or set signature of the room (markdown supported)",
|
description: "Get or set signature of the room (markdown supported)",
|
||||||
|
|||||||
@@ -119,7 +119,8 @@ func (b *Bot) setOption(ctx context.Context, name, value string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if name == config.RoomSignature {
|
if name == config.RoomAutoreply ||
|
||||||
|
name == config.RoomSignature {
|
||||||
value = strings.Join(b.parseCommand(evt.Content.AsMessage().Body, false)[1:], " ")
|
value = strings.Join(b.parseCommand(evt.Content.AsMessage().Body, false)[1:], " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,22 +18,26 @@ const (
|
|||||||
RoomOwner = "owner"
|
RoomOwner = "owner"
|
||||||
RoomMailbox = "mailbox"
|
RoomMailbox = "mailbox"
|
||||||
RoomDomain = "domain"
|
RoomDomain = "domain"
|
||||||
RoomNoSend = "nosend"
|
|
||||||
RoomNoReplies = "noreplies"
|
|
||||||
RoomNoCC = "nocc"
|
|
||||||
RoomNoSender = "nosender"
|
|
||||||
RoomNoRecipient = "norecipient"
|
|
||||||
RoomNoSubject = "nosubject"
|
|
||||||
RoomNoHTML = "nohtml"
|
|
||||||
RoomNoThreads = "nothreads"
|
|
||||||
RoomNoFiles = "nofiles"
|
|
||||||
RoomNoInlines = "noinlines"
|
|
||||||
RoomPassword = "password"
|
RoomPassword = "password"
|
||||||
RoomSignature = "signature"
|
RoomSignature = "signature"
|
||||||
|
RoomAutoreply = "autoreply"
|
||||||
|
|
||||||
|
RoomNoCC = "nocc"
|
||||||
|
RoomNoFiles = "nofiles"
|
||||||
|
RoomNoHTML = "nohtml"
|
||||||
|
RoomNoInlines = "noinlines"
|
||||||
|
RoomNoRecipient = "norecipient"
|
||||||
|
RoomNoReplies = "noreplies"
|
||||||
|
RoomNoSend = "nosend"
|
||||||
|
RoomNoSender = "nosender"
|
||||||
|
RoomNoSubject = "nosubject"
|
||||||
|
RoomNoThreads = "nothreads"
|
||||||
|
|
||||||
RoomSpamcheckDKIM = "spamcheck:dkim"
|
RoomSpamcheckDKIM = "spamcheck:dkim"
|
||||||
|
RoomSpamcheckMX = "spamcheck:mx"
|
||||||
RoomSpamcheckSMTP = "spamcheck:smtp"
|
RoomSpamcheckSMTP = "spamcheck:smtp"
|
||||||
RoomSpamcheckSPF = "spamcheck:spf"
|
RoomSpamcheckSPF = "spamcheck:spf"
|
||||||
RoomSpamcheckMX = "spamcheck:mx"
|
|
||||||
RoomSpamlist = "spamlist"
|
RoomSpamlist = "spamlist"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -71,6 +75,10 @@ func (s Room) Signature() string {
|
|||||||
return s.Get(RoomSignature)
|
return s.Get(RoomSignature)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s Room) Autoreply() string {
|
||||||
|
return s.Get(RoomAutoreply)
|
||||||
|
}
|
||||||
|
|
||||||
func (s Room) NoSend() bool {
|
func (s Room) NoSend() bool {
|
||||||
return utils.Bool(s.Get(RoomNoSend))
|
return utils.Bool(s.Get(RoomNoSend))
|
||||||
}
|
}
|
||||||
|
|||||||
103
bot/email.go
103
bot/email.go
@@ -111,6 +111,8 @@ func (b *Bot) GetIFOptions(roomID id.RoomID) email.IncomingFilteringOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IncomingEmail sends incoming email to matrix room
|
// IncomingEmail sends incoming email to matrix room
|
||||||
|
//
|
||||||
|
//nolint:gocognit // TODO
|
||||||
func (b *Bot) IncomingEmail(ctx context.Context, email *email.Email) error {
|
func (b *Bot) IncomingEmail(ctx context.Context, email *email.Email) error {
|
||||||
roomID, ok := b.GetMapping(email.Mailbox(true))
|
roomID, ok := b.GetMapping(email.Mailbox(true))
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -125,9 +127,11 @@ func (b *Bot) IncomingEmail(ctx context.Context, email *email.Email) error {
|
|||||||
defer b.mu.Unlock(roomID.String())
|
defer b.mu.Unlock(roomID.String())
|
||||||
|
|
||||||
var threadID id.EventID
|
var threadID id.EventID
|
||||||
|
newThread := true
|
||||||
if email.InReplyTo != "" || email.References != "" {
|
if email.InReplyTo != "" || email.References != "" {
|
||||||
threadID = b.getThreadID(roomID, email.InReplyTo, email.References)
|
threadID = b.getThreadID(roomID, email.InReplyTo, email.References)
|
||||||
if threadID != "" {
|
if threadID != "" {
|
||||||
|
newThread = false
|
||||||
b.setThreadID(roomID, email.MessageID, threadID)
|
b.setThreadID(roomID, email.MessageID, threadID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,6 +142,7 @@ func (b *Bot) IncomingEmail(ctx context.Context, email *email.Email) error {
|
|||||||
return utils.UnwrapError(serr)
|
return utils.UnwrapError(serr)
|
||||||
}
|
}
|
||||||
threadID = "" // unknown event edge case - remove existing thread ID to avoid complications
|
threadID = "" // unknown event edge case - remove existing thread ID to avoid complications
|
||||||
|
newThread = true
|
||||||
}
|
}
|
||||||
if threadID == "" {
|
if threadID == "" {
|
||||||
threadID = eventID
|
threadID = eventID
|
||||||
@@ -154,18 +159,103 @@ func (b *Bot) IncomingEmail(ctx context.Context, email *email.Email) error {
|
|||||||
b.sendFiles(ctx, roomID, email.Files, cfg.NoThreads(), threadID)
|
b.sendFiles(ctx, roomID, email.Files, cfg.NoThreads(), threadID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if newThread && cfg.Autoreply() != "" {
|
||||||
|
b.sendAutoreply(roomID, threadID)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:gocognit // TODO
|
||||||
|
func (b *Bot) sendAutoreply(roomID id.RoomID, threadID id.EventID) {
|
||||||
|
cfg, err := b.cfg.GetRoom(roomID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
text := cfg.Autoreply()
|
||||||
|
if text == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
evt := &event.Event{
|
||||||
|
ID: threadID + "-autoreply",
|
||||||
|
RoomID: roomID,
|
||||||
|
Content: event.Content{
|
||||||
|
Parsed: &event.MessageEventContent{
|
||||||
|
RelatesTo: &event.RelatesTo{
|
||||||
|
Type: event.RelThread,
|
||||||
|
EventID: threadID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
meta := b.getParentEmail(evt, cfg.Mailbox())
|
||||||
|
|
||||||
|
if meta.To == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if meta.ThreadID == "" {
|
||||||
|
meta.ThreadID = threadID
|
||||||
|
}
|
||||||
|
if meta.Subject == "" {
|
||||||
|
meta.Subject = "Automatic response"
|
||||||
|
}
|
||||||
|
content := format.RenderMarkdown(text, true, true)
|
||||||
|
signature := format.RenderMarkdown(cfg.Signature(), true, true)
|
||||||
|
body := content.Body
|
||||||
|
if signature.Body != "" {
|
||||||
|
body += "\n\n---\n" + signature.Body
|
||||||
|
}
|
||||||
|
var htmlBody string
|
||||||
|
if !cfg.NoHTML() {
|
||||||
|
htmlBody = content.FormattedBody
|
||||||
|
if htmlBody != "" && signature.FormattedBody != "" {
|
||||||
|
htmlBody += "<br><hr><br>" + signature.FormattedBody
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
meta.MessageID = email.MessageID(evt.ID, meta.FromDomain)
|
||||||
|
meta.References = meta.References + " " + meta.MessageID
|
||||||
|
b.log.Info().Any("meta", meta).Msg("sending automatic reply")
|
||||||
|
eml := email.New(meta.MessageID, meta.InReplyTo, meta.References, meta.Subject, meta.From, meta.To, meta.RcptTo, meta.CC, body, htmlBody, nil, nil)
|
||||||
|
data := eml.Compose(b.cfg.GetBot().DKIMPrivateKey())
|
||||||
|
if data == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var queued bool
|
||||||
|
ctx := newContext(evt)
|
||||||
|
recipients := meta.Recipients
|
||||||
|
for _, to := range recipients {
|
||||||
|
queued, err = b.Sendmail(evt.ID, meta.From, to, data)
|
||||||
|
if queued {
|
||||||
|
b.log.Info().Err(err).Str("from", meta.From).Str("to", to).Msg("email has been queued")
|
||||||
|
b.saveSentMetadata(ctx, queued, meta.ThreadID, recipients, eml, cfg, "Autoreply has been sent (queued)")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
b.Error(ctx, evt.RoomID, "cannot send email: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.saveSentMetadata(ctx, queued, meta.ThreadID, recipients, eml, cfg, "Autoreply has been sent")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) canReply(sender id.UserID, roomID id.RoomID) bool {
|
||||||
|
return b.allowSend(sender, roomID) && b.allowReply(sender, roomID)
|
||||||
|
}
|
||||||
|
|
||||||
// SendEmailReply sends replies from matrix thread to email thread
|
// SendEmailReply sends replies from matrix thread to email thread
|
||||||
//
|
//
|
||||||
//nolint:gocognit // TODO
|
//nolint:gocognit // TODO
|
||||||
func (b *Bot) SendEmailReply(ctx context.Context) {
|
func (b *Bot) SendEmailReply(ctx context.Context) {
|
||||||
evt := eventFromContext(ctx)
|
evt := eventFromContext(ctx)
|
||||||
if !b.allowSend(evt.Sender, evt.RoomID) {
|
if !b.canReply(evt.Sender, evt.RoomID) {
|
||||||
return
|
|
||||||
}
|
|
||||||
if !b.allowReply(evt.Sender, evt.RoomID) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cfg, err := b.cfg.GetRoom(evt.RoomID)
|
cfg, err := b.cfg.GetRoom(evt.RoomID)
|
||||||
@@ -399,12 +489,15 @@ func (b *Bot) getParentEmail(evt *event.Event, newFromMailbox string) *parentEma
|
|||||||
|
|
||||||
// saveSentMetadata used to save metadata from !pm sent and thread reply events to a separate notice message
|
// saveSentMetadata used to save metadata from !pm sent and thread reply events to a separate notice message
|
||||||
// because that metadata is needed to determine email thread relations
|
// because that metadata is needed to determine email thread relations
|
||||||
func (b *Bot) saveSentMetadata(ctx context.Context, queued bool, threadID id.EventID, recipients []string, eml *email.Email, cfg config.Room) {
|
func (b *Bot) saveSentMetadata(ctx context.Context, queued bool, threadID id.EventID, recipients []string, eml *email.Email, cfg config.Room, textOverride ...string) {
|
||||||
addrs := strings.Join(recipients, ", ")
|
addrs := strings.Join(recipients, ", ")
|
||||||
text := "Email has been sent to " + addrs
|
text := "Email has been sent to " + addrs
|
||||||
if queued {
|
if queued {
|
||||||
text = "Email to " + addrs + " has been queued"
|
text = "Email to " + addrs + " has been queued"
|
||||||
}
|
}
|
||||||
|
if len(textOverride) > 0 {
|
||||||
|
text = textOverride[0]
|
||||||
|
}
|
||||||
|
|
||||||
evt := eventFromContext(ctx)
|
evt := eventFromContext(ctx)
|
||||||
content := eml.Content(threadID, cfg.ContentOptions())
|
content := eml.Content(threadID, cfg.ContentOptions())
|
||||||
|
|||||||
Reference in New Issue
Block a user