Skip to content

Triggers and conditions ​

A trigger is the node that starts a workflow. It sits on the left of the canvas, has no input, and never runs as a step: when it fires, it creates an run and hands it to the nodes linked after it. This page lists every trigger, what makes each one fire, what it brings to the run, and how the conditions of the email trigger are written.

How the rest of the graph runs is explained in Workflows.

The triggers at a glance ​

TypeName in the editorFires whenTriggering emailData it brings
trigger.emailEmail receiveda new email matching the conditions arrives in one of your mailboxesyesthe email, as email
trigger.webhookWebhook receivedan HTTP POST reaches the workflow's URLnothe JSON body, as data.webhook
trigger.calledCalled by a workflowanother workflow runs it with "Call a workflow"the caller's, if it has onethe caller's data, as data.input
trigger.scheduleSchedulethe clock reaches the next occurrencenodata.schedule
trigger.manualManual runyou run it on an emailyesthe email, as email
trigger.airtableAirtable row changeda check finds new or changed rowsnodata.airtable
trigger.notion_changesNotion entry changeda check finds new or changed entriesnodata.notion
trigger.notionNotion — eventNotion sends an event to the workflow's URLnodata.notion
trigger.mynotaryMyNotary — eventMyNotary sends an event to the workflow's URLnodata.mynotary
trigger.yousignYousign — eventYousign sends a signed event to the workflow's URLnodata.yousign

A trigger only fires once the workflow is published, except trigger.manual, which never fires on its own (you start it). Pausing a workflow stops email, schedule and polling triggers without unpublishing it.

Several triggers in one workflow ​

A workflow can carry several triggers. Each run remembers the trigger that started it, and only the branches linked after that trigger run. A workflow with an email trigger and a webhook trigger runs only its webhook branches when a call arrives.

  • trigger.email can be repeated as often as you like: each copy is another set of conditions. When one email satisfies two email triggers of the same workflow, the workflow still runs once on it, through the trigger whose node id comes first in alphabetical order.
  • trigger.webhook, trigger.called, trigger.mynotary, trigger.notion and trigger.yousign can appear only once per workflow (duplicate_trigger at publication). The URL and the ability to be called belong to the workflow, not to a node.
  • A workflow has a single URL. Use one URL-based trigger per workflow (trigger.webhook or one integration event trigger), not several of different types.
  • A disabled trigger does not fire. A workflow whose triggers are all disabled still publishes, with the warning all_triggers_disabled.

The triggering email ​

Some triggers bring an email to the run, others do not. This decides which nodes can be used.

What the triggers bringTriggersEffect on nodes that act on the email
Guaranteed — every run has an emailonly trigger.email and/or trigger.manualallowed
Inherited — the email of the calling workflow, if it has onetrigger.called, possibly with email or manual triggers, and none of the triggers belowallowed, with the warning carrier_email_inherited: they work when the caller was started by an email, and fail when it was started by a schedule or a webhook
Absent — at least one trigger starts runs with no emailany of trigger.webhook, trigger.schedule, trigger.airtable, trigger.notion_changes, trigger.notion, trigger.mynotary, trigger.yousignrefused at publication with carrier_email_required

All triggers count, disabled ones included: adding a schedule trigger to an email workflow, even disabled, makes the email absent for the whole workflow.

What "acts on the email" means:

  • Nodes that need the email: mail.move (Move) and mail.flag (Flag). They have nothing to act on without one.
  • Settings that need the email: "Reply in thread" on mail.compose (Compose) and ai.compose (Compose (AI)) — unticked by default when the email is absent; "Reply in thread" (reply) on mail.send (Send) — use "New thread" or the default instead; and the "A reply lands in the thread" early wake-up of flow.wait (Wait).

A disabled node is never checked: it does not run. To work on an email from a webhook or a schedule workflow, look it up from the data (for example by a reference stored in a table) rather than relying on a triggering email.

Email received (trigger.email) ​

trigger.email starts the workflow when a new email that matches its conditions arrives in one of your mailboxes. A new workflow starts with this trigger already in place.

Which mailboxes. Publishing arms the trigger on every mailbox you own that is not disconnected. A mailbox you connect later is armed automatically, without republishing. There is no per-trigger mailbox setting.

Which emails can reach a trigger. Before any condition is evaluated, an email must:

  • be new: delivered by synchronisation after the mailbox was connected. The history imported when you connect a mailbox never starts workflows, and neither does an old email that reappears (restored from the trash, relabelled);
  • not be in Sent, Spam, Trash or Drafts;
  • be in scope: not excluded by the organisation's or your own scope rules;
  • not be stopped by a guardrail: emails sent by Mankomail itself (for example a workflow's own reply coming back through sync), automatic replies (out-of-office and similar), mailing-list and newsletter emails, and emails from no-reply addresses never reach triggers.

Because of the guardrails, conditions such as signals.isMailingList equals true or signals.isAutoReply equals true never match in practice. The signal fields are still useful to exclude, for example signals.isAutoReply equals false.

Existing emails are not processed. Publishing a workflow does not apply it to emails already received: only emails that arrive afterwards are checked. To run a published workflow on an email you already have, use a manual run.

One run per email and per version. The same email delivered twice (push and poll, retries) starts a given published version only once.

When several workflows match the same email, your trigger policy decides, in your scope settings under "My trigger policy":

  • "Every workflow that matches" (default): each matching workflow gets its own run;
  • "The first match, and only that one": workflows are tried in priority order and only the first match runs. The order is the "Rank" in each workflow's "Settings" tab, from 0 to 9999, lowest first; workflows with no rank come after, in creation order.

The trigger also shows a "When several workflows match" setting (multiMatch: inherit, all, priority). The policy actually applied is the one from your settings, for every workflow.

Email trigger conditions ​

The conditions of trigger.email (parameter conditions) are a tree of groups and leaves:

  • a group is { "all": [ … ] } (every child must hold) or { "any": [ … ] } (at least one must hold), and groups can be nested;
  • a leaf is { "field": …, "op": …, "value": …, "caseSensitive": … }.

No conditions means the trigger listens to every email that can reach it.

json
{
  "all": [
    { "field": "from.domain", "op": "matchesDomain", "value": "client.example" },
    { "field": "attachments.mimeTypes", "op": "contains", "value": "application/pdf" },
    {
      "any": [
        { "field": "subject", "op": "contains", "value": "invoice" },
        { "field": "subject", "op": "contains", "value": "facture" }
      ]
    }
  ]
}

This fires for an email from client.example or one of its subdomains, with a PDF attachment, whose subject contains "invoice" or "facture" in any case.

In the editor, the trigger panel edits one level: choose "All" or "Any" under "How conditions combine", then add conditions (Field, Operator, Value). Nested groups written through the API are kept and shown as "nested groups kept — not editable here". "Ready-made conditions…" inserts common ones, for example "Has at least one attachment", "Has a PDF attachment" or "Is not an auto-reply".

Condition fields ​

Each field has a type, which decides the allowed operators and the type of value.

FieldLabel in the editorTypeContent
from.emailSender (address)textthe sender's address
from.nameSender (name)textthe sender's display name
from.domainSender (domain)textthe sender's domain, lower case, without trailing dot
to.emailsTo (addresses)listthe To addresses
to.domainsTo (domains)listthe To domains
cc.emailsCc (addresses)listthe Cc addresses
cc.domainsCc (domains)listthe Cc domains
recipients.emailsAll recipients (addresses)listTo + Cc + Bcc — use it for "sent to this alias"
recipients.domainsAll recipients (domains)listthe domains of To + Cc + Bcc
replyTo.emailsReply-To (addresses)listthe Reply-To addresses
subjectSubjecttextthe subject
bodyTextMessage bodytextthe full plain-text body
receivedAtReceived attextISO 8601 date-time in UTC
sentAtSent attextISO 8601 date-time in UTC, when the email carries one
attachments.countAttachment countnumberthe number of real attachments; inline images such as signature logos do not count
attachments.filenamesAttachment nameslistthe file names of the real attachments
attachments.mimeTypesAttachment typeslistthe MIME types of the real attachments (application/pdf)
folderLabelsFolders / labelslistthe IMAP folders or Gmail labels the email carries
flags.seenSeenyes/noread
flags.flaggedFlaggedyes/noflagged / starred
flags.draftDraftyes/nodraft
flags.sentSentyes/nosent
signals.isAutoReplyAuto-replyyes/nodetected as an automatic reply
signals.isNoReplyNo-reply addressyes/nosent from a no-reply address
signals.isMailingListMailing listyes/nodetected as a mailing-list or bulk email
signals.isFromSelfSent by meyes/nosent by one of your own addresses
header:<name>—listevery value of a raw header, for example header:list-id or header:x-mailer

header:<name> is available in the document and through the API, not in the editor's field list. The header name must be written in lower case letters, digits and hyphens (header:x-priority, not header:X-Priority).

Condition operators ​

OperatorLabel in the editorText fieldsNumber fieldsYes/no fieldsList fields
eqequals✓✓✓✓
neqdoes not equal✓✓✓✓
containscontains✓——✓
startsWithstarts with✓——✓
endsWithends with✓——✓
gtis greater than✓✓——
ltis less than✓✓——
existsis set✓✓✓✓
matchesDomainhas domain✓——✓

Value types. Text and list fields take a text value; number fields a number (0, not "0"); yes/no fields true or false. matchesDomain always takes a text value. exists takes no value (or true) to test that the field is set, and false to test that it is not.

How each operator behaves:

  • Text comparisons ignore case by default but respect accents: cafe.fr and café.fr are different. Add "caseSensitive": true to a leaf to compare exactly. Unicode spellings of the same character compare equal.
  • On list fields, operators hold when at least one element satisfies them (to.domains contains client holds if any To domain contains it). neq is the exception: it holds when no element is equal, so an empty list satisfies it.
  • matchesDomain compares domains: the value is a domain or an address (only the part after @ is used), and the field holds when its domain is that domain or a subdomain of it. from.email has domain client.example matches anna@mail.client.example.
  • gt and lt on text compare alphabetically. On receivedAt and sentAt, which are ISO dates in UTC, this is a chronological comparison: receivedAt is greater than 2026-10-01 holds for anything received from 1 October 2026 (UTC).
  • exists holds for a text field that is not empty, a list that has at least one element, and a number or yes/no field that has a value.
  • A text field the email does not carry (no sender, no sentAt) makes every operator false, except exists with false. List fields are always present, possibly empty.

Condition errors ​

Publication refuses conditions that cannot be evaluated, with the error invalid_trigger_conditions and one issue per faulty leaf, located by its path in the tree (all[0].any[2]):

IssueMeaning
unknown_fieldthe field is not in the list above
op_not_supportedthe operator is not allowed on this field type (for example contains on attachments.count)
missing_valuethe operator needs a value
invalid_value_typethe value has the wrong type ("0" instead of 0, a non-boolean for exists)
empty_groupa group has no child: an empty all would match every email and an empty any none

Conditions are checked on disabled email triggers too, so a trigger can be re-enabled safely.

Condition examples ​

Has at least one real attachment:

json
{ "all": [ { "field": "attachments.count", "op": "gt", "value": 0 } ] }

Sent to a given alias, wherever it appears (To, Cc or Bcc):

json
{ "all": [ { "field": "recipients.emails", "op": "eq", "value": "invoices@company.example" } ] }

From one of two customers, and not an automatic reply:

json
{
  "all": [
    { "any": [
      { "field": "from.domain", "op": "matchesDomain", "value": "client-a.example" },
      { "field": "from.domain", "op": "matchesDomain", "value": "client-b.example" }
    ] },
    { "field": "signals.isAutoReply", "op": "eq", "value": false }
  ]
}

Subject starting exactly with an upper-case reference:

json
{ "all": [ { "field": "subject", "op": "startsWith", "value": "REF-", "caseSensitive": true } ] }

No Cc at all:

json
{ "all": [ { "field": "cc.emails", "op": "exists", "value": false } ] }

The same tree, with fields prefixed email. and data fields data.<path> plus the isEmpty operator, is used by the Condition (If) node later in the graph.

Webhook received (trigger.webhook) ​

trigger.webhook starts the workflow on an HTTP call. It has no settings.

The URL. Each workflow that uses this trigger has one URL:

<PUBLIC_BASE_URL>/hooks/wf/<token>

The token (256 random bits) is created at the first publication, or earlier with "Generate the URL" in the trigger panel. It is shown only once: only a fingerprint is stored, so nobody can display it again. If you lose it, or if it leaks, use "Generate a new URL": the previous URL stops working immediately. Republishing keeps the same URL. The editor shows the path starting with /hooks/wf/; put your instance address in front of it. Through the API, POST /api/v1/workflows/:id/webhook generates a new token and returns it once.

The token is the authentication: no header or key is required. Treat the URL as a secret.

The request.

bash
curl -X POST "<PUBLIC_BASE_URL>/hooks/wf/<token>" \
  -H "Content-Type: application/json" \
  -d '{"client": {"name": "Jane Doe", "email": "jane@example.com"}}'
  • Method: POST only.
  • Body: JSON, at most 256 KB (262,144 bytes). The body becomes data.webhook: here {{ data.webhook.client.email }}. An empty body gives null. Headers and query string are not passed to the workflow.

The responses.

StatusBodyWhen
202{ "executionId": "…" }the run was created; it runs in the background
400errorthe body is not valid JSON
404{ "code": "not_found", … }unknown token, or the workflow is not published, archived, or has no active webhook trigger — all answer the same way
413{ "code": "request.payload_too_large", … }the body exceeds 256 KB
429empty, with Retry-Aftertoo many calls from your address; retry later

Every call creates a new run: two identical calls are two runs. The run has no triggering email (see the triggering email).

Details: Webhook received.

Called by a workflow (trigger.called) ​

trigger.called makes a workflow callable by others with the Call a workflow node. It has no settings, subscribes to no email, and fires only when called.

  • The caller's working data arrives under {{ data.input }}.
  • The run inherits the caller's triggering email when the caller has one; otherwise it has none.
  • The workflow must be published to be callable, and only one trigger.called is allowed per workflow.
  • A workflow can also carry email triggers: when called, only the branches after trigger.called run.

Details: Called by a workflow.

Schedule (trigger.schedule) ​

trigger.schedule starts the workflow at regular times, with no triggering email.

SettingLabelValues
modeCadenceinterval — "Every N minutes" (default), or cron — "Cron expression"
everyMinutesEvery (minutes)whole number from 5 to 44,640 (31 days); default 60
cronCron expressionfive fields: minute, hour, day of month, month, day of week
timezoneTime zoneIANA name such as Europe/Paris; empty means UTC

Cron expressions accept, in each field, *, a number, a range a-b, a list x,y,z of these, and a step /n (*/15, 0/15, 8-18/2). Months accept JAN–DEC and days of the week SUN–SAT; day of week goes from 0 to 7, both 0 and 7 meaning Sunday. Extensions such as ?, L, W and # are refused. An expression that would fire more often than every 5 minutes is refused.

ExpressionMeaning
0 8 * * 1every Monday at 08:00
0 9 * * 1-5weekdays at 09:00
*/15 8-18 * * MON-FRIevery 15 minutes from 08:00 to 18:45 on weekdays
30 7 1 * *the 1st of each month at 07:30

The cron expression is read in the chosen time zone, daylight-saving changes included. The trigger panel shows the "Next runs" computed by the server. Intervals are aligned on fixed points in time rather than on the publication time: every 60 minutes fires on the hour (UTC).

What the run receives, under data.schedule: plannedFor (the scheduled time, rounded as requested, ISO 8601), firedAt (the actual time) and timezone.

Reliability. Each occurrence creates at most one run. If the service was down at the scheduled time, only the last missed occurrence is caught up, within a few minutes; earlier ones are skipped rather than fired in a burst. A paused workflow skips its occurrences.

Publication refuses an unusable schedule with invalid_schedule and one of: cron_missing, cron_field_count, cron_field_syntax, cron_too_frequent, interval_out_of_range, unknown_timezone.

Details: Schedule.

Manual run (trigger.manual) ​

trigger.manual marks a workflow meant to be started by hand. Nothing arms it: publishing shows the warning trigger_not_armed, which here only reminds you that you start it.

A manual run executes the published version for real, on one email:

  • in the webmail, open the email and choose "Run a workflow", then the workflow ("REAL run of the published version: side effects go out for good");
  • through the API, POST /api/v1/workflows/:id/run with { "messageId": "…" }, and an optional clientToken so a repeated request creates only one run.

Any published workflow can be run this way, with or without trigger.manual. If the workflow has an active trigger.manual, only the branches after it run; otherwise every branch leaving a trigger runs. The email must belong to one of your mailboxes. To try a draft without effects, use test runs instead.

Details: Manual run.

Integration triggers by polling ​

trigger.airtable and trigger.notion_changes ask the service "what is new since last time?" at a regular interval, and create one run per new or changed item.

TriggerWatchesInterval ("Check every (minutes)")Data
trigger.airtable (Airtable row changed)rows of an Airtable table, optionally a view and an extra formula; needs a "Last modified time" (or "Created time") field5 to 1,440 minutes, default 15data.airtable: baseId, tableId, recordId, changedAt, fields
trigger.notion_changes (Notion entry changed)entries of a Notion database: created or changed, new only, or changed only5 to 1,440 minutes, default 15data.notion
  • Starting point: the first publication starts watching from now. Rows or entries that existed before are not replayed, and republishing never resets the position.
  • No loss, no duplicate: each item creates one run, even if a check is repeated after an incident.
  • A bounded batch per check: what does not fit is picked up by the next check.
  • The instance can impose a slower minimum (INTEGRATION_POLL_MIN_MINUTES, 5 minutes by default): a trigger never checks faster than that.
  • Errors: a temporary error (rate limit, outage) changes nothing and the next check retries; a permanent one (revoked connection, deleted base) sends you one notification per outage.
  • A paused workflow skips its checks.

Details: Airtable row changed, Notion entry changed, and the Airtable and Notion integrations.

Integration triggers by webhook ​

trigger.mynotary, trigger.yousign and trigger.notion receive events on the same workflow URL as trigger.webhook (<PUBLIC_BASE_URL>/hooks/wf/<token>, created and shown once in the same way). Each carries a connection to the service and an "Events" setting.

TriggerDefault eventOrigin checkDuplicates
trigger.mynotary (MyNotary — event)signature_completedthe URL token only (MyNotary does not sign)not deduplicated: a delivery repeated by MyNotary creates a second run
trigger.yousign (Yousign — event)signature_request.donesignature x-yousign-signature-256 checked against the connection's secretdeduplicated on event_id: Yousign's retries start nothing new
trigger.notion (Notion — event)page.properties_updatedsignature X-Notion-Signature checked against the connection's verification tokendeduplicated on the event id
  • Event filter: only the ticked events create runs. Others receive 202 and create nothing, so the sender does not retry. With nothing ticked, every event is accepted.
  • A rejected signature answers the same 404 as an unknown token.
  • Subscriptions are created on the service side: through the integration node's API operation for MyNotary and Yousign (or by hand in Yousign), and by hand in Notion's developer portal for Notion. Notion can also be watched without any setup with trigger.notion_changes.
  • The body arrives under data.mynotary, data.yousign or data.notion, exactly as the service sent it. Treat it as untrusted data.

Details: MyNotary — event, Yousign — event, Notion — event, and the MyNotary, Yousign and Notion integrations.

Pausing and arming ​

TriggerArmed by publicationStopped by pause
trigger.emailyes, on every mailbox you own that is not disconnectedyes
trigger.scheduleyes, one schedule per active triggeryes
trigger.airtable, trigger.notion_changesyes, one polling rule per active triggeryes
trigger.webhook, trigger.mynotary, trigger.yousign, trigger.notionyes, the workflow URL answersno
trigger.calledyes, the workflow becomes callableno
trigger.manualno — you start it—

Unpublishing or archiving disarms every trigger: the URL answers 404, schedules and polling stop, and the workflow can no longer be called.