Introduction

The most basic Workflow to send a notification in response to an event looks like:

import { workflow } from '@novu/framework';

const myWorkflow = workflow('new-signup', async ({ step, payload }) => {
	// Send a welcome email
  await step.email('send-email', async () => {
    return {
      subject: `Welcome to Acme, ${payload.name}`,
      body: 'We look forward to helping you achieve mission.',
    }
  });
  // JSON Schema for validation and type-safety. Zod, and others coming soon.
}, { payloadSchema: { properties: { name: { type: 'string' }}}} );

Just-in-time data fetching

You can add any custom logic into your steps that you need. Maybe you’d like to fetch some information about your new sign-up from somewhere else during the Workflow execution. You can achieve it with the following changes:

const myWorkflow = workflow('new-signup', async ({ step, payload }) => {
	// Send a welcome email
  await step.email('send-email', async () => {
	  // Fetch the user from your database
	  const user = await db.getUser(payload.userId);
        return {
          subject: `Welcome to Acme ${user.productTier} tier, ${user.name}.`,
          // 'Welcome to Acme Business tier, John Doe.'
          body: 'We look forward to helping you achieve mission.',
        }
      });
      // Changing the schema from `name` -> `userId`, for type-safety.
    }, { payloadSchema: { properties: { userId: { type: 'string' }}}} );

We call this just-in-time notification data fetching. It allows you pull in data from the relevant sources during the Workflow execution, removing the need to store all of your subscriber data in Novu.

Multi-step workflow

Now, what if you wanted to send another update to the same new user in 1 week? But, you don’t want to send the follow-up if the User opted out. We can add more steps to the Workflow to achieve this.

const myWorkflow = workflow(
  'new-signup',
  async ({ step, payload }) => {
    await step.email('send-email', async () => {
      const user = await db.getUser(payload.userId);
      return {
        subject: `Welcome to Acme ${user.productTier} tier, ${user.name}.`,
        body: 'We look forward to helping you achieve mission.',
      };
    });

    // Wait for 1 week before continuing. After 1 week, Novu will continue
    // executing the Workflow from here.
    await step.delay('onboarding-follow-up', async () => ({
      amount: 1,
      type: 'regular',
      unit: 'weeks',
    }));

    // 1 week passed, let's follow up with an in-app notification.
    await step.inApp(
      'onboarding-follow-up',
      async (inputs) => {
        const user = await db.getUser(payload.userId);
        // The `feedbackUrl` can be updated in Novu Web without changing code.
        // This helps you to create re-usable Workflow snippets.
        return {
          body: `Hey ${user.name}! How do you like the product?

      Let us know <a href="${inputs.feedbackUrl}">here</a>
      if you have any questions.`,
        };
      },
      {
        // Don't follow up if the Subscriber opted out.
        skip: () => !payload.shouldFollowUp,
        // Add validation to ensure `feedbackUrl` provided in Web is a `uri`
        inputSchema: {
          type: 'object',
          properties: { feedbackUrl: { type: 'string', format: 'uri', default: 'https://acme.com/feedback' } },
          required: ['feedbackUrl'],
          additionalProperties: false,
        } as const,
      },
    );
  },
  {
    payloadSchema: {
      type: 'object',
      properties: {
        userId: { type: 'string' },
        shouldFollowUp: { type: 'boolean', default: true },
      },
      required: ['userId', 'shouldFollowUp'],
      additionalProperties: false,
    } as const,
  },
);

With this simple Workflow, we:

  • Sent a new signup email
  • Waited 1 week
  • Sent an in-app follow-up notification

You should now have a grasp of the flexibility that Novu Framework offers.

Continue your reading in Steps to find out the other channels and actions you can use with Novu Framework.

Workflow Interface

workflow(
	// the Workflow name. Must be unique across your Bridge server.
	'new-signup',
	// Workflow resolver, the entry-point for your Workflow steps
	async ({
		// Helper function to declare your Workflow Steps.
		step,
		// Workflow Trigger payload
		payload,
	}) => {
	// ...your Workflow Steps

}, {
	// JSON Schema for validation and type-safety. Zod, and others coming soon.
	// https://json-schema.org/draft-07/json-schema-release-notes

	// The schema for the Workflow payload passed dynamically via Novu Trigger API
	// Defaults to an empty schema.
	payloadSchema: { properties: { name: { type: 'string' }}},
	// The schema for the Workflow inputs passed statically via Novu Web
	// Defaults to an empty schema.
	inputSchema: { properties: { brandColor: { type: 'string' }}},
});