larksuite/cli · error
multipart part %s missing boundary
Error message
multipart part %s missing boundary
What it means
During RFC822/MIME parsing of a mail draft, parsePart encountered a part whose media type starts with 'multipart/' but whose Content-Type parameters contain no 'boundary' parameter. Without a boundary the multipart body cannot be split into child parts, so parsing aborts. This guards against malformed MIME structures.
Source
Thrown at shortcuts/mail/draft/parse.go:167
} else {
part.MediaParams["charset"] = "UTF-8"
}
if disp := headerValue(headers, "Content-Disposition"); disp != "" {
dispType, params, err := mime.ParseMediaType(disp)
if err == nil {
part.ContentDisposition = strings.ToLower(dispType)
part.ContentDispositionArg = lowerCaseKeys(params)
}
// On parse error, silently ignore the disposition. The original
// header is preserved in part.Headers for serialization.
}
part.ContentID = strings.Trim(strings.TrimSpace(headerValue(headers, "Content-ID")), "<>")
part.TransferEncoding = strings.ToLower(strings.TrimSpace(headerValue(headers, "Content-Transfer-Encoding")))
if strings.HasPrefix(part.MediaType, "multipart/") {
boundary := part.MediaParams["boundary"]
if boundary == "" {
return nil, fmt.Errorf("multipart part %s missing boundary", partID)
}
children, preamble, epilogue, err := parseMultipartChildren(body, boundary, partID, depth)
if err != nil {
return nil, err
}
if len(children) == 0 {
// Boundary declared but never found in the body. Reclassify as
// text rather than returning an empty multipart with no children
// (following mail-parser's approach per Postel's law).
part.MediaType = "text/plain"
part.MediaParams = map[string]string{"charset": "UTF-8"}
part.Body = body
part.EncodingProblem = true
return part, nil
}
part.Children = children
part.Preamble = preamble
part.Epilogue = epilogueView on GitHub (pinned to 7fd6ef3c07)
Solutions
- Inspect the raw MIME source and ensure every multipart/* Content-Type header includes a boundary parameter, e.g. Content-Type: multipart/mixed; boundary="===BOUNDARY==="
- If you generate MIME yourself, use a MIME library (Go: mime/multipart with Writer.SetBoundary) instead of concatenating strings.
- If the MIME comes from a Lark draft, re-fetch the raw content and check it was not truncated or re-encoded in transit.
- Wrap the parse call and treat this as malformed input: reject or repair the payload before parsing.
Example fix
// before Content-Type: multipart/mixed; // after Content-Type: multipart/mixed; boundary="----=_Part_0_123456"
Defensive patterns
Strategy: validation
Validate before calling
func hasBoundary(ct string) bool {
_, params, _ := mime.ParseMediaType(ct)
if !strings.HasPrefix(strings.ToLower(ct), "multipart/") { return true }
return params["boundary"] != ""
}
// check before parsing: if !hasBoundary(contentType) { repair or reject } Type guard
func isParseableMultipart(ct string) bool {
if !strings.HasPrefix(strings.ToLower(ct), "multipart/") { return true }
_, params, err := mime.ParseMediaType(ct)
return err == nil && params["boundary"] != ""
} Try / catch
part, err := parseRootPart(raw)
if err != nil {
var e *errs.TypedError
if errors.As(err, &e) { log.Printf("mime parse failed: %s", e.Message) }
return fmt.Errorf("malformed draft MIME: %w", err)
} Prevention
- Always generate multipart bodies with mime/multipart and an explicit SetBoundary.
- Round-trip validate generated MIME by re-parsing it before sending.
- Never strip Content-Type parameters when forwarding raw email bodies.
- Check for truncation when MIME passes through size-limited channels.
When it happens
Trigger: Parsing a draft whose raw MIME body contains a 'multipart/*' Content-Type header (e.g. multipart/mixed, multipart/alternative) with no 'boundary=' parameter, typically produced when parsing parts returned by parseRootPart or recursively via parseMultipartChildren.
Common situations: Feeding hand-crafted or third-party-generated MIME into the draft parser; upstream tools that strip Content-Type parameters; truncated or rewritten emails that lost the boundary parameter.
Related errors
- draft has coupled text/plain summary and text/html body; edi
- draft has both text/plain and text/html body parts, but they
- draft has no unique primary body part; use replace_body with
- draft has no primary %s body part
- body part %s not found
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/6d46d80c1037d303.
Report an issue: GitHub.