larksuite/cli · error
draft has coupled text/plain summary and text/html body; edi
Error message
draft has coupled text/plain summary and text/html body; edit them together with set_body
What it means
When a draft's text/plain part is detected as a plain-text summary of the text/html body (a coupled summary+html pair), replaceBody refuses to edit either part in isolation, because editing one would desynchronize it from the other. The draft must be edited with set_body, which updates both parts together.
Source
Thrown at shortcuts/mail/draft/patch.go:250
return append([]Address{}, snapshot.Cc...), "Cc"
case "bcc":
return append([]Address{}, snapshot.Bcc...), "Bcc"
default:
return nil, ""
}
}
func setRecipientField(snapshot *DraftSnapshot, headerName string, addrs []Address) {
if len(addrs) == 0 {
removeHeader(&snapshot.Headers, headerName)
return
}
upsertHeader(&snapshot.Headers, headerName, formatAddressList(addrs))
}
func replaceBody(snapshot *DraftSnapshot, bodyKind, value string, options PatchOptions) error {
if hasCoupledBodySummary(snapshot) {
return fmt.Errorf("draft has coupled text/plain summary and text/html body; edit them together with set_body")
}
part, err := bodyPartForKind(snapshot, bodyKind, options.RewriteEntireDraft)
if err != nil {
return err
}
part.Body = []byte(value)
part.Dirty = true
return nil
}
func appendBody(snapshot *DraftSnapshot, bodyKind, value string, options PatchOptions) error {
if hasCoupledBodySummary(snapshot) {
return fmt.Errorf("draft has coupled text/plain summary and text/html body; edit them together with set_body")
}
part, err := bodyPartForKind(snapshot, bodyKind, options.RewriteEntireDraft)
if err != nil {
return err
}View on GitHub (pinned to 7fd6ef3c07)
Solutions
- Use the set_body op with the full HTML content instead of replace_body — it updates the summary and HTML together
- Use replace_body only on drafts where text/plain is not a coupled summary (check the draft's structure first)
- If you truly need independent part editing, construct the draft with a non-coupled body structure
Example fix
// before
applyOp(Op{Op: "replace_body", BodyKind: "text/html", Value: html})
// after
applyOp(Op{Op: "set_body", Value: html}) // updates text/plain summary + html together Defensive patterns
Strategy: validation
Validate before calling
if draftHasCoupledSummary(snap) {
// use set_body instead of replace_body
} Try / catch
if err := applyOp(op); err != nil && strings.Contains(err.Error(), "coupled text/plain summary") {
// retry with Op{Op: "set_body", Value: html}
} Prevention
- Prefer set_body for drafts that may contain HTML + plain-text summary
- Inspect the draft's body structure before choosing replace_body vs set_body
- Only use replace_body on drafts you know have a single independent body part
When it happens
Trigger: A replace_body patch op (via applyOp) with any body_kind on a snapshot where hasCoupledBodySummary(snapshot) is true — typically drafts created by the API with both text/plain summary and text/html body.
Common situations: Scripts that worked on text-only drafts then applied replace_body to an HTML draft with a plain-text summary; trying to tweak the text/plain 'summary' directly.
Related errors
- draft has both text/plain and text/html body parts, but they
- draft has no unique primary body part; use replace_body with
- multipart part %s missing boundary
- remove_attachment: %w
- replace_inline: %w
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/537b693b40854cc5.
Report an issue: GitHub.