diff --git a/release-notes/8.0.0/fix/3504.md b/release-notes/8.0.0/fix/3504.md
new file mode 100644
index 0000000000..8ece1557ab
--- /dev/null
+++ b/release-notes/8.0.0/fix/3504.md
@@ -0,0 +1 @@
+Fixed that inline attachments of emails (as they occur for example with Apple Mail) are not attached to comments.
diff --git a/services/mailer/incoming/incoming.go b/services/mailer/incoming/incoming.go
index eade0cf271..6530b7cc60 100644
--- a/services/mailer/incoming/incoming.go
+++ b/services/mailer/incoming/incoming.go
@@ -219,6 +219,11 @@ loop:
 			}
 
 			err := func() error {
+				if isAlreadyHandled(handledSet, msg) {
+					log.Debug("Skipping already handled message")
+					return nil
+				}
+
 				r := msg.GetBody(section)
 				if r == nil {
 					return fmt.Errorf("could not get body from message: %w", err)
@@ -277,6 +282,11 @@ loop:
 	return nil
 }
 
+// isAlreadyHandled tests if the message was already handled
+func isAlreadyHandled(handledSet *imap.SeqSet, msg *imap.Message) bool {
+	return handledSet.Contains(msg.SeqNum)
+}
+
 // isAutomaticReply tests if the headers indicate an automatic reply
 func isAutomaticReply(env *enmime.Envelope) bool {
 	autoSubmitted := env.GetHeader("Auto-Submitted")
@@ -367,6 +377,14 @@ func getContentFromMailReader(env *enmime.Envelope) *MailContent {
 			Content: attachment.Content,
 		})
 	}
+	for _, inline := range env.Inlines {
+		if inline.FileName != "" {
+			attachments = append(attachments, &Attachment{
+				Name:    inline.FileName,
+				Content: inline.Content,
+			})
+		}
+	}
 
 	return &MailContent{
 		Content:     reply.FromText(env.Text),
diff --git a/services/mailer/incoming/incoming_test.go b/services/mailer/incoming/incoming_test.go
index 5d84848e3f..f2bb7fc498 100644
--- a/services/mailer/incoming/incoming_test.go
+++ b/services/mailer/incoming/incoming_test.go
@@ -7,10 +7,24 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/emersion/go-imap"
 	"github.com/jhillyerd/enmime"
 	"github.com/stretchr/testify/assert"
 )
 
+func TestNotHandleTwice(t *testing.T) {
+	handledSet := new(imap.SeqSet)
+	msg := imap.NewMessage(90, []imap.FetchItem{imap.FetchBody})
+
+	handled := isAlreadyHandled(handledSet, msg)
+	assert.Equal(t, false, handled)
+
+	handledSet.AddNum(msg.SeqNum)
+
+	handled = isAlreadyHandled(handledSet, msg)
+	assert.Equal(t, true, handled)
+}
+
 func TestIsAutomaticReply(t *testing.T) {
 	cases := []struct {
 		Headers  map[string]string
@@ -95,6 +109,32 @@ func TestGetContentFromMailReader(t *testing.T) {
 	assert.Equal(t, "attachment.txt", content.Attachments[0].Name)
 	assert.Equal(t, []byte("attachment content"), content.Attachments[0].Content)
 
+	mailString = "Content-Type: multipart/mixed; boundary=message-boundary\r\n" +
+		"\r\n" +
+		"--message-boundary\r\n" +
+		"Content-Type: multipart/alternative; boundary=text-boundary\r\n" +
+		"\r\n" +
+		"--text-boundary\r\n" +
+		"Content-Type: text/plain\r\n" +
+		"Content-Disposition: inline\r\n" +
+		"\r\n" +
+		"mail content\r\n" +
+		"--text-boundary--\r\n" +
+		"--message-boundary\r\n" +
+		"Content-Type: text/plain\r\n" +
+		"Content-Disposition: inline; filename=attachment.txt\r\n" +
+		"\r\n" +
+		"attachment content\r\n" +
+		"--message-boundary--\r\n"
+
+	env, err = enmime.ReadEnvelope(strings.NewReader(mailString))
+	assert.NoError(t, err)
+	content = getContentFromMailReader(env)
+	assert.Equal(t, "mail content", content.Content)
+	assert.Len(t, content.Attachments, 1)
+	assert.Equal(t, "attachment.txt", content.Attachments[0].Name)
+	assert.Equal(t, []byte("attachment content"), content.Attachments[0].Content)
+
 	mailString = "Content-Type: multipart/mixed; boundary=message-boundary\r\n" +
 		"\r\n" +
 		"--message-boundary\r\n" +