diff --git a/docs/releases.md b/docs/releases.md
index d9dad6c9..d93d07da 100644
--- a/docs/releases.md
+++ b/docs/releases.md
@@ -6,7 +6,7 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
 
 **Bug fixes + maintenance:**
 
-* Support for base64 encoded emails ([#610](https://github.com/binwiederhier/ntfy/issues/610), thanks to [@Robert-litts](https://github.com/Robert-litts))
+* Support for base64-encoded and nested multipart emails ([#610](https://github.com/binwiederhier/ntfy/issues/610), thanks to [@Robert-litts](https://github.com/Robert-litts))
 
 **Additional languages:**
 
diff --git a/server/smtp_server.go b/server/smtp_server.go
index aefe3147..3e1df55f 100644
--- a/server/smtp_server.go
+++ b/server/smtp_server.go
@@ -22,9 +22,14 @@ var (
 	errInvalidAddress         = errors.New("invalid address")
 	errInvalidTopic           = errors.New("invalid topic")
 	errTooManyRecipients      = errors.New("too many recipients")
+	errMultipartNestedTooDeep = errors.New("multipart message nested too deep")
 	errUnsupportedContentType = errors.New("unsupported content type")
 )
 
+const (
+	maxMultipartDepth = 2
+)
+
 // smtpBackend implements SMTP server methods.
 type smtpBackend struct {
 	config  *Config
@@ -121,7 +126,7 @@ func (s *smtpSession) Data(r io.Reader) error {
 		if err != nil {
 			return err
 		}
-		body, err := readMailBody(msg)
+		body, err := readMailBody(msg.Body, msg.Header)
 		if err != nil {
 			return err
 		}
@@ -203,36 +208,42 @@ func (s *smtpSession) withFailCount(fn func() error) error {
 	return err
 }
 
-func readMailBody(msg *mail.Message) (string, error) {
-	if msg.Header.Get("Content-Type") == "" {
-		return readPlainTextMailBody(msg.Body, msg.Header.Get("Content-Transfer-Encoding"))
+func readMailBody(body io.Reader, header mail.Header) (string, error) {
+	if header.Get("Content-Type") == "" {
+		return readPlainTextMailBody(body, header.Get("Content-Transfer-Encoding"))
 	}
-	contentType, params, err := mime.ParseMediaType(msg.Header.Get("Content-Type"))
+	contentType, params, err := mime.ParseMediaType(header.Get("Content-Type"))
 	if err != nil {
 		return "", err
 	}
 	if strings.ToLower(contentType) == "text/plain" {
-		return readPlainTextMailBody(msg.Body, msg.Header.Get("Content-Transfer-Encoding"))
+		return readPlainTextMailBody(body, header.Get("Content-Transfer-Encoding"))
 	} else if strings.HasPrefix(strings.ToLower(contentType), "multipart/") {
-		return readMultipartMailBody(msg, params)
+		return readMultipartMailBody(body, params, 0)
 	}
 	return "", errUnsupportedContentType
 }
 
-func readMultipartMailBody(msg *mail.Message, params map[string]string) (string, error) {
-	mr := multipart.NewReader(msg.Body, params["boundary"])
+func readMultipartMailBody(body io.Reader, params map[string]string, depth int) (string, error) {
+	if depth >= maxMultipartDepth {
+		return "", errMultipartNestedTooDeep
+	}
+	mr := multipart.NewReader(body, params["boundary"])
 	for {
 		part, err := mr.NextPart()
 		if err != nil { // may be io.EOF
 			return "", err
 		}
-		partContentType, _, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
+		partContentType, partParams, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
 		if err != nil {
 			return "", err
-		} else if strings.ToLower(partContentType) != "text/plain" {
-			continue
 		}
-		return readPlainTextMailBody(part, part.Header.Get("Content-Transfer-Encoding"))
+		if strings.ToLower(partContentType) == "text/plain" {
+			return readPlainTextMailBody(part, part.Header.Get("Content-Transfer-Encoding"))
+		} else if strings.HasPrefix(strings.ToLower(partContentType), "multipart/") {
+			return readMultipartMailBody(part, partParams, depth+1)
+		}
+		// Continue with next part
 	}
 }
 
diff --git a/server/smtp_server_test.go b/server/smtp_server_test.go
index 8932d726..701ef211 100644
--- a/server/smtp_server_test.go
+++ b/server/smtp_server_test.go
@@ -390,10 +390,8 @@ L0VOIj4KClRoaXMgaXMgYSB0ZXN0IG1lc3NhZ2UgZnJvbSBUcnVlTkFTIENPUkUuCg==
 	writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
 }
 
-/*
-	func TestSmtpBackend_NestedMultipartBase64(t *testing.T) {
-		email := `EHLO example.com
-
+func TestSmtpBackend_NestedMultipartBase64(t *testing.T) {
+	email := `EHLO example.com
 MAIL FROM: test@mydomain.me
 RCPT TO: ntfy-mytopic@ntfy.sh
 DATA
@@ -431,14 +429,69 @@ L0VOIj4KClRoaXMgaXMgYSB0ZXN0IG1lc3NhZ2UgZnJvbSBUcnVlTkFTIENPUkUuCg==
 .
 `
 
-		s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
-			t.Fatal("This should not be called")
-		})
-		defer s.Close()
-		defer c.Close()
-		writeAndReadUntilLine(t, email, c, scanner, "451 4.0.0 invalid address")
-	}
-*/
+	s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
+		require.Equal(t, "/mytopic", r.URL.Path)
+		require.Equal(t, "TrueNAS truenas.local: TrueNAS Test Message hostname: truenas.local", r.Header.Get("Title"))
+		require.Equal(t, "This is a test message from TrueNAS CORE.", readAll(t, r.Body))
+	})
+	defer s.Close()
+	defer c.Close()
+	writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
+}
+
+func TestSmtpBackend_NestedMultipartTooDeep(t *testing.T) {
+	email := `EHLO example.com
+MAIL FROM: test@mydomain.me
+RCPT TO: ntfy-mytopic@ntfy.sh
+DATA
+Content-Type: multipart/mixed; boundary="===============1=="
+MIME-Version: 1.0
+Subject: TrueNAS truenas.local: TrueNAS Test Message hostname: truenas.local
+From: =?utf-8?q?Robbie?= <test@mydomain.me>
+To: test@mydomain.me
+Date: Thu, 16 Feb 2023 01:04:00 -0000
+Message-ID: <truenas-20230216.010400.344514.b'8jfL'@truenas.local>
+
+This is a multi-part message in MIME format.
+--===============1==
+Content-Type: multipart/alternative; boundary="===============2=="
+MIME-Version: 1.0
+
+--===============2==
+Content-Type: multipart/alternative; boundary="===============3=="
+MIME-Version: 1.0
+
+--===============3==
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: base64
+
+VGhpcyBpcyBhIHRlc3QgbWVzc2FnZSBmcm9tIFRydWVOQVMgQ09SRS4=
+
+--===============3==
+Content-Type: text/html; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: base64
+
+PCFET0NUWVBFIEhUTUwgUFVCTElDICItLy9XM0MvL0RURCBIVE1MIDQuMCBUcmFuc2l0aW9uYWwv
+L0VOIj4KClRoaXMgaXMgYSB0ZXN0IG1lc3NhZ2UgZnJvbSBUcnVlTkFTIENPUkUuCg==
+
+--===============3==--
+
+--===============2==--
+
+--===============1==--
+.
+`
+
+	s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
+		t.Fatal("This should not be called")
+	})
+	defer s.Close()
+	defer c.Close()
+	writeAndReadUntilLine(t, email, c, scanner, "554 5.0.0 Error: transaction failed, blame it on the weather: multipart message nested too deep")
+}
+
 type smtpHandlerFunc func(http.ResponseWriter, *http.Request)
 
 func newTestSMTPServer(t *testing.T, handler smtpHandlerFunc) (s *smtp.Server, c net.Conn, conf *Config, scanner *bufio.Scanner) {