The Body() method now correctly handles: - Plain text/plain and text/html emails (not just multipart) - All multipart types (mixed, related, etc., not just alternative) - Falls back to HTML if text/plain is not available - Adds debug logging for body length and content type Additionally, when sending to multiple targets, query parameters are now merged instead of replaced, preserving service-specific parameters like Mattermost's username, icon, and channel configuration. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
92 lines
2.3 KiB
Go
92 lines
2.3 KiB
Go
package smtp2shoutrrr
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"log/slog"
|
|
"mime"
|
|
"mime/multipart"
|
|
"net/mail"
|
|
"strings"
|
|
)
|
|
|
|
type ReceivedEmail struct {
|
|
Recipients []string
|
|
Msg *mail.Message
|
|
body string
|
|
}
|
|
|
|
func (re *ReceivedEmail) Body() (string, error) {
|
|
if re.body == "" {
|
|
contentType := re.Msg.Header.Get("Content-Type")
|
|
|
|
if contentType == "" {
|
|
body, err := io.ReadAll(re.Msg.Body)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to read email body: %w", err)
|
|
}
|
|
re.body = string(body)
|
|
} else {
|
|
mediaType, params, err := mime.ParseMediaType(contentType)
|
|
if err != nil {
|
|
log.Fatalf("Failed to parse Content-Type: %v", err)
|
|
}
|
|
|
|
if strings.HasPrefix(mediaType, "multipart/") {
|
|
// Handle any multipart type (alternative, mixed, related, etc.)
|
|
mr := multipart.NewReader(re.Msg.Body, params["boundary"])
|
|
var htmlBody string
|
|
|
|
for {
|
|
part, err := mr.NextPart()
|
|
if err != nil {
|
|
break
|
|
}
|
|
|
|
partContentType := part.Header.Get("Content-Type")
|
|
slog.Debug("Processing email part", slog.String("content_type", partContentType))
|
|
|
|
// Prefer text/plain, but keep HTML as fallback
|
|
if strings.HasPrefix(partContentType, "text/plain") {
|
|
body := new(bytes.Buffer)
|
|
_, err = body.ReadFrom(part)
|
|
_ = part.Close()
|
|
if err != nil {
|
|
slog.Error("Failed to read part body", slog.String("err", err.Error()))
|
|
return "", fmt.Errorf("failed to read part body: %w", err)
|
|
}
|
|
|
|
re.body = body.String()
|
|
break // Found text/plain, use it
|
|
} else if strings.HasPrefix(partContentType, "text/html") && htmlBody == "" {
|
|
// Store HTML as fallback if no text/plain found
|
|
body := new(bytes.Buffer)
|
|
_, err = body.ReadFrom(part)
|
|
_ = part.Close()
|
|
if err == nil {
|
|
htmlBody = body.String()
|
|
}
|
|
} else {
|
|
_ = part.Close()
|
|
}
|
|
}
|
|
|
|
// If no text/plain was found, use HTML
|
|
if re.body == "" && htmlBody != "" {
|
|
re.body = htmlBody
|
|
}
|
|
} else if strings.HasPrefix(mediaType, "text/") {
|
|
// Handle non-multipart text content (text/plain, text/html, etc.)
|
|
body, err := io.ReadAll(re.Msg.Body)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to read email body: %w", err)
|
|
}
|
|
re.body = string(body)
|
|
}
|
|
}
|
|
}
|
|
|
|
return re.body, nil
|
|
}
|