Email your team when an order is placed
A buyer places an order, and your sales or service mailbox gets one short note with the order number, the total and the buyer. An Integration Studio workflow does it: an event trigger starts a run for each placed order, and a Send Email node sends over your own SMTP server. This guide builds that workflow, decides which orders deserve a mail, and shows what the platform does and does not do when orders come in faster than anyone can read.
This is for integrators and shop operators who want a team told about platform events without writing a listener. You need a tenant with the Orders app and Integration Studio installed, access to Cockpit (the admin interface at app.revenexx.com) to build a workflow, and an SMTP server that accepts mail from a workflow (or, if your tenant shows the option, the platform's own mail servers, see step 1), with a mailbox or distribution list to receive it. New to the platform? Prerequisites covers Cockpit, installing an app and a test tenant. The same steps work for any other event: only the topic in step 2 and the fields in step 4 change. Looking for Slack or Teams? See Why this guide does not post to Slack at the end of the steps.
What the platform does, and what it leaves to you
| Responsible for | |
|---|---|
| The platform | An order.placed event for each placed order (delivered at least once), the event's id, and one run for each event the trigger accepts. A repeat delivery of the same event starts no second run |
| The workflow | Choosing which orders to mail about, building the text, and handing it to your SMTP server |
| Your mail system | Delivering the mail, folding many mails into a digest, and keeping the mailbox's access list |
| You | The recipient, the message content, and watching for failed runs |
1. Create an SMTP credential
In Cockpit, open Integration Studio → Credentials → New, pick the type SMTP and fill in the form (host, port, user, password, TLS). If your tenant shows a Use revenexx Mail Servers switch, it fills the login from a platform mail credential instead (as of 2026-11). Before you save, run the dialog's connection test: a green test removes the most common cause of a first run that fails. See Credentials for the types and the four positions a credential reports.
The password lives in the credential, never in the workflow. The node only names the credential.
2. Look up the topic to subscribe to
The event's name is order.placed. A workflow trigger matches the topic, which puts the vendor and the app in front of the name. Read it from your tenant's event catalog instead of typing it from memory. The catalog has no documented scope name yet, so make this one call with a development key that carries *, created under API keys (see create an API key):
curl -s https://api.revenexx.com/v1/events/catalog \
-H "X-Revenexx-Tenant: $REVENEXX_TENANT" \
-H "X-Revenexx-Api-Key: $REVENEXX_API_KEY" \
| jq -r '.emits[] | select(.app == "orders" and .name == "order.placed") | .topic'
revenexx.orders.order.placed
REVENEXX_TENANT is your tenant slug. The catalog lists the Orders app only while it is installed and active on the tenant.
3. Start the workflow on the event
Open Integration Studio → Workflows → New and add an Event trigger. Pick or type the topic in the trigger's Event field:
revenexx.orders.order.placed
The field also shows a Sample payload for the event, which is the quickest way to check field names. Save, and leave the switch in the editor's header on Paused until step 7. Events that arrive while the workflow is paused are skipped and not kept: they do not start a run later and do not land in Missed runs. See Event triggers for subject patterns and the payload envelope.
The event's own fields sit under data:
| Expression | Value |
|---|---|
${{ trigger.payload.data.number }} | The order number, such as ORD-000123 |
${{ trigger.payload.data.order_id }} | The order's id |
${{ trigger.payload.data.grand_total }} | The total, a number |
${{ trigger.payload.data.currency }} | The currency code for every amount in the payload |
${{ trigger.payload.data.item_count }} | The number of items on the order |
${{ trigger.payload.data.buyer.name }} | The buyer's name as typed at checkout, or empty |
${{ trigger.payload.data.placed_at }} | When the order was placed, RFC 3339 |
The payload also carries, among others, status, subtotal, items[], buyer.email, contact_id, organization_id and cart_id. buyer can be null, and then buyer.name is empty. It has no addresses and no payment details. ${{ trigger.payload.id }} is the event's id, not the order's, and because both are in the payload nothing refuses the wrong one: the mail would just carry an id no order has.
order.requested instead. When it is approved, it fires order.placed like any other order. If your team should hear about waiting orders, add a second workflow on that topic, read from the catalog as above. See Set up purchase approvals.4. Decide which orders are worth a mail
This step is optional: without it, every placed order sends one mail. Each placed order starts one run, and each run sends one mail. Apart from dropping a repeat delivery, the platform does not hold, batch or thin anything out between the two, so this is where you control the volume (as of 2026-11).
What exists:
- The trigger drops a repeat delivery of the same event. Delivery is at least once, and the trigger de-duplicates on the event id before it creates a run, so one event starts one run. It does not make the mail itself once-only: with the default of three attempts per node, one run can still send the same mail up to three times, which step 6 closes by setting Max attempts to 1 (see Delivery and de-duplication). Events about one change are not folded: three events are three runs.
- A narrow subject. Subscribe to the one topic you need. A subject such as
revenexx.orders.>starts a run for every order topic, which is many runs for one order. - A condition in front of the mail. Add an If condition (or a Filter) node behind the trigger and wire only its
trueport to the mail. Conditions are not expressions: pick the field with the expression picker, then an operator and a value. To mail only about orders above a size, testgrand_totaloritem_count. The list of operators is not documented, so look in the dropdown for a greater-than comparison. Use Switch to send different kinds of order to different mailboxes.
What does not exist:
- No throttle, no debounce, no batching, no daily cap. A workflow has no concurrency setting and no rate setting: nothing limits how many runs start close together, and nothing groups them (see Execution modes).
- No built-in digest. A scheduled workflow that sends one summary would have to read the newest orders from the API and remember where the last run stopped. The order list has no range filter, so this guide does not build it. If you need a digest, use your mail system: send to a mailbox or list and let a rule or the client fold the notes.
A filter that is too loose is the usual way a mailbox becomes noise. Start with a threshold, watch a week of runs, and loosen it.
5. Send the mail
Add a Send Email node behind the condition and pick the SMTP credential from step 1. from, to, subject and the body accept expressions:
from: shop-alerts@example.com
to: sales-orders@example.com
subject: New order ${{ trigger.payload.data.number }} (${{ trigger.payload.data.grand_total }} ${{ trigger.payload.data.currency }})
text: Order ${{ trigger.payload.data.number }} was placed at ${{ trigger.payload.data.placed_at }}.
Total: ${{ trigger.payload.data.grand_total }} ${{ trigger.payload.data.currency }}
Items: ${{ trigger.payload.data.item_count }}
Buyer: ${{ trigger.payload.data.buyer.name }}
Confirm each expression with the picker in the node's input panel (see Expressions). An expression that resolves to nothing does not fail: it shows up as an empty spot in the mail.
Three choices that are yours:
- Type
fromandtoby hand. Both accept expressions, so they could be filled from the event. Fill them from data you do not control and anyone who can place an order can steer where the mail goes. - Use the
textbody, nothtml. An expression is put into the string as it is, with no escaping. A buyer's name or note containing<a href=...>would arrive as a live link in an HTML mail. Plain text shows it as text. - Leave personal data out unless the reader needs it. The order number is enough to find the order in Cockpit. A mail is stored on your mail server, outside the platform.
The mail goes out over SMTP and the node's sent port fires once the server has accepted it. That is not proof of delivery: a mail can still bounce later, and the platform does not see that.
6. Make a failed send visible
An event-driven workflow fails with nobody watching. Open the Send Email node's Settings tab and set:
- Max attempts to
1. A node is tried three times by default, and an SMTP send that timed out after the server accepted the mail would otherwise go out again, so your team gets the same note up to three times. The price is that a short outage loses that mail, which is why the next setting matters. - On error to Route to error output.
Wire the error port to a Stop and Error node with a message that names the order:
Order mail failed for ${{ trigger.payload.data.number }}, see the run's steps
The run is then marked failed with a reason someone can read. Do not route the error to a second Send Email node on the same SMTP credential: if the server is the problem, every placed order would fail twice. There is no alert on a failed run (as of 2026-11), so check Integration Studio → Runs, filtered to failed, as part of someone's day. See Failure handling.
7. Activate and test
Switch the workflow from Paused to active. A workflow only runs while its build is ready, so an order placed right after a save, while the new build is still running, lands under Integration Studio → Runs → Missed runs and can be replayed from there. See Missed runs.
Place an order on a test tenant, through your storefront or the API (see Ship, cancel and return an order for the order calls), and open Integration Studio → Runs. A run for the order should be there, and the mail should reach the mailbox. Place a second, smaller order to check that the condition from step 4 holds it back.
Why this guide does not post to Slack
Posting an event to Slack or Teams is the common request. The obvious build is an HTTP Request node that posts to the chat service's incoming-webhook address. It works, but you cannot keep the address secret, so this guide does not build it (as of 2026-11).
- The address is the secret. A Slack incoming-webhook address carries its key in the path. Anyone holding the address can post to your channel.
- No credential type puts a secret into a URL. The HTTP Request node takes a credential of an HTTP type, such as a bearer token, and sends it as a header. The address itself is a plain field, so you type it into the node.
- A typed address is stored in the clear. It becomes part of the workflow document, so it also sits in every saved revision and in the run history. A failed call can repeat it in the error message.
- Expressions cannot reach a secret. There is no
secretsroot in an expression, on purpose (see Secrets). A workflow variable does not help, because its value is stored in the workflow document too. - No Slack node is released. The node catalog has none.
A Teams: Send Message node, with a credential that holds the Teams webhook address, exists in a separate node package. It is not in the node catalog, and whether it is registered on the platform is not confirmed (as of 2026-11). Look in the node palette in Cockpit. If it is there, use it in place of the Send Email node in step 5. If it is not, use mail, or ask your platform contact whether it can be installed (see Registration for how packages reach a tenant).
Troubleshooting
- The workflow never starts. It is still Paused: events that arrive while it is paused are skipped and not kept, so activate it and place a new order. Or the subject is
order.placedor has a typo. It has to be the full topic from step 2. Then look in Missed runs: a firing during a build lands there, not in the run list. A paused workflow does not. - The run starts but the mail never comes. Open the run and read the Send Email step. A credential that was never tested reads Not connected; test it again from its detail view. Check your server's own log for a refusal.
- The mail has blanks where values should be. An expression read the wrong field. The event's own fields are under
data, for exampletrigger.payload.data.number, nottrigger.payload.number. - The same order was mailed twice. Look at the runs: two runs with different event ids mean two events, which the trigger does not merge. One run with the node tried twice means Max attempts is still
3. - The mailbox is flooded. The platform has no throttle. Tighten the condition in step 4 or fold the mail on your side.
Where to go next
Start with Failure handling: an unattended workflow is only as good as the way you hear about its failures.
- Event triggers: subjects, wildcards and the payload envelope.
- Credentials and Secrets: the two stores, and why a secret is not an expression.
- Hand an order to the ERP: the same trigger, with calls out to your ERP and back.
- Keep an Integration Studio workflow running: reading a failed run and replaying a missed one.
- Choose an event surface: a workflow trigger, a webhook or an App handler.