line

Tutorial: Refer a Friend

Step by step instructions on launching a workflow that waits for 3 external events and then sends an email notification.

View the github project. View all of the files, fork the project and deploy to heroku in a few clicks. Or run it in the online sandbox without needing to install Zenaton.

Play on Zenaton

This is a simple workflow that starts when a user invites 5 friends to purchase an "Awesome Cheesecake" Deal. If at least 3 friends also purchase the deal then the user is credited for their payment and gets it for free.

Our workflow will wait for up to 3 of the 5 friends to purchase the deal at which point it will refund the user their payment so that they receive the deal for free.

How to do the tutorial

Try it on the sandbox where you can launch your workflow and send events directly or using curl from your command line.

View the project on github where you can fork and deploy to heroku or your own hosting solution and make it your own!

In this tutorial, you learn how to:

  • launch a Workflow via HTTP
  • use the wait function with external events
  • send event to a running workflow using HTTP
  • use the Sendgrid API connector to send email (optional)

Sections

  1. Launch a workflow instance for the user when they invite friends.
  2. Send the first event and view the event details in 'Workflow Executions.'
  3. Add and send 2 more events for each friend that purchases a deal.
  4. Refund the deal to the user using an http:put Task.
  5. Modify the workflow logic to ignore one friend purchasing multiple times.
  6. Send an email using the Sendgrid connector to notify the user of their refund.

1) Launch the workflow

A workflow instance is started when a user invites 5 friends to buy their deal.

The instance will be launched with the following data input that contains the email and the deal of the main user:

{
  "id": 123,
  "email": "foo@example.com",
  "deal": "awesome cheese cake"
}

To see it in action, you can launch a workflow instance within your application code using HTTP or using the quick launch button in the Sandbox.

curl -X POST https://gateway.zenaton.com/rest-api/v1/instances \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "app-env: dev" \
  -H "app-id: FBKTJXUMLO" \
  -H "api-token: Bt29HVlvrY9atH41LNwnAp5MQYkEut8oCqXKH8NGOjUAtJltB6Sea82FbP2P" \
  -d '{"name":"ReferAFriend","input":[{"id":123,"email":"foo@example.com","deal":"awesome cheese cake"}],"version":"v1"}'

You can also launch a workflow from within your application using the Zenaton SDK.

The purpose of the workflow is:

  • If at least 3 friends also purchase the deal then the user is credited for their payment and gets it for free.

So the first step is to wait for 3 friends to buy a deal (the deal_purchased event).

It can simply be achieved by using the wait function on the "deal_purchased" external event in a loop.

The wait function will suspend the the workflow execution until each event is received.

  // Initialize the counter of friend that have purchased a deal.
  let nb_friends = 0;

  // Loop until 3 friends purchase a deal.
  do {
    // Wait for the deal_purchased event
    yield this.wait.event("deal_purchased").forever();
    nb_friends++;
  } while (nb_friends < 3);

View the workflow executions

Once the workflow is launched, check the real-time executions in your dashboard, click on the "ReferAFriend" card and on the first progress-bar line, you will see a live summary of the steps.

Click on the "dispatched" step to view the input parameters and you can also see the first wait of the loop.

2) Send Events

The workflow is running and waiting for the deal_purchased event which will be sent when a friend purchases the deal.

You can send event to the workflow using HTTP

Replace the INSTANCE_ID with the id of the workflow you got when you launched it.

curl -X POST https://gateway.zenaton.com/rest-api/v1/instances/INSTANCE_ID/event \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "app-env: dev" \
  -H "app-id: FBKTJXUMLO" \
  -H "api-token: Bt29HVlvrY9atH41LNwnAp5MQYkEut8oCqXKH8NGOjUAtJltB6Sea82FbP2P" \
  -d '{"name":"deal_purchased","data":[{"deal_name":"awesome cheese cake","email":"friend_1@example.com"}]}'

Here is the event payload in details. It's composed of a name "deal_purchased", and some data: some details about the friend that buys a deal:

{
  "name": "deal_purchased",
  "data": [
    {
      "deal_name": "awesome cheese cake",
      "email": "friend_1@example.com"
    }
  ]
}

These events would normally be sent from our application code using the Zenaton SDK or via HTTP.

You can also send a test event in the sandbox by clicking on the 'send event' button.

Workflow Executions

A new row has appeared in workflow execution as well as the next wait in the loop. Click on the event to see the associated data (user_id and email)

When the first deal_purchased event is received by the waiting workflow, the workflow stops 'waiting' and resumes and starts the next step - which is the beginning of another wait inside the loop.

3) Send Events for Friends 2 and 3

Now, to simulate two more friends purchasing the deal, modify the parameters of the event to add their email address and ID.

Send each event to the workflow.

Once three friends have purchased the deal, the loop ends and the next step will be executed - crediting our user for their deal so that they get it for free with the http:put task.

4) The http:put task

Normally this task would be executed on your own servers or on an external service (like Stripe) by sending an http call, but in our example, we've created a dummy http request to allow us to easily simulate and test this action in the workflow.

Check the workflow executions for a new row about called http:put. View the details to see the HTTP request that was sent, and it's response.

5) Modify Workflow Logic

Now that you are familiar with this workflow, lets make a few changes to the logic and run it again.

Prevent one friend from buying the deal multiple times

You might have noticed that the actual logic in the loop is quite naive: there is no check on the user email to make sure that one friend isn't buying the deal multiple times.

So we're going check the user email coming from the event's data to count only distinct emails.

Replace the actual loop with the following one:

// Initialize the set of friends that have purchased a deal.
  let friends = new Set()
  // Loop until 3 friends purchase a deal.
  do {
    // Wait for the deal_purchased event
    const [event_name, event_data] = yield this.wait.event("deal_purchased").forever();
    // Get the friend's email from the event data received.
    friends.add(event_data.email)
  } while (friends.size < 3);

Launch and Test the workflow:

Launch the workflow again and when you send 3 events this time, send two of them with the same parameters.Note that the second time the workflow receives an event with the same parameters, it will not resume and will continue waiting.Now enter the third event with the correct parameters and the workflow will resume and advance to the final step to refund the user.

5) Send Email with Sendgrid API connector (Optional)

Now that the user has been refunded, we will notify them via email using the Zenaton connector function for the Sendgrid API.

Setup the Sendgrid connector from your dashboard : search for "sendgrid", then click add.

Then give it a name and copy/paste your Sendgrid API key.

You will then have it in your connectors list

We've written the workflow code to send your email so just uncomment the body of the sendMail function and set the sendgridConnectorId variable at the top of the code

For sending the refund notification the sendMail function will use the connector with the id you got from your dashboard: 71a2e903-e10e-4553-991c-2ac3fb5d278c.

Then you don't manage authentication anymore, you just use the Sendgrid API.

const sendgridConnectorId = "71a2e903-e10e-4553-991c-2ac3fb5d278c";
const sendgrid = this.connector("sendgrid", sendgridConnectorId);

// Example of sendMail implementation using Sendgrid
function* sendMail(sendgrid, from, to) {
  const payload = {
    body: {
      personalizations: [{ to: [{ email: to }]}],
      content: [{ type: "text/plain", value: "Congratulations, you have received a refund..." }],
      subject: "Deal refund",
      from: { email: from },
    }
  };

  sendgrid.post("/mail/send", payload);
}

Re-Launch Workflow

Before launching the workflow again, you will need to update the workflow input, to update the user email with yours to be able to receive the email.

So, replace "YOUR_EMAIL" by yours in the following HTTP request:

curl -X POST https://gateway.zenaton.com/rest-api/v1/instances \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "app-env: dev" \
  -H "app-id: FBKTJXUMLO" \
  -H "api-token: Bt29HVlvrY9atH41LNwnAp5MQYkEut8oCqXKH8NGOjUAtJltB6Sea82FbP2P" \
  -d '{"name":"ReferAFriend","input":[{"id":123,"email":"YOUR_EMAIL","deal":"awesome cheese cake"}],"version":"v1"}'

Run the workflow again and send 3 "purchased_deal" events to trigger the deal credit and email. Then, check your email to see if you received the email.

Want to play around with the workflow? Here are some ideas. You can write any logic you please using plain javascript and the Zenaton functions to control the timing and flow.

  • add a deadline for each friend to buy their deal.
  • check that the deal price is in the same range
  • add email reminders if the friends don't respond within 3 days.

Workflow Code

View the github project. View all of the files, fork the project and deploy to heroku in a few clicks. Or run it in the online sandbox without needing to install Zenaton.

Play on Zenaton

const sendgridConnectorId = "";
const emailFrom = "zenaton-tutorial@zenaton.com";

module.exports.handle = function*(user) {
  // Setup connectors
  const http = this.connector("http");
  const sendgrid = this.connector("sendgrid", sendgridConnectorId);

  // Initialize the counter of friends that have purchased a deal.
  let nb_friends = 0;

  // Loop until 3 friends purchase a deal.
  do {
    // Wait for the deal_purchased event
    yield this.wait.event("deal_purchased").forever();
    nb_friends++;
  } while (nb_friends < 3);

  // Trigger the refund to the user in your application.
  yield* refund(http, user);

  // Send an email to user
  yield* sendMail(sendgrid, emailFrom, user.email);
};

// call to fake server - IRL you should probably use your own endpoint
function* refund(http, user) {
  yield http.put("https://httpbin.org/anything/refund", { body: user });
}

// Example of sendMail implementation using Sendgrid
function* sendMail(sendgrid, from, to) {
  /*
  const payload = {
    body: {
      personalizations: [{ to: [{ email: to }]}],
      content: [{ type: "text/plain", value: "Congratulations, you have received a refund..." }],
      subject: "Deal refund",
      from: { email: from },
    }
  };

  sendgrid.post("/mail/send", payload);
  */
}