larksuite/cli · error
draft main body is text/html and text/plain is only its summ
Error message
draft main body is text/html and text/plain is only its summary; set_body requires HTML input for this draft
What it means
For drafts whose text/plain part is only a summary of the text/html body, set_body requires the new value to be HTML, because it writes the value into the HTML part and regenerates the plain-text summary. A non-HTML value would corrupt the coupled pair, so it is rejected up front.
Source
Thrown at shortcuts/mail/draft/patch.go:407
if snapshot == nil {
return false
}
textPart := findPart(snapshot.Body, snapshot.PrimaryTextPartID)
htmlPart := findPart(snapshot.Body, snapshot.PrimaryHTMLPartID)
if textPart == nil || htmlPart == nil {
return false
}
return strings.EqualFold(textPart.MediaType, "text/plain") && strings.EqualFold(htmlPart.MediaType, "text/html")
}
func coupledBodySetBodyInputError(snapshot *DraftSnapshot, value string) error {
if !hasCoupledBodySummary(snapshot) {
return nil
}
if bodyLooksLikeHTML(value) {
return nil
}
return fmt.Errorf("draft main body is text/html and text/plain is only its summary; set_body requires HTML input for this draft")
}
func bodyPartForKind(snapshot *DraftSnapshot, bodyKind string, allowRewrite bool) (*Part, error) {
var partID string
switch strings.ToLower(bodyKind) {
case "text/plain":
partID = snapshot.PrimaryTextPartID
case "text/html":
partID = snapshot.PrimaryHTMLPartID
default:
return nil, fmt.Errorf("unsupported body kind %q", bodyKind)
}
if partID == "" {
if !allowRewrite {
return nil, fmt.Errorf("draft has no primary %s body part", bodyKind)
}
return ensureBodyPart(snapshot, bodyKind)
}View on GitHub (pinned to 7fd6ef3c07)
Solutions
- Convert the value to HTML (wrap in <p>, escape entities) before calling set_body
- Detect the coupled structure first and branch: HTML value for coupled drafts, plain text otherwise
- Use replace_body with body_kind text/plain only if you intentionally want to break the coupling pattern (not recommended)
Example fix
// before
applyOp(Op{Op: "set_body", Value: "hello world"}) // coupled draft requires HTML
// after
applyOp(Op{Op: "set_body", Value: "<p>hello world</p>"}) Defensive patterns
Strategy: validation
Validate before calling
if draftHasCoupledSummary(snap) && !bodyLooksLikeHTML(value) {
value = "<p>" + html.EscapeString(value) + "</p>"
} Type guard
func isHTML(s string) bool { return strings.Contains(s, "<p>") || strings.Contains(s, "<div>") || strings.Contains(s, "<br") } Try / catch
if err := applyOp(op); err != nil && strings.Contains(err.Error(), "requires HTML input") {
// wrap value in HTML and retry
} Prevention
- Always produce HTML output when editing HTML drafts
- Detect the coupled summary structure before set_body and branch on it
- Escape plain text into HTML (html.EscapeString + <p> wrapper) as a default path
When it happens
Trigger: A set_body patch op on a coupled summary+html draft where bodyLooksLikeHTML(value) is false — i.e. the supplied value looks like plain text rather than HTML.
Common situations: Scripts that set plain-text bodies reused against HTML drafts; user input pasted as plain text; templates producing plain-text output applied to an HTML-format draft.
Related errors
- recipient field must be one of to/cc/bcc
- recipient address is empty
- unsupported body kind %q
- set_calendar: event_start must be a valid ISO 8601 timestamp
- set_calendar: event_end must be a valid ISO 8601 timestamp
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/6911986439ce8c78.
Report an issue: GitHub.