Comment on page
Invitation by e-mail
Communicate with external services
In our previous tutorials, only users interacted with the process. An important feature of Live Contract is the ability to interact with both people and systems.
This tutorial will start after the introduction and exchange of the recipients e-mail address. The initiator is organizing a meetup and wants to invite the people he met at the conference via e-mail.
Create a new subdirectory named
invitation
and copy main.feature
and scenario.yml
from the condition
subdirectory.We'll only focus on the situation where the recipient gives her e-mail address and the process is completed successfully.
Feature: Initiator invites the recipient to a meetup.
Background:
Given a chain is created by "Joe"
Given "Joe" creates the "main" process using the "invitation" scenario
And "Joe" is the "initiator" actor of the "main" process
And "Jane" is the "recipient" actor of the "main" process
Scenario:
When "Joe" runs the "introduce" action of the "main" process with:
| name | Joe Smith |
| organization | LTO Network |
Then the "initiator" actor of the "main" process has:
| name | Joe Smith |
| organization | LTO Network |
And the "main" process is in the "initial" state
When "Jane" runs the "introduce" action of the "main" process with:
| name | Jane Wong |
| organization | Acme Inc |
Then the "recipient" actor of the "main" process has:
| name | Jane Wong |
| organization | Acme Inc |
And the "main" process is in the "wait_on_initiator" state
When "Joe" runs the "request_email" action of the "main" process
Then the "main" process is in the "wait_on_recipient" state
When "Jane" runs the "reply" action of the "main" process with:
| email | [email protected] |
Then the "recipient" actor of the "main" process has:
| name | Jane Wong |
| organization | Acme Inc |
| email | [email protected] |
And the "main" process is in the "pending_invitation" state
When "Joe" runs the "invite" action of the "main" process
Then the "main" process is completed
In that case Joe needs to manually invite Jane (and all other invitees) by e-mail. Luckily, the workflow engine supports running actions in an automated manner.
An important aspect of a Live Contract is that it only defines what should be done and not how it must be done. In this case we'll define that the initiator should send an e-mail. The way that's done is up to the actor that performs the action.
YAML
JSON
actions:
introduce:
actors:
- initiator
- recipient
condition: !eval current.actor.name == null
update: current.actor
request_email:
actor: initiator
reply:
actor: recipient
update: actors.recipient
invite:
$schema: http://exmple.com/schemas/invite/schema.json#
actor: initiator
invitee: !ref actors.recipient
message: Please come to great Meetup next week.
states:
initial:
action: introduce
transitions:
- transition: wait_on_initiator
condition: !eval actors.initiator.name != null && actors.recipient.name != null
wait_on_initiator:
action: request_email
transition: wait_on_recipient
wait_on_recipient:
action: reply
transitions:
- transition: pending_invitation
condition: !eval actors.recipient.email != null
- transition: :cancelled
condition: !eval actors.recipient.email == null
pending_invitation:
action: invite
transition: :success
{
"actions": {
"introduce": {
"actors": [
"initiator",
"recipient"
],
"condition": {
"<eval>": "current.actor.name == null"
},
"update": "current.actor"
},
"request_email": {
"actor": "initiator"
},
"reply": {
"actor": "recipient",
"update": "actors.recipient"
},
"invite": {
"$schema": "http://exmple.com/schemas/invite/schema.json#",
"actor": "initiator",
"invitee": "actors.recipient",
"message": "Please come to great Meetup next week."
}
},
"states": {
"initial": {
"action": "introduce",
"transitions": [
{
"transition": "wait_on_initiator",
"condition": {
"<eval>": "actors.initiator.name != null && actors.recipient.name != null"
}
}
]
},
"wait_on_initiator": {
"action": "request_email",
"transition": "wait_on_recipient"
},
"wait_on_recipient": {
"action": "reply",
"transitions": [
{
"transition": "pending_invitation",
"condition": {
"<eval>": "actors.recipient.email != null"
}
},
{
"transition": ":cancelled",
"condition": {
"<eval>": "actors.recipient.email == null"
}
}
]
},
"pending_invitation": {
"action": "invite",
"transition": ":success"
}
}
}
Up until now, we've only used the default schema for an action. Actions that may be automated should contain all information required to run them. The structure of such an action must be defined in a custom JSON Schema.
The invite action uses a custom JSON Schema, specified with
$schema
. The schema may be publicly available at the specified URL or can be distributed as part of the Live Contract.http://exmple.com/schemas/invite/schema.json
JavaScript
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"actor": {
"type": "string"
},
"invitee": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"email": {
"type": "string",
"format": "email"
}
}
},
"subject": {
"type": "string"
},
"message": {
"type": "string"
}
}
}
{
"title": "JSON schema for invite action",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"actor": {
"type": "string"
},
"invitee": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"email": {
"type": "string"
}
}
},
"subject": {
"type": "string"
},
"message": {
"type": "string"
}
}
}
It's recommended, though not required, that every actor in the process has a copy of the JSON Schema. Actors that may perform the action do require the schema.
For this tutorial we'll send an e-mail through Mailgun using their REST API.
- On the 'Sending' page, select your sandbox URL.
- Add your e-mail address to 'Authorized Recipients' and verify it.

This tutorial uses
sandbox000.mailgun.org
as endpoint, a dummy API key and [email protected]
as e-mail address. Replace that with the (long) sandbox URL, your Mailgun API key and your own e-mail address.A node can define a trigger for an action schema. If an actor on the node is allowed to perform the action the workflow engine will automatically do so.
Triggers are defined through the configuration of the workflow engine. In order to use a custom configuration, please stop docker and than create and link a new directory
workflow-settings
.$ docker-compose stop
In the
workflow-settings
directory, create a settings.yml
file with custom configuration settings. In that file, we'll define a custom trigger for the invite schema.triggers:
invite:
type: http
$schema: http://exmple.com/schemas/invite/schema.json#
url: sandbox000.mailgun.org
method: POST
auth:
username: api
password: key-00000000000000000000000000000000
headers:
Content-Type: application/x-www-form-urlencoded
data:
from: [email protected]
subject: You are invited to our Meetup
projection: "{ data: { to: join('', [invitee.name, ' <', invitee.email, '>']), text: message } }"
The
projection
is a JMESPath expression that converts the action into an object that's expected by the HTTP trigger.The
data
will be encoded and send as request body. The data parameters are described by the Mailgun API. The data in the trigger is merged with the data from the projection.Triggers are configured by each party on their own node. This node will be configured to send an e-mail using Mailgun, but any other service could be used or an entirely different action could be taken to invite the recipient.
Enable outgoing capturing HTTP requests in
workflow-settings/settings.yml
by adding the following settinghttp_request_log: true
In the test, we can check if the workflow engine has send a specific HTTP request.
Feature: Initiator invites the recipient to a meetup.
Background:
Given a chain is created by "Joe"
Given "Joe" creates the "main" process using the "handshake" scenario
And "Joe" is the "initiator" actor of the "main" process
And "Jane" is the "recipient" actor of the "main" process
Scenario:
When "Joe" runs the "introduce" action of the "main" process with:
| name | Joe Smith |
| organization | LTO Network |
Then the "initiator" actor of the "main" process has:
| name | Joe Smith |
| organization | LTO Network |
And the "main" process is in the "initial" state
When "Jane" runs the "introduce" action of the "main" process with:
| name | Jane Wong |
| organization | Acme Inc |
Then the "recipient" actor of the "main" process has:
| name | Jane Wong |
| organization | Acme Inc |
And the "main" process is in the "wait_on_initiator" state
When "Joe" runs the "request_email" action of the "main" process
Then the "main" process is in the "wait_on_recipient" state
When "Jane" runs the "reply" action of the "main" process with:
| email | [email protected] |
Then the "recipient" actor of the "main" process has:
| name | Jane Wong |
| organization | Acme Inc |
| email | [email protected] |
And a "POST" request has been send to "https://api.mailgun.net/v3/sandbox000.mailgun.org/messages" with:
| from | [email protected] |
| to | Jane Wong <[email protected]> |
| subject | You are invited to our Meetup |
| text | Please come to great Meetup next week. |
And that request received a "200" response with:
| message | Queued. Thank you. |
Then the "main" process is completed
The workflow engine can only capture outbound HTTP requests. It can't mock requests, but needs to work with an actual service or you need to setup a custom mocked service yourself.
Last modified 4yr ago