English
Compose (AI)
Has the model write a message from your instructions and the recipient profile (formality, tone, language, signature). Does not send it: add “Send” after it to save a draft or send it.
Compose (AI) has the model write an email from your instructions, adapted to the recipient: formal or informal address, tone, language, context notes and signature come from the recipient's profile in the address book. The same workflow can then write informally to a long-standing customer and formally to a prospect, without you writing either rule.
The node only produces the message. It saves no draft and sends nothing. Link Send after it to save the message as a draft or send it, and put whatever you need in between: an Approval, a condition, a table write. When the text is a fixed template rather than something to write, use Compose instead: it costs no model call.
At a glance
- Type:
ai.compose· version 1 - Category: AI
- Kind: Step — one stage of a run
- Effect: No external effect (
none) — nothing is written outside Mankomail; safe to replay - Needs a carrier email: No
- Connection: None
- Inputs:
main - Outputs:
main - Service ports:
model(llm.model, optional)
Parameters
instructions
What the email should say — The instructions given to the model, not the final text. Accepts {{ }} expressions: {{ data.extract_1.number }}, {{ email.from.name }}.
- Type: Long text (
text) - Required: Yes
- Default:
""(empty) - 10000 characters at most
- Example:
Acknowledge the request and announce a priced answer within 48 hours. - Expressions:
{{ }}accepted
to
To — Comma-separated addresses. Defaults to the sender of the triggering email — and to nothing when the trigger brings no email.
- Type: Text (
string) - Required: Yes
- Default: Depends on the trigger —
{{ email.from.email }}(email or manual trigger) ;""(empty) (called by another workflow) ;""(empty) (schedule, webhook or integration event) - 2000 characters at most
- Expressions:
{{ }}accepted
cc
Cc
- Type: Text (
string) - Required: No
- Default:
""(empty) - 2000 characters at most
- Expressions:
{{ }}accepted
bcc
Bcc
- Type: Text (
string) - Required: No
- Default:
""(empty) - 2000 characters at most
- Expressions:
{{ }}accepted
subject
Subject
- Type: Text (
string) - Required: Yes
- Default: Depends on the trigger —
Re: {{ email.subject }}(email or manual trigger) ;""(empty) (called by another workflow) ;""(empty) (schedule, webhook or integration event) - 500 characters at most
- Expressions:
{{ }}accepted
replyInThread
Reply in thread — The reply is attached to the triggering email and stays in the conversation. Ticked by default under an email trigger, unticked under a schedule, a webhook or a call — there is then no thread, and publishing refuses it if it stays ticked.
- Type: Yes / no (
boolean) - Required: No
- Default: Depends on the trigger —
true(email or manual trigger) ;false(called by another workflow) ;false(schedule, webhook or integration event) - Needs a carrier email for:
true
includeThreadContext
Give the email to the model — The body of the triggering email is provided as context, as delimited data. The full thread is not available yet. Without a triggering email there is nothing to give: the model works from your instructions only.
- Type: Yes / no (
boolean) - Required: No
- Default: Depends on the trigger —
true(email or manual trigger) ;false(called by another workflow) ;false(schedule, webhook or integration event)
useRecipientProfiles
Apply recipient profiles — The recipient’s formality, tone, language, notes and signature are injected into the model instructions.
- Type: Yes / no (
boolean) - Required: No
- Default:
true
languageOverride
Force the language
- Type: One choice (
options) - Required: No
- Default:
""(empty) - Options:
— From the profilefr— Frenchen— English
toneOverride
Force the tone
- Type: One choice (
options) - Required: No
- Default:
""(empty) - Options:
— From the profileformal— Formalneutral— Neutralcasual— Casual
Outputs
main
Data produced
What this node adds to the run data, and how to read it in an expression. <step> stands for the step key: the node name turned into an identifier (see Data and expressions).
{{ data.<step>.message }}—object { to, cc?, bcc?, subject, bodyText?, inReplyToMessageId? }. The message written, ready for a Send node. Point Send's Message to send field at this expression.to,ccandbccare arrays of addresses;ccandbccare absent when empty;inReplyToMessageIdis present only when the message replies in the thread.{{ data.<step>.message.bodyText }}—string. The full body written by the model, with the recipient's signature appended when the profile has one.{{ data.<step>.message.subject }}—string. The subject. When the Subject field renders empty and the run has a triggering email, it isRe:followed by that email's subject.{{ data.<step>.bodyPreview }}—string. The first 280 characters of the body, for display in the run detail.{{ data.<step>.bodyTruncated }}—boolean.truewhen the body is longer than the preview.{{ data.<step>.style }}—object { formality, tone, language, overridden }. The style actually applied:formalityistuorvous,toneisformal,neutralorcasual,languageis a language code ornull(the reply follows the email),overriddenlists what the node forced (tone,language).{{ data.<step>.profileApplied }}—object keyed by address. For each resolved recipient:formality,tone,language(ornull),signature(whether the profile has one) andnotes(the number of notes). The note texts themselves are not copied.
Example
Acknowledge every quote request with a reply in the thread. After a Categorize branch cat:Quote, add a Compose (AI) node named Reply:
instructions: "Thank them for the request, confirm we received it and announce a priced answer within 48 hours. Mention the product they asked about."
to: "{{ email.from.email }}"
subject: "Re: {{ email.subject }}"
replyInThread: true
includeThreadContext: true
useRecipientProfiles: trueThen add a Send node with:
message: "{{ data.reply.message }}"
mode: draftThe step data of Reply holds the message:
json
{
"message": {
"to": ["buyer@example.com"],
"subject": "Re: Price for 200 units",
"bodyText": "Hello Ms Martin,\n\nThank you for your request…",
"inReplyToMessageId": "<CAF123@mail.example.com>"
},
"bodyPreview": "Hello Ms Martin,\n\nThank you for your request…",
"bodyTruncated": false,
"style": { "formality": "vous", "tone": "neutral", "language": null, "overridden": [] },
"profileApplied": { "buyer@example.com": { "formality": null, "tone": null, "language": null, "signature": false, "notes": 0 } }
}Send reads the message, saves it as a draft in the mailbox of the triggering email, and the draft shows up for review.
Output language
Unless something fixes it, the model writes in the language of the triggering email, whatever the language of your instructions or of the interface: instructions written in English produce a French reply to a French email. Two settings fix the language instead: a language set on the main recipient's profile, or Force the language on the node (French or English). Force the language wins over the profile.
Recipient profiles
With Apply recipient profiles ticked (the default), the node resolves the profiles of every address in To and Cc. The first address in To sets the style: formal or informal address (formal when nothing is set), tone (neutral when nothing is set), language, display name and signature. The notes of every resolved recipient are added as context. Force the tone overrides the profile's tone without disabling the rest. Untick Apply recipient profiles to write without any profile.
When the main recipient's profile has a signature, the model is told to end with it unchanged, and the node appends it itself if the model forgot it.
What the model sees
Your instructions are sent in a user message, never in the system message. With Give the email to the model ticked and a triggering email present, the email's subject and text body are added in a separate, delimited block, with an explicit instruction to treat it as data and ignore any instruction it contains. Only the triggering email is given, not the rest of the thread. Without a triggering email (schedule, webhook, call), the model works from your instructions alone.
How the model is chosen
The node has a model service port. Leave it empty and the call uses the default model the administrator set for the Compose use on the Connections page (Artificial intelligence section) (or the instance default). Link a provider node such as Anthropic (Claude) or Mistral AI to the model port to write with that provider.
Tips
- Runs without an email. Under a schedule, a webhook or a polling trigger, To and Subject start empty and must be filled in, and Reply in thread starts unticked. If Reply in thread stays ticked, publishing refuses it; at run time the node fails with
mail.reply_without_emailbefore calling the model, so you are not charged for a reply that cannot be delivered. - No Message-ID. If the triggering email has no usable
Message-IDheader, the message is written outside the thread rather than lost. - Two signatures. The profile signature is part of the body. If Send also adds a signature, the message carries both: pick one or the other.
- Inserted values are data. Instructions can insert third-party content (
{{ data.<step>.attachments.0.text }}); the model is told that inserted values are data, not instructions. - Test runs. In test runs the model is really called, so you can read the real reply; nothing is saved or sent, since the node has no effect and Send is simulated. If the instance has no usable AI provider, the body is a fixed placeholder and the run detail says the AI was skipped.
- Errors. Empty instructions give
node_nothing_to_do; no recipient left after rendering To givesnode_invalid_param, as does an empty subject without a triggering email. See Error handling.