1
1
mirror of https://github.com/go-gitea/gitea synced 2025-07-22 18:28:37 +00:00

Add support for incoming emails (#22056)

closes #13585
fixes #9067
fixes #2386
ref #6226
ref #6219
fixes #745

This PR adds support to process incoming emails to perform actions.
Currently I added handling of replies and unsubscribing from
issues/pulls. In contrast to #13585 the IMAP IDLE command is used
instead of polling which results (in my opinion 😉) in cleaner code.

Procedure:
- When sending an issue/pull reply email, a token is generated which is
present in the Reply-To and References header.
- IMAP IDLE waits until a new email arrives
- The token tells which action should be performed

A possible signature and/or reply gets stripped from the content.

I added a new service to the drone pipeline to test the receiving of
incoming mails. If we keep this in, we may test our outgoing emails too
in future.

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
KN4CK3R
2023-01-14 16:57:10 +01:00
committed by GitHub
parent 20e3ffd208
commit fc037b4b82
26 changed files with 1524 additions and 38 deletions

View File

@@ -29,6 +29,8 @@ import (
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/translation"
incoming_payload "code.gitea.io/gitea/services/mailer/incoming/payload"
"code.gitea.io/gitea/services/mailer/token"
"gopkg.in/gomail.v2"
)
@@ -302,14 +304,57 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
msgID := createReference(ctx.Issue, ctx.Comment, ctx.ActionType)
reference := createReference(ctx.Issue, nil, activities_model.ActionType(0))
var replyPayload []byte
if ctx.Comment != nil && ctx.Comment.Type == issues_model.CommentTypeCode {
replyPayload, err = incoming_payload.CreateReferencePayload(ctx.Comment)
} else {
replyPayload, err = incoming_payload.CreateReferencePayload(ctx.Issue)
}
if err != nil {
return nil, err
}
unsubscribePayload, err := incoming_payload.CreateReferencePayload(ctx.Issue)
if err != nil {
return nil, err
}
msgs := make([]*Message, 0, len(recipients))
for _, recipient := range recipients {
msg := NewMessageFrom([]string{recipient.Email}, ctx.Doer.DisplayName(), setting.MailService.FromEmail, subject, mailBody.String())
msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info)
msg.SetHeader("Message-ID", "<"+msgID+">")
msg.SetHeader("In-Reply-To", "<"+reference+">")
msg.SetHeader("References", "<"+reference+">")
msg.SetHeader("Message-ID", msgID)
msg.SetHeader("In-Reply-To", reference)
references := []string{reference}
listUnsubscribe := []string{"<" + ctx.Issue.HTMLURL() + ">"}
if setting.IncomingEmail.Enabled {
if ctx.Comment != nil {
token, err := token.CreateToken(token.ReplyHandlerType, recipient, replyPayload)
if err != nil {
log.Error("CreateToken failed: %v", err)
} else {
replyAddress := strings.Replace(setting.IncomingEmail.ReplyToAddress, setting.IncomingEmail.TokenPlaceholder, token, 1)
msg.ReplyTo = replyAddress
msg.SetHeader("List-Post", fmt.Sprintf("<mailto:%s>", replyAddress))
references = append(references, fmt.Sprintf("<reply-%s@%s>", token, setting.Domain))
}
}
token, err := token.CreateToken(token.UnsubscribeHandlerType, recipient, unsubscribePayload)
if err != nil {
log.Error("CreateToken failed: %v", err)
} else {
unsubAddress := strings.Replace(setting.IncomingEmail.ReplyToAddress, setting.IncomingEmail.TokenPlaceholder, token, 1)
listUnsubscribe = append(listUnsubscribe, "<mailto:"+unsubAddress+">")
}
}
msg.SetHeader("References", references...)
msg.SetHeader("List-Unsubscribe", listUnsubscribe...)
for key, value := range generateAdditionalHeaders(ctx, actType, recipient) {
msg.SetHeader(key, value)
@@ -345,7 +390,7 @@ func createReference(issue *issues_model.Issue, comment *issues_model.Comment, a
}
}
return fmt.Sprintf("%s/%s/%d%s@%s", issue.Repo.FullName(), path, issue.Index, extra, setting.Domain)
return fmt.Sprintf("<%s/%s/%d%s@%s>", issue.Repo.FullName(), path, issue.Index, extra, setting.Domain)
}
func generateAdditionalHeaders(ctx *mailCommentContext, reason string, recipient *user_model.User) map[string]string {
@@ -357,8 +402,6 @@ func generateAdditionalHeaders(ctx *mailCommentContext, reason string, recipient
// https://datatracker.ietf.org/doc/html/rfc2369
"List-Archive": fmt.Sprintf("<%s>", repo.HTMLURL()),
//"List-Post": https://github.com/go-gitea/gitea/pull/13585
"List-Unsubscribe": ctx.Issue.HTMLURL(),
"X-Mailer": "Gitea",
"X-Gitea-Reason": reason,