MCPcopy Create free account
hub / github.com/Mnexa-AI/e2a / ComposeMessageWithAttachments

Function ComposeMessageWithAttachments

internal/outbound/compose.go:129–222  ·  view source on GitHub ↗

ComposeMessageWithAttachments builds an RFC 2822 multipart/mixed email with attachments. If no attachments are provided, falls back to ComposeMultipartMessage. See ComposeMessage for replyToMsgID / references semantics.

(from string, to []string, cc []string, subject, textBody, htmlBody, replyToMsgID string, references []string, fromDomain, replyTo, conversationID string, attachments []Attachment)

Source from the content-addressed store, hash-verified

127// If no attachments are provided, falls back to ComposeMultipartMessage.
128// See ComposeMessage for replyToMsgID / references semantics.
129func ComposeMessageWithAttachments(from string, to []string, cc []string, subject, textBody, htmlBody, replyToMsgID string, references []string, fromDomain, replyTo, conversationID string, attachments []Attachment) ([]byte, error) {
130 // Defense-in-depth header-injection guard: reject any attachment
131 // whose user-supplied Filename or ContentType contains CR or LF.
132 // fmt.Sprintf("%q", ...) escapes Filename safely, but ContentType
133 // is written via "%s" and would inject extra MIME headers if it
134 // contained "\r\n" — so reject before composing.
135 for _, att := range attachments {
136 if strings.ContainsAny(att.Filename, "\r\n") {
137 return nil, fmt.Errorf("attachment filename contains CR/LF: header injection refused")
138 }
139 if strings.ContainsAny(att.ContentType, "\r\n") {
140 return nil, fmt.Errorf("attachment content_type contains CR/LF: header injection refused")
141 }
142 }
143 if len(attachments) == 0 {
144 return ComposeMultipartMessage(from, to, cc, subject, textBody, htmlBody, replyToMsgID, references, fromDomain, replyTo, conversationID)
145 }
146
147 mixedBoundary := generateBoundary()
148
149 var buf strings.Builder
150 writeHeader := headerWriter(&buf)
151
152 writeHeader("From", from)
153 if len(to) > 0 {
154 writeHeader("To", strings.Join(to, ", "))
155 }
156 if len(cc) > 0 {
157 writeHeader("Cc", strings.Join(cc, ", "))
158 }
159 if replyTo != "" {
160 writeHeader("Reply-To", replyTo)
161 }
162 writeHeader("Subject", mime.QEncoding.Encode("utf-8", subject))
163 writeHeader("Date", time.Now().UTC().Format(time.RFC1123Z))
164 writeHeader("MIME-Version", "1.0")
165 writeHeader("Content-Type", fmt.Sprintf("multipart/mixed; boundary=%q", mixedBoundary))
166
167 writeThreadingHeaders(writeHeader, replyToMsgID, references)
168 if conversationID != "" {
169 writeHeader("X-E2A-Conversation-ID", conversationID)
170 }
171
172 buf.WriteString("\r\n")
173
174 // Body part
175 if htmlBody != "" {
176 altBoundary := generateBoundary()
177 buf.WriteString("--" + mixedBoundary + "\r\n")
178 buf.WriteString(fmt.Sprintf("Content-Type: multipart/alternative; boundary=%q\r\n\r\n", altBoundary))
179
180 buf.WriteString("--" + altBoundary + "\r\n")
181 buf.WriteString("Content-Type: text/plain; charset=utf-8\r\n\r\n")
182 buf.WriteString(textBody)
183 buf.WriteString("\r\n")
184
185 buf.WriteString("--" + altBoundary + "\r\n")
186 buf.WriteString("Content-Type: text/html; charset=utf-8\r\n\r\n")

Callers 2

ComposeMIMEFunction · 0.92
SendMethod · 0.85

Calls 5

ComposeMultipartMessageFunction · 0.85
generateBoundaryFunction · 0.85
headerWriterFunction · 0.85
writeThreadingHeadersFunction · 0.85
StringMethod · 0.45

Tested by

no test coverage detected