Skip to content

Send ​

Saves a message as a draft or sends it, from the mailbox of your choice and with the signature of your choice. Goes after “Compose” — or is filled in by hand.

Send is the node that makes a message exist: saved as a draft in a mailbox, or sent for real. It is the only mail node whose effect is send, so it is the one held back in test runs, the one the organisation's node policy can restrict, and the one the send kill switch stops.

Send usually follows Compose or Compose (AI), which prepare the message without touching the mailbox. It can also be filled in by hand when there is nothing to write: a one-line acknowledgement, a templated reminder.

At a glance ​

  • Type: mail.send · version 1
  • Category: Actions
  • Kind: Step — one stage of a run
  • Effect: Sends an email (send) — sends an email; described instead of performed during a test run, stopped by the send kill switch
  • Needs a carrier email: No
  • Connection: None
  • Inputs: main
  • Outputs: main

Parameters ​

message ​

Message to send — The expression carrying a message composed upstream — {{ data.compose_1.message }}. Leave empty to compose the whole message below. Fields filled in here always win over the incoming message.

  • Type: Text (string)
  • Required: No
  • Default: "" (empty)
  • 200000 characters at most
  • Expressions: {{ }} accepted

mode ​

What to do with it — A draft is reviewed before it leaves (it shows up in the morning Review and in the real mailbox). Sending goes out for real, under the organisation safeguards.

  • Type: One choice (options)
  • Required: Yes
  • Default: draft
  • Options:
    • draft — Save as draft: Nothing leaves. The message waits for a human to read it.
    • send — Send: The message goes out for real, under the organisation’s kill switch and hourly cap. During a test run, nothing leaves.

mailbox ​

Sending mailbox — Which mailbox the message leaves from. Empty: the one of the triggering email. Required when the trigger is not an email (schedule, webhook, polling): there is then no mailbox to reuse, and publishing refuses it. A workflow triggered on one mailbox can write from another — that is what lets you answer from the service mailbox.

  • Type: Remote resource (resourceLocator)
  • Required: Depends on the trigger — yes: schedule, webhook or integration event
  • Ways to choose: pick from a list, type an ID (mail.mailbox)

to ​

To — Comma-separated addresses. Empty: those of the incoming message.

  • Type: Text (string)
  • Required: No
  • Default: "" (empty)
  • 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 — Empty: the one of the incoming message.

  • Type: Text (string)
  • Required: No
  • Default: "" (empty)
  • 500 characters at most
  • Expressions: {{ }} accepted

bodyText ​

Body — Empty: the body of the incoming message.

  • Type: Long text (text)
  • Required: No
  • Default: "" (empty)
  • 200000 characters at most
  • Expressions: {{ }} accepted

threading ​

Thread

  • Type: One choice (options)
  • Required: Yes
  • Default: inherit
  • Options:
    • inherit — As the message says: The thread comes from the incoming message, which already knows whether it replies.
    • reply — Reply in thread: The reply is attached to the triggering email and stays in its conversation. Refused at publication when the trigger is not an email: there is no thread.
    • new — New thread: The message starts on its own, outside the original conversation.
  • Needs a carrier email for: reply

attachments ​

Attachments

  • Type: One choice (options)
  • Required: Yes
  • Default: fromMessage
  • Options:
    • fromMessage — Those of the message: The attachments the “Compose” node selected.
    • none — None: The message leaves with none.
    • fromTrigger — Those of the run: All of them: those of the incoming email, then the files added by earlier steps.
    • fromTriggerFiltered — Those of the run, filtered: Only those whose type or name matches.

attachmentMime ​

File type — Exact MIME type (application/pdf) or a whole family (image/*). Left empty, no attachment is kept.

  • Type: Text (string)
  • Required: No
  • Default: "" (empty)
  • 200 characters at most
  • Example: application/pdf
  • Shown when: attachments is fromTriggerFiltered
  • Expressions: {{ }} accepted

attachmentNamePattern ​

File name — Simple pattern: * matches anything, ? one character (*.pdf, invoice-*). Case is ignored.

  • Type: Text (string)
  • Required: No
  • Default: "" (empty)
  • 200 characters at most
  • Example: *.pdf
  • Shown when: attachments is fromTriggerFiltered
  • Expressions: {{ }} accepted

signature ​

Signature — A workflow does not sign on its own: with no explicit choice, the message leaves unsigned.

  • Type: One choice (options)
  • Required: Yes
  • Default: none
  • Options:
    • none — None
    • member — That of the running member: Their default signature, the one the composer uses.
    • person — That of a chosen person: Write from the service mailbox and sign with the name of the person in charge.
    • named — A specific signature

signatureMemberId ​

Person

  • Type: Remote resource (resourceLocator)
  • Required: No
  • Ways to choose: pick from a list, type an ID (org.member)
  • Shown when: signature is person

signatureId ​

Signature

  • Type: Remote resource (resourceLocator)
  • Required: No
  • Ways to choose: pick from a list, type an ID (mail.signature)
  • Shown when: signature is named

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>.opId }} — string. The identifier of the recorded outbound operation. Stable when the step is retried. In a test run it is simulated:mail.draft or simulated:mail.send.
  • {{ data.<step>.simulated }} — boolean. true in a test run: nothing was saved or sent, the run detail describes what would have happened.
  • {{ data.<step>.mode }} — string. draft or send, as chosen in What to do with it.
  • {{ data.<step>.mailboxId }} — string. The identifier of the mailbox actually used. In a test run, the mailbox chosen on the node, or simulated when none was chosen.
  • {{ data.<step>.mailboxAddress }} — string. The address of the mailbox the message leaves from.
  • {{ data.<step>.messageId }} — string. The Message-ID header set on the message, stable when the step is retried. Useful to store in a table and find the message later. Absent in a test run.
  • {{ data.<step>.sentAt }} — string or null. In send mode, the ISO 8601 instant the sending chain accepted the message (not the instant the provider delivered it). null for a draft and in a test run.
  • {{ data.<step>.signature }} — string. The name of the signature added. Absent when the message is unsigned or in a test run.
  • {{ data.<step>.message }} — object { to, cc, bcc, subject, inReplyToMessageId, attachments }. What was actually recorded, after combining the incoming message and the node fields: to, cc and bcc as arrays (possibly empty), subject, inReplyToMessageId (null outside a thread) and attachments as an array of { position, filename, mime, size }. The body is not repeated.
  • {{ data.<step>.summary }} — string. A one-line description for the run detail (draft saved or message sent, from which mailbox, with which files), in the language of the member running the workflow. summaryKey and summaryParams hold the same sentence as a translation key and its parameters.

Example ​

A Compose (AI) node named Reply wrote an answer. Add a Send node:

message: "{{ data.reply.message }}"
mode: draft
mailbox: (empty)
threading: inherit
attachments: fromMessage
signature: member

The draft is saved in the mailbox of the triggering email, in the thread (Compose (AI) replied in the thread), with the default signature of the member running the workflow. The step data reads:

json
{
  "opId": "6f1c…",
  "simulated": false,
  "mode": "draft",
  "mailboxId": "2b7e…",
  "mailboxAddress": "sales@example.com",
  "messageId": "<…@example.com>",
  "sentAt": null,
  "signature": "Default",
  "message": {
    "to": ["buyer@example.com"],
    "cc": [],
    "bcc": [],
    "subject": "Re: Price for 200 units",
    "inReplyToMessageId": "<CAF123@mail.example.com>",
    "attachments": []
  },
  "summary": "…"
}

Where the message comes from ​

Two sources combine, and the rule fits in one sentence: the incoming message gives the base, and any field filled in on Send wins over it.

  • Message to send takes an expression pointing at a message prepared upstream, such as {{ data.reply.message }}. The expression must point at the message object itself; anything else (plain text, another key) fails with node_invalid_param and a hint to point it at the output of a Compose node.
  • To, Cc, Bcc, Subject, Body replace the matching part of the incoming message when they are filled in. Left empty, the incoming value is kept. Leave Message to send empty to write the whole message on the node.

Because an empty field means "keep the incoming value", a copy cannot be removed from Send: prepare the message without it instead.

Draft or send ​

  • Save as draft: nothing leaves. The draft appears in the real mailbox and in the Review, waiting for a human. Drafts are never held by the send kill switch or the hourly cap.
  • Send: the message leaves for real, through the sending chain. If sending is suspended (the instance-wide SEND_ENABLED setting or the organisation's own switch), the message is held, not lost, and leaves when sending reopens. The organisation's hourly cap (SEND_MAX_PER_HOUR, 100 per hour by default) delays messages beyond it rather than dropping them. See Review and approvals and Environment variables.

Sending mailbox ​

The mailbox is chosen in this order: the Sending mailbox field, then the sender proposed by the incoming message, then the mailbox of the triggering email. A workflow triggered on one mailbox can therefore answer from another, such as a shared service address. Only mailboxes of the member running the workflow can be used.

When the trigger brings no email (schedule, webhook, polling), the field is required and publishing refuses an empty value. If no mailbox can be found at run time, the step fails with mail.mailbox_required.

Thread ​

  • As the message says (default): the thread comes from the incoming message. Compose and Compose (AI) already decided whether they reply.
  • Reply in thread: forces In-Reply-To/References from the triggering email. It requires a triggering email: publishing refuses it otherwise, and at run time it fails with mail.reply_without_email. If the triggering email has no usable Message-ID, the message leaves outside the thread rather than being lost.
  • New thread: the message starts on its own.

Attachments and signature ​

Attachments default to Those of the message, the files the Compose node selected. Those of the run and Those of the run, filtered pick from the triggering email's attachments and the files added by earlier steps; inline images are never included. The composed message, attachments included, must stay under the instance size limit (SEND_MAX_BYTES, 25 MB by default), otherwise the step fails with node_invalid_param.

A workflow does not sign on its own: with Signature set to None, the message leaves unsigned. That of the running member uses their default signature; That of a chosen person signs with someone else's default signature (write from the service mailbox, sign as the person in charge); A specific signature uses one picked from the list.

Tips ​

  • Never sent twice. Send records an outbound operation keyed on the step. If the step is retried (worker crash, transient error), it finds the same operation and returns the same opId: the message is not sent again.
  • Replaying a run resends. Replaying a whole run from the runs list starts a new run with new keys, so its Send steps send again. See Error handling.
  • Test runs. In test runs nothing is written: no draft, no message, no outbound operation. The run detail says what would have been saved or sent. A test does not check the mailbox or the signature, except for a missing mailbox (mail.mailbox_required), which fails in the test as it would in production.
  • Permanent refusals. mail.mailbox_forbidden (the mailbox does not exist or is not the member's), mail.mailbox_cannot_send (disconnected, in error or without credentials: reconnect it) and mail.signature_not_found are permanent and are not retried. Choosing That of a chosen person or A specific signature without picking one gives node_invalid_param.
  • No recipient. If neither the incoming message nor To provides an address, the step fails with node_invalid_param.