Introduction

Application user onboardings tend to compose multiple actions (sending emails) that might spread across multiple steps over time.

For example, when a user onboards, we might want to immediately:

  • send a welcome email
  • trigger a Hubspot contacts sync

then, make the user as onboarded and, a few days later: send a tutorial if the user is not engaged.

While such workflow can be implemented in a distributed way, in a event-based architecture, or with CRONs, Defer offers a context-switch free and code-first way to implement such workflows.

Before jumping into the user onboarding workflow implementation, let’s see how Defer enables to run background functions in parallel while waiting for their result, as needed in:

when a user onboards, we might want to immediately:

  • send a welcome email
  • trigger a Hubspot contacts sync

then, make the user as onboarded […]

Running background functions in parallel

Given the following project:

- src/
  - defer/
    - importContactsFromHubspot.ts ⬅
    - sendWelcomeEmail.ts ⬅
    - sendTutorialsEmailIfNoActions.ts ⬅
    - userOnboarding.ts ⬅
  - ...
- package.json
- .env
- ...

We want to call trigger and wait for the execution of importContactsFromHubspot() and sendWelcomeEmail(), in parallel.

For this, will use the awaitResult() API, as follows:

src/demoParallelRuns.ts
import { defer } from "@defer.run/client";
import importContactsFromHubspot from "./defer/importContactsFromHubspot";
import sendWelcomeEmail from "./defer/sendWelcomeEmail";

async function myDemo() {
  const user = {
    /* ... */
  };

  // run the background functions immediately in parallel and wait for the results
  const results = await Promise.allSettled([
    awaitResult(importContactsFromHubspot)(user),
    awaitResult(sendWelcomeEmail)(user),
  ]);
}

If the background function execution succeded, awaitResult() will return the value returned by the function.

If it failed, it will raise an error (the one raised by the background function or a generic one.)

Now that we saw how to run multiple background functions in parallel to get their results, let’s see how to write our user onboarding workflow.

Writing a workflow with Defer

Writing workflows with Defer is simple, just call other background functions from your main background function.

Our userOnboarding() function would call other functions and execute as follow:

Hubspot contacts import workflow

As stated earlier, implementing such a workflow is achieved with simple background functions calls:

src/userOnboarding.ts
import { delay, defer } from "@defer/client";
import sendWelcomeEmail from "./sendWelcomeEmail";
import importContactsFromHubspot from "./importContactsFromHubspot";
import sendTutorialsEmailIfNoActions from "./sendTutorialsEmailIfNoActions";

async function userOnboarding(user: User) {
  await Promise.allSettled([
    awaitResult(importContactsFromHubspot)(user),
    awaitResult(sendWelcomeEmail)(user),
  ]);

  // once the Hubspot data is synced and the welcome mail sent,
  //  let's flagged the user as onboarded
  await prisma.user.update({
    where: { id: user.id },
    data: { onboarded: true },
  });

  const sendTutorialsEmailIfNoActionsDelayed = delay(
    sendTutorialsEmailIfNoActions,
    "1 day",
  );
  await sendTutorialsEmailIfNoActionsDelayed(user);
}

export default defer(userOnboarding);

Congrats, you implemented your first workflow with Defer 🎉

You can start this workflow from your application’s API like any background function: userOnboarding(user).

Your workflow will benefit of all Defer’s features:

  • Analytics and logs on Defer dashboard
  • Smart retries on child background failures
  • Slack notification on workflow failures
  • Cancel and re-run workflow from the Defer dashboard