diff --git a/README.md b/README.md
index f1bfee6..cfe0f65 100644
--- a/README.md
+++ b/README.md
@@ -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] Reply to matrix thread sends reply into email thread
- [x] Email signatures
+- [x] Email autoreply / autoresponder for new email threads
## 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
+* **`!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 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)
diff --git a/bot/command.go b/bot/command.go
index 69c0367..3f9e321 100644
--- a/bot/command.go
+++ b/bot/command.go
@@ -99,6 +99,12 @@ func (b *Bot) initCommands() commandList {
allowed: b.allowOwner,
},
{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,
description: "Get or set signature of the room (markdown supported)",
diff --git a/bot/command_owner.go b/bot/command_owner.go
index 0e2696b..d72fc26 100644
--- a/bot/command_owner.go
+++ b/bot/command_owner.go
@@ -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:], " ")
}
diff --git a/bot/config/room.go b/bot/config/room.go
index 0531f28..f99a203 100644
--- a/bot/config/room.go
+++ b/bot/config/room.go
@@ -14,27 +14,31 @@ type Room map[string]string
// option keys
const (
- RoomActive = ".active"
- RoomOwner = "owner"
- RoomMailbox = "mailbox"
- RoomDomain = "domain"
- RoomNoSend = "nosend"
- RoomNoReplies = "noreplies"
- RoomNoCC = "nocc"
- RoomNoSender = "nosender"
- RoomNoRecipient = "norecipient"
- RoomNoSubject = "nosubject"
- RoomNoHTML = "nohtml"
- RoomNoThreads = "nothreads"
- RoomNoFiles = "nofiles"
- RoomNoInlines = "noinlines"
- RoomPassword = "password"
- RoomSignature = "signature"
+ RoomActive = ".active"
+ RoomOwner = "owner"
+ RoomMailbox = "mailbox"
+ RoomDomain = "domain"
+ RoomPassword = "password"
+ 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"
+ RoomSpamcheckMX = "spamcheck:mx"
RoomSpamcheckSMTP = "spamcheck:smtp"
RoomSpamcheckSPF = "spamcheck:spf"
- RoomSpamcheckMX = "spamcheck:mx"
- RoomSpamlist = "spamlist"
+
+ RoomSpamlist = "spamlist"
)
// Get option
@@ -71,6 +75,10 @@ func (s Room) Signature() string {
return s.Get(RoomSignature)
}
+func (s Room) Autoreply() string {
+ return s.Get(RoomAutoreply)
+}
+
func (s Room) NoSend() bool {
return utils.Bool(s.Get(RoomNoSend))
}
diff --git a/bot/email.go b/bot/email.go
index 3401767..b5798f9 100644
--- a/bot/email.go
+++ b/bot/email.go
@@ -111,6 +111,8 @@ func (b *Bot) GetIFOptions(roomID id.RoomID) email.IncomingFilteringOptions {
}
// IncomingEmail sends incoming email to matrix room
+//
+//nolint:gocognit // TODO
func (b *Bot) IncomingEmail(ctx context.Context, email *email.Email) error {
roomID, ok := b.GetMapping(email.Mailbox(true))
if !ok {
@@ -125,9 +127,11 @@ func (b *Bot) IncomingEmail(ctx context.Context, email *email.Email) error {
defer b.mu.Unlock(roomID.String())
var threadID id.EventID
+ newThread := true
if email.InReplyTo != "" || email.References != "" {
threadID = b.getThreadID(roomID, email.InReplyTo, email.References)
if threadID != "" {
+ newThread = false
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)
}
threadID = "" // unknown event edge case - remove existing thread ID to avoid complications
+ newThread = true
}
if threadID == "" {
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)
}
+ if newThread && cfg.Autoreply() != "" {
+ b.sendAutoreply(roomID, threadID)
+ }
+
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 += "