Skip to content

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 profile
    • fr — French
    • en — English

toneOverride ​

Force the tone

  • Type: One choice (options)
  • Required: No
  • Default: "" (empty)
  • Options:
    • — From the profile
    • formal — Formal
    • neutral — Neutral
    • casual — 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, cc and bcc are arrays of addresses; cc and bcc are absent when empty; inReplyToMessageId is 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 is Re: 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. true when the body is longer than the preview.
  • {{ data.<step>.style }} — object { formality, tone, language, overridden }. The style actually applied: formality is tu or vous, tone is formal, neutral or casual, language is a language code or null (the reply follows the email), overridden lists what the node forced (tone, language).
  • {{ data.<step>.profileApplied }} — object keyed by address. For each resolved recipient: formality, tone, language (or null), signature (whether the profile has one) and notes (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: true

Then add a Send node with:

message: "{{ data.reply.message }}"
mode: draft

The 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_email before 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-ID header, 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 gives node_invalid_param, as does an empty subject without a triggering email. See Error handling.