Extend medusajs to console log the token when the admin request for reset password.
Medusa Architecture:
Medusa Architecture Overview | Medusa
2–3 minutes

In this document, you'll get an overview of Medusa's architecture to better understand how all resources and tools work together.
Architecture Overview​

Medusa's core package @medusajs/medusa is a Node.js headless server. It combines all the Commerce Modules that Medusa provides. Commerce Modules are ecommerce features that can be used as building blocks in an ecommerce ecosystem. Product is an example of a Commerce Module.

Medusa Core Architecture

The backend connects to a database, such as PostgreSQL, to store the ecommerce store’s data. The tables in that database are represented by Entities, built on top of Typeorm. Entities can also be reflected in the database using Migrations.

The retrieval, manipulation, and other utility methods related to that entity are created inside a Service. Services are TypeScript or JavaScript classes that, along with other resources, can be accessed throughout the Medusa backend through dependency injection.

The backend doesn't have any tightly-coupled frontend. Instead, it exposes API Routes which are REST APIs that frontends such as an admin or a storefront can use to communicate with the backend.

Medusa also uses an Events Architecture to trigger and handle events. Events are triggered when a specific action occurs, such as when an order is placed. To manage this events system, Medusa connects to a service that implements a pub/sub model, such as Redis.

Events can be handled using Subscribers. Subscribers are TypeScript or JavaScript files that export a handler function that's executed asynchronously when an event is triggered, and a configuration object defining what events the subscriber listens to.

You can create any of the resources in the backend’s architecture, such as entities, API Routes, services, and more, as part of your custom development without directly modifying the backend itself. The Medusa backend uses loaders to load the backend’s resources, as well as your custom resources and resources in Plugins.

You can package your customizations into Plugins to reuse them in different Medusa backends or publish them for others to use. You can also install existing plugins into your Medusa backend.


Dependency Container and Injection | Medusa
9–12 minutes

In this document, you’ll learn what the dependency container is and how you can use it in Medusa with dependency injection.
Introduction​
What is Dependency Injection​

Dependency Injection is the act of delivering the required resources to a class. These resources are the class’s dependencies. This is usually done by passing (or injecting) the dependencies in the constructor of the class.

Generally, all resources are registered in a container. Then, whenever a class depends on one of these resources, the system retrieves the resources from the container and injects them into the class’s constructor.
Medusa’s Dependency Container​

Medusa uses a dependency container to register essential resources of the backend. You can then access these resources in classes and API Routes using the dependency container.

For example, if you create a custom service, you can access any other service registered in Medusa in your service’s constructor. That includes Medusa’s core services, services defined in plugins, or other services that you create on your backend.

You can load more than services in your Medusa backend. You can load the Entity Manager, logger instance, and much more.
MedusaContainer​

To manage dependency injections, Medusa uses Awilix. Awilix is an NPM package that implements dependency injection in Node.js projects.

When you run the Medusa backend, a container of the type MedusaContainer is created. This type extends the AwilixContainer object.

The backend then registers all important resources in the container, which makes them accessible in classes and API Routes.
Registered Resources​

The Medusa backend scans the core Medusa package, plugins, and your files in the dist directory and registers the following resources:

Tip

The Lifetime column indicates the lifetime of a service. Other resources that aren't services don't have a lifetime, which is indicated with the - in the column. You can learn about what a lifetime is in the Create a Service documentation.

Resource
	

Description
	

Registration Name
	

Lifetime

Configurations
	

The configurations that are exported from medusa-config.js.
	

configModule
	

-

Services
	

Services that extend the TransactionBaseService class.
	

Each service is registered under its camel-case name. For example, the ProductService is registered as productService.
	

Core services by default have the SINGLETON lifetime. However, some have a different lifetime which is indicated in this table. Custom services, including services in plugins, by default have the SCOPED lifetime, unless defined differently within the custom service.

Entity Manager
	

An instance of Typeorm’s Entity Manager.
	

manager
	

-

Logger
	

An instance of Medusa CLI’s logger. You can use it to log messages to the terminal.
	

logger
	

-

Single Payment Processor
	

An instance of every payment processor that extends the AbstractPaymentService or the AbstractPaymentProcessor classes.
	

Every payment processor is registered under two names:

    Its camel-case name of the processor. For example, the StripeProviderService is registered as stripeProviderService.
    pp_ followed by its identifier. For example, the StripeProviderService is registered as pp_stripe.

	

By default, it's SINGLETON unless defined differently within the payment processor service.

All Payment Processors
	

An array of all payment processor that extend the AbstractPaymentService or AbstractPaymentProcessor class.
	

paymentProviders
	

paymentProviders is TRANSIENT, and each item in it is SINGLETON.

Single Fulfillment Provider
	

An instance of every fulfillment provider that extends the FulfillmentService class.
	

Every fulfillment provider is registered under two names:

    Its camel-case name. For example, the WebshipperFulfillmentService is registered as webshipperFulfillmentService.
    fp_ followed by its identifier. For example, the WebshipperFulfillmentService is registered as fp_webshipper.

	

By default, it's SINGLETON unless defined differently within the fulfillment provider service.

All Fulfillment Providers
	

An array of all fulfillment providers that extend the FulfillmentService class.
	

fulfillmentProviders
	

fulfillmentProviders is TRANSIENT, and each item in it is SINGLETON.

Single Notification Provider
	

An instance of every notification provider that extends the AbstractNotificationService or the BaseNotificationService classes.
	

Every notification provider is registered under two names:

    Its camel-case name. For example, the SendGridService is registered as sendGridService.
    noti_ followed by its identifier. For example, the SendGridService is registered as noti_sendgrid.

	

By default, it's SINGLETON unless defined differently within the notification provider service.

All Notification Providers
	

An array of all notification providers that extend the AbstractNotificationService or the BaseNotificationService classes.
	

notificationProviders
	

notificationProviders is TRANSIENT, and each item in it is SINGLETON.

File Service
	

An instance of the class that extends the FileService class, if any.
	

The file service is registered under two names:

    Its camel-case name. For example, the MinioService is registered as minioService.
    fileService

	

By default, it's SINGLETON unless defined differently within the file service.

Search Service
	

An instance of the class that extends the AbstractSearchService or the SearchService classes, if any.
	

The search service is registered under two names:

    Its camel-case name. For example, the AlgoliaService is registered as algoliaService.
    searchService

	

By default, it's SINGLETON unless defined differently within the search service.

Single Tax Provider
	

An instance of every tax provider that extends the AbstractTaxService class.
	

The tax provider is registered under two names:

    Its camel-case name.
    tp_ followed by its identifier.

	

By default, it's SINGLETON unless defined differently within the tax provider service.

All Tax Providers
	

An array of every tax provider that extends the AbstractTaxService class.
	

taxProviders
	

taxProviders is TRANSIENT, and each item in it is SINGLETON.

Oauth Services
	

An instance of every service that extends the OauthService class.
	

Each Oauth Service is registered under its camel-case name followed by Oauth.
	

By default, it's SINGLETON unless defined differently within the Oauth service.

Feature Flag Router
	

An instance of the FlagRouter. This can be used to list feature flags, set a feature flag’s value, or check if they’re enabled.
	

featureFlagRouter
	

-

Redis
	

An instance of the Redis client. If Redis is not configured, a fake Redis client is registered.
	

redisClient
	

-

Single Entity
	

An instance of every entity.
	

Each entity is registered under its camel-case name followed by Model. For example, the CustomerGroup entity is stored under customerGroupModel.
	

-

All Entities
	

An array of all database entities that is passed to Typeorm when connecting to the database.
	

db_entities
	

-

Repositories
	

An instance of each repository.
	

Each repository is registered under its camel-case name. For example, CustomerGroupRepository is stored under customerGroupRepository.
	

-

Single Batch Job Strategy
	

An instance of every class extending the AbstractBatchJobStrategy class.
	

Each batch job strategy is registered under three names:

    Its camel-case name. For example, ProductImportStrategy is registered as productImportStrategy.
    batch_ followed by its identifier. For example, the ProductImportStrategy is registered under batch_product-import-strategy.
    batchType_ followed by its batch job type. For example, the ProductImportStrategy is registered under batchType_product-import.

	

-

All Batch Job Strategies
	

An array of all classes extending the AbstractBatchJobStrategy abstract class.
	

batchJobStrategies
	

-

Tax Calculation Strategy
	

An instance of the class implementing the ITaxCalculationStrategy interface.
	

taxCalculationStrategy
	

-

Cart Completion Strategy
	

An instance of the class extending the AbstractCartCompletionStrategy class.
	

cartCompletionStrategy
	

-

Price Selection Strategy
	

An instance of the class implementing the IPriceSelectionStrategy interface.
	

priceSelectionStrategy
	

-

Strategies
	

An instance of strategies that aren’t of the specific types mentioned above and that are under the strategies directory.
	

Its camel-case name.
	

-
Resolve Resources​

This section covers how to resolve resources from the dependency container to use them in API Routes and classes in general.
In API Routes​

To resolve resources, such as services, in API Routes, use the MedusaRequest object's scope.resolve method. The method receives the registration name of the resource as a parameter.

For example:

const productService: ProductService = req.scope.resolve(
  "productService"
)

Please note that in API Routes some resources, such as repositories, aren't available. Refer to the repositories documentation to learn how you can load them.
In Subscribers​

To resolve resources, such as services, in subscriber handler functions, use the container property of the handler function's parameter. The container has a method resolve which accepts the registration name of a resource as a parameter.

For example:

import { 
  ProductService,
  type SubscriberConfig, 
  type SubscriberArgs,
} from "@medusajs/medusa"

export default async function productUpdateHandler({ 
  data, eventName, container, pluginOptions, 
}: SubscriberArgs<Record<string, string>>) {
  const productService: ProductService = container.resolve(
    "productService"
  )

  // ...
}

// ...

In Classes​

In classes such as services or strategies, you can load resources in the constructor function using dependency injection. The constructor receives an object of dependencies as a first parameter. Each dependency in the object should use the registration name of the resource that should be injected to the class.

For example:

import { OrderService } from "@medusajs/medusa"

class OrderSubscriber {
  protected orderService: OrderService

  constructor({ orderService }) {
    this.orderService = orderService
  }
}


Events | Medusa
~4 minutes

In this document, you’ll learn what events are and why they’re useful in Medusa.
Overview​

Events are used in Medusa to inform different parts of the commerce ecosystem that this event occurred. For example, when an order is placed, the order.placed event is triggered, which informs notification services like SendGrid to send a confirmation email to the customer.

Tip

If you want to implement order confirmation emails, you can check this step-by-step guide.

The events system in Medusa is built on a publish/subscribe architecture. The Medusa core publish an event when an action takes place, and modules, plugins, or other forms of customizations can subscribe to that event. Subscribers can then perform a task asynchronously when the event is triggered.

Although the core implements the main logic behind the events system, you’ll need to use an event module that takes care of the publish/subscribe functionality such as subscribing and emitting events. Medusa provides modules that you can use both for development and production, including Redis and Local modules.
Database Transactions​

Transactions in Medusa ensure Atomicity, Consistency, Isolation, and Durability (ACID) guarantees for operations in the Medusa core.

In many cases, services typically update resources in the database and emit an event within a transactional operation. To ensure that these events don't cause data inconsistencies (for example, a plugin subscribes to an event to contact a third-party service, but the transaction fails) the concept of a staged job is introduced.

Instead of events being processed immediately, they're stored in the database as a staged job until they're ready. In other words, until the transaction has succeeded.

This rather complex logic is abstracted away from the consumers of the EventBusService, but here's an example of the flow when an API request is made:

    API request starts.
    Transaction is initiated.
    Service layer performs some logic.
    Events are emitted and stored in the database for eventual processing.
    Transaction is committed.
    API request ends.
    Events in the database become visible.

To pull staged jobs from the database, a separate enqueuer polls the database every three seconds to discover new visible jobs. These jobs are then added to the queue and processed as described in the Processing section earlier.
Emitting Events​

You can emit events in Medusa using the EventBusService. For example:

this.eventBusService.emit("custom-event", {
  // attach any data to the event
})

You can also emit more than one event:

this.eventBusService.emit([
  {
    eventName: "custom-event-1",
    data: {
      // attach any data to the event
    },
  },
  {
    eventName: "custom-event-2",
    data: {
      // attach any data to the event
    },
  },
])
Subscribers

In this document, you'll learn what Subscribers are in Medusa.
What are Subscribers

Subscribers register handlers for an events and allows you to perform an action when that event occurs. For example, if you want to send your customer an email when they place an order, then you can listen to the order.placed event and send the email when the event is emitted.

Natively in Medusa there are subscribers to handle different events. However, you can also create your own custom subscribers.

Custom subscribers are TypeScript or JavaScript files in your project's src/subscribers directory. Subscriber files must default export a handler function and export a configuration object.

Whenever an event is emitted, the subscriber’s handler function is executed. The handler function receives as a parameter an object that includes the data payload, among other parameters. For example, if an order is placed, the order.placed event is emitted and all the handlers receive the order ID in the data object.
Example Use Cases

Subscribers are useful in many use cases, including:

    Send a confirmation email to the customer when they place an order by subscribing to the order.placed event.
    Automatically assign new customers to a customer group by subscribing to the customer.created.
    Handle custom events that you emit


Events Reference

This document details all events in Medusa, when they are triggered, and what data your handler function will receive when the event is triggered.
Prerequisites

It is assumed you’re already familiar with Subscribers in Medusa and how to listen to events. You can then use the name of events from this documentation in your subscriber to listen to events.
Legend

Events in this document are listed under the entity they’re associated with. They’re listed in a table of three columns:

    Event Name: The name you use to subscribe a handler for the event.
    Description: When this event is triggered.
    Event Data Payload: The data your handler receives as a parameter.

Batch Jobs Events

This section holds all events related to batch jobs.

Event Name
	

Description
	

Event Data Payload

batch.created
	

Triggered when a batch job is created.
	

Object of the following format:

{
  id // string ID of batch job
}

batch.updated
	

Triggered when a batch job is updated.
	

Object of the following format:

{
  id // string ID of batch job
}

batch.canceled
	

Triggered when a batch job is canceled.
	

Object of the following format:

{
  id // string ID of batch job
}

batch.pre_processed
	

Triggered after the preProcessBatchJob of a batch job strategy is done executing.
	

Object of the following format:

{
  id // string ID of batch job
}

batch.confirmed
	

Triggered after the batch job is done pre-processing and the batch job is not in dry-run mode.
	

Object of the following format:

{
  id // string ID of batch job
}

batch.processing
	

Triggered when a batch job starts processing after it's confirmed.
	

Object of the following format:

{
  id // string ID of batch job
}

batch.completed
	

Triggered when a batch job is done processing and is completed.
	

Object of the following format:

{
  id // string ID of batch job
}

batch.failed
	

Triggered when an error occurs while running a batch job and the batch job fails.
	

Object of the following format:

{
  id // string ID of batch job
}

Cart Events

This section holds all events related to a cart.

Event Name
	

Description
	

Event Data Payload

cart.customer_updated
	

Triggered when a cart is associated with a different email than it was already associated with, or if a customer logs in after adding items to their cart as a guest.
	

The cart ID passed as a string parameter.

cart.created
	

Triggered when a cart is created.
	

Object of the following format:

{
  id // string ID of cart
}

cart.updated
	

Triggered when a cart and data associated with it (payment sessions, shipping methods, user details, etc…) are updated.
	

An object with at least the ID of the cart, however, in most cases the entire cart model is available. You can refer to the Cart entity for an idea of what fields to expect.
Claim Events

This section holds all events related to claims.

Event Name
	

Description
	

Event Data Payload

claim.created
	

Triggered when a claim is created.
	

Object of the following format:

{
  id, // string ID of claim
  no_notification // boolean indicating whether a notification should be sent
}

claim.updated
	

Triggered when a claim is updated.
	

Object of the following format:

{
  id, // string ID of claim
  no_notification // boolean indicating whether a notification should be sent
}

claim.canceled
	

Triggered when a claim is canceled.
	

Object of the following format:

{
  id, // string ID of claim
  no_notification // boolean indicating whether a notification should be sent
}

claim.fulfillment_created
	

Triggered when fulfillment is created for a claim.
	

Object of the following format:

{
  id, // string ID of claim
  fulfillment_id, // string ID of the fulfillment created
  no_notification // boolean indicating whether a notification should be sent
}

claim.shipment_created
	

Triggered when a claim fulfillment is set as “shipped”.
	

Object of the following format:

{
  id, // string ID of claim
  fulfillment_id, // string ID of the fulfillment created
  no_notification // boolean indicating whether a notification should be sent
}

claim.refund_processed
	

Triggered when a claim of type “refunded” has been refunded.
	

Object of the following format:

{
  id, // string ID of claim
  no_notification // boolean indicating whether a notification should be sent
}

Claim Item Events

This section holds all events related to claim items.

Event Name
	

Description
	

Event Data Payload

claim_item.created
	

Triggered when claim items are created and associated with a claim. This happens during the creation of claims.
	

Object of the following format:

{
  id // string ID of claim item
}

claim_item.updated
	

Triggered when a claim item is updated. This happens when a claim is updated.
	

Object of the following format:

{
  id // string ID of claim item
}

claim_item.canceled
	

Triggered when a claim is canceled.
	

Object of the following format:

{
  id // string ID of claim item
}

Currency Events

This section holds all events related to currencies.

Event Name
	

Description
	

Event Data Payload

currency.updated
	

Triggered when a currency is updated.
	

Object of the following format:

{
  code // string 3 character ISO code of the updated currency.
}

Customer Events

This section holds all events related to customers.

Event Name
	

Description
	

Event Data Payload

customer.created
	

Triggered when a customer is created.
	

The entire customer passed as an object. You can refer to the Customer entity for an idea of what fields to expect.

customer.updated
	

Triggered when a customer is updated including their information or password, or when a customer account is created that is associated with an existing email (for example, if a customer placed an order with their email as a guest, then created an account with that email).
	

The entire customer passed as an object. You can refer to the Customer entity for an idea of what fields to expect.

customer.password_reset
	

Triggered when a customer requests to reset their password.
	

Object of the following format:

{
  id, // string ID of customer
  email, // string email of the customer
  first_name, // string first name of the customer
  last_name, // string last name of the customer
  token // string reset password token
}

Discount Events

This section holds all events related to discounts.

Event Name
	

Description
	

Event Data Payload

discount.created
	

Triggered when a discount is created.
	

Object of the following format:

{
  id // string ID of discount
}

Draft Order Events

This section holds all events related to draft orders.

Event Name
	

Description
	

Event Data Payload

draft_order.created
	

Triggered when a draft order is created.
	

Object of the following format:

{
  id // string ID of draft order
}

draft_order.updated
	

Triggered when a draft order and data associated with it (email, billing address, discount, etc…) are updated.
	

Object of the following format:

{
  id // string ID of draft order
}

Gift Card Events

This section holds all events related to gift cards.

Event Name
	

Description
	

Event Data Payload

gift_card.created
	

Triggered when a gift card is created.
	

Object of the following format:

{
  id //string ID of gift card
}

Inventory Item Events

This section holds all events related to inventory items.

Event Name
	

Description
	

Event Data Payload

inventory-item.created
	

Triggered when an inventory item is created.
	

Object of the following format:

{
  id // string ID of the inventory item
}

inventory-item.updated
	

Triggered when an inventory item is updated.
	

Object of the following format:

{
  id // string ID of the inventory item
}

inventory-item.deleted
	

Triggered when an inventory item is deleted.
	

Object of the following format:

{
  id // string ID of the inventory item
}

Inventory Level Events

This section holds all events related to inventory levels.

Event Name
	

Description
	

Event Data Payload

inventory-level.created
	

Triggered when an inventory level is created.
	

Object of the following format:

{
  id // string ID of the inventory level
}

inventory-level.updated
	

Triggered when an inventory level is updated.
	

Object of the following format:

{
  id // string ID of the inventory level
}

inventory-level.deleted
	

Triggered when an inventory level is deleted, which can be done either directly using its ID or based on the ID of a location. The returned ID depends on how the inventory level was deleted.
	

Object of the following format:

{
  id // (optional) string ID of the inventory level, available if it was deleted directly
  location_id // (optional) string ID of location, available if level was deleted by location ID
}

Invite Events

This section holds all events related to invites.

Event Name
	

Description
	

Event Data Payload

invite.created
	

Triggered when an invite is created for a user to join the admin team.
	

Object of the following format:

{
  id // string ID of invite
  token, // string token generated to validate the invited user
  user_email // string email of invited user
}

Note Events

This section holds all events related to notes.

Event Name
	

Description
	

Event Data Payload

note.created
	

Triggered when a note is created.
	

Object of the following format:

{
  id // string ID of note
}

note.updated
	

Triggered when a note is updated.
	

Object of the following format:

{
  id // string ID of note
}

note.deleted
	

Triggered when a note is deleted.
	

Object of the following format:

{
  id // string ID of note
}

App Authentication Events

This section holds all events related to app authentications.
Note

Event names of app authentication are scoped specifically towards each application. When listening to these events, you must replace <APP> with the name of the application you’re targeting.

Event Name
	

Description
	

Event Data Payload

oauth.token_generated.<APP>
	

Triggered when a token is generated for an application.
	

The returned data from the method generateToken in the auth handler service of the application.

oauth.token_refreshed.<APP>
	

Triggered when the token of an application is refreshed.
	

The returned data from the method refreshToken in the auth handler service of the application.
Order Events

This section holds all events related to orders.

Event Name
	

Description
	

Event Data Payload

order.placed
	

Triggered when a new order is placed.
	

Object of the following format:

{
  id, // string ID of order
  no_notification // boolean indicating whether a notification should be sent
}

order.updated
	

Triggered when an order and data associated with it (shipping method, shipping address, etc…) are updated.
	

Object of the following format:

{
  id, // string ID of order
  no_notification // (optional) boolean indicating whether a notification should be sent
}

order.canceled
	

Triggered when an order is canceled.
	

Object of the following format:

{
  id, // string ID of order
  no_notification // boolean indicating whether a notification should be sent
}

order.completed
	

Triggered when an order is completed.
	

Object of the following format:

{
  id, // string ID of order
  no_notification // boolean indicating whether a notification should be sent
}

order.orders_claimed
	

Triggered when an order is claimed.
	

Object of the following format:

{
  id, //string ID of order
}

order.gift_card_created
	

Triggered when a gift card in an order is created.
	

Object of the following format:

{
  id // string ID of order
}

order.payment_captured
	

Triggered when the payment of an order is captured.
	

Object of the following format:

{
  id, // string ID of order
  no_notification // boolean indicating whether a notification should be sent
}

order.payment_capture_failed
	

Triggered when capturing the payment of an order fails.
	

Object of the following format:

{
  id, // string ID of order
  payment_id, // string ID of Payment
  error, // string error message
  no_notification // boolean indicating whether a notification should be sent
}

order.fulfillment_created
	

Triggered when fulfillment is created for an order.
	

Object of the following format:

{
  id, // string ID of order
  fulfillment_id, // string ID of fulfillment
  no_notification // boolean indicating whether a notification should be sent
}

order.shipment_created
	

Triggered when a shipment is created for fulfillment and the fulfillment is registered as “shipped”.
	

Object of the following format:

{
  id, // string ID of order
  fulfillment_id, // string ID of fulfillment
  no_notification // boolean indicating whether a notification should be sent
}

order.fulfillment_canceled
	

Triggered when fulfillment of an order is canceled.
	

Object of the following format:

{
  id, // string ID of order
  fulfillment_id, // string ID of fulfillment
  no_notification // boolean indicating whether a notification should be sent
}

order.return_requested
	

Triggered when a return of an order is requested.
	

Object of the following format:

{
  id, // string ID of order
  return_id, // string ID of return
  no_notification // (optional) boolean indicating whether a notification should be sent
}

order.items_returned
	

Triggered when the items of an order have been returned and the order has been registered as “returned”.
	

Object of the following format:

{
  id, // string ID of order
  return_id, // string ID of return
  no_notification // boolean indicating whether a notification should be sent
}

order.return_action_required
	

Triggered when the order is being registered as “returned” but there are additional actions required related to refunding the payment.
	

Object of the following format:

{
  id, // string ID of order
  return_id, // string ID of return
  no_notification // boolean indicating whether a notification should be sent
}

order.refund_created
	

Triggered when the order’s payment is refunded.
	

Object of the following format:

{
  id, // string ID of order
  refund_id, // string ID of refund
  no_notification // boolean indicating whether a notification should be sent
}

order.refund_failed
	

Triggered when the refund of the order’s payment fails.
	

Object of the following format:

{
  id, //string ID of order
}

order.swap_created
	

Triggered when a swap for an order is created.
	

Object of the following format:

{
  id, //string ID of order
}

Order Edit Events

This section holds all events related to order edits.

Event Name
	

Description
	

Event Data Payload

order-edit.created
	

Triggered when a order edit is created.
	

Object of the following format:

{
  id, //string ID of order edit
}

order-edit.updated
	

Triggered when an order edit is updated.
	

Object of the following format:

{
  id, //string ID of order edit
}

order-edit.canceled
	

Triggered when an order edit is canceled.
	

Object of the following format:

{
  id, //string ID of order edit
}

order-edit.declined
	

Triggered when an order edit is declined.
	

Object of the following format:

{
  id, //string ID of order edit
}

order-edit.requested
	

Triggered when an order edit is requested.
	

Object of the following format:

{
  id // string ID of order edit
}

order-edit.confirmed
	

Triggered when an order edit is confirmed.
	

Object of the following format:

{
  id, //string ID of order edit
}

Order Edit Item Changes Events

This section holds all events related to order edit item changes.

Event Name
	

Description
	

Event Data Payload

order-edit-item-change.CREATED
	

Triggered when an order edit item change is created.
	

{
  id // string ID of item change
}

order-edit-item-change.DELETED
	

Triggered when an order edit item change is deleted.
	

{
  id // string ID of item change
}

Payment Events

This section holds all events related to payment.

Event Name
	

Description
	

Event Data Payload

payment.created
	

Triggered when a payment is created.
	

The entire payment passed as an object. You can refer to the Payment entity for an idea of what fields to expect.

payment.updated
	

Triggered when a payment is updated.
	

The entire payment passed as an object. You can refer to the Payment entity for an idea of what fields to expect.

payment.payment_captured
	

Triggered when a payment is captured.
	

The entire payment passed as an object. You can refer to the Payment entity for an idea of what fields to expect.

payment.payment_capture_failed
	

Triggered when the capturing of a payment fails.
	

The entire payment passed as an object. You can refer to the Payment entity for an idea of what fields to expect.

In addition, an error object is passed within the same object as the Payment Processor:

{
  id, //string ID of payment
  //... other payment fields
  error: {
    name, //string
    message, //string
    stack, //(optional) string
  }
}

payment.payment_refund_created
	

Triggered when a refund of a payment is created.
	

The entire refund passed as an object. You can refer to the Refund entity for an idea of what fields to expect.

payment.payment_refund_failed
	

Triggered when a payment's refund fails.
	

The entire payment passed as an object. You can refer to the Payment entity for an idea of what fields to expect.
Payment Collection Events

This section holds all events related to payment collections.

Event Name
	

Description
	

Event Data Payload

payment-collection.created
	

Triggered when a payment collection is created.
	

The entire payment collection passed as an object. You can refer to the Payment Collection entity for an idea of what fields to expect.

payment-collection.updated
	

Triggered when a payment collection is update.
	

The entire payment collection passed as an object. You can refer to the Payment Collection entity for an idea of what fields to expect.

payment-collection.deleted
	

Triggered when a payment collection is deleted.
	

The entire payment collection passed as an object. You can refer to the Payment Collection entity for an idea of what fields to expect.

payment-collection.payment_authorized
	

Triggered when a payment collection is either marked authorized or its payment session is authorized.
	

The entire payment collection passed as an object. You can refer to the Payment Collection entity for an idea of what fields to expect.
Product Events

This section holds all events related to products.

Event Name
	

Description
	

Event Data Payload

product.created
	

Triggered when a product is created.
	

Object of the following format:

{
  id // string ID of product
}

product.updated
	

Triggered when a product and data associated with it (options, variant orders, etc…) is updated.
	

The entire product passed as an object. You can refer to the Product entity for an idea of what fields to expect.

In one case, when the /admin/products/{id} API Route is used to update the product, the payload is an object of the following format:

{
  id, // id of product
  fields // an array of field names that were updated
}

product.deleted
	

Triggered when a product is deleted.
	

Object of the following format:

{
  id // string ID of product
}

Product Category Events

This section holds all events related to product categories.
Note

Product Category feature is currently in beta mode and guarded by a feature flag. You can learn how to enable it in the Product Categories documentation.

Event Name
	

Description
	

Event Data Payload

product-category.created
	

Triggered when a product category is created.
	

Object of the following format:

{
  id, // string ID of category
}

product-category.updated
	

Triggered when a product category is updated.
	

Object of the following format:

{
  id, // string ID of category
}

product-category.deleted
	

Triggered when a product category is deleted.
	

Object of the following format:

{
  id, // string ID of category
}

Product Variant Events

This section holds all events related to product variants.

Event Name
	

Description
	

Event Data Payload

product-variant.created
	

Triggered when a product variant is created.
	

Object of the following format:

{
  id, // string ID of variant
  product_id // string ID of product
}

product-variant.updated
	

Triggered when a product variant is updated.
	

Object of the following format:

{
  id, // string ID of variant
  product_id, // string ID of product
  fields // array of names of updated fields
}

product-variant.deleted
	

Triggered when a product variant is deleted.
	

Object of the following format:

{
  id, // string ID of variant
  product_id, // string ID of product
  metadata // object of the `metadata` field of the variant
}

Publishable API Key Events

This section holds all events related to publishable API keys.

Event Name
	

Description
	

Event Data Payload

publishable_api_key.created
	

Triggered when a publishable API key is created.
	

Object of the following format:

{
  id // string ID of publishable API key
}

publishable_api_key.revoked
	

Triggered when a publishable API key is revoked.
	

Object of the following format:

{
  id // string ID of publishable API key
}

Region Events

This section holds all events related to regions.

Event Name
	

Description
	

Event Data Payload

region.created
	

Triggered when a region is created.
	

Object of the following format:

{
  id // string ID of region
}

region.updated
	

Triggered when a region or data associated with it (countries, fulfillment providers, etc…) are updated.
	

Object of the following format:

{
  id, // string ID of region
  fields // array of names of updated fields
}

region.deleted
	

Triggered when a region is deleted.
	

Object of the following format:

{
  id // string ID of region
}

Reservation Item Events

This section holds all events related to reservation items.

Event Name
	

Description
	

Event Data Payload

reservation-item.created
	

Triggered when a reservation item is created.
	

Object of the following format:

{
  id // string ID of the reservation item
}

reservation-item.updated
	

Triggered when an reservation item is updated.
	

Object of the following format:

{
  id // string ID of the reservation item
}

reservation-item.deleted
	

Triggered when a reservation item is deleted, which can be done either directly using its ID or based on the ID of a location or a line item. The returned ID depends on how the reservation item was deleted.
	

Object of the following format:

{
  id // (optional) string ID of the reservation item, available if it was deleted directly
  location_id // (optional) string ID of location, available if item was deleted by location ID
  line_item_id // (optional) string ID of line item, available if reservation item was deleted by line item ID
}

Sales Channel Events

This section holds all events related to sales channels.

Event Name
	

Description
	

Event Data Payload

sales_channel.created
	

Triggered when a sales channel is created.
	

Object of the following format:

{
  id // string ID of sales channel
}

sales_channel.updated
	

Triggered when a sales channel is updated
	

Object of the following format:

{
  id, //string ID of sales channel
}

sales_channel.deleted
	

Triggered when a sales channel is deleted.
	

Object of the following format:

{
  id // string ID of sales channel
}

Stock Location Events

This section holds all events related to stock locations.

Event Name
	

Description
	

Event Data Payload

stock-location.created
	

Triggered when a stock location is created.
	

Object of the following format:

{
  id // string ID of the stock location
}

stock-location.updated
	

Triggered when an stock location is updated.
	

Object of the following format:

{
  id // string ID of the stock location
}

stock-location.deleted
	

Triggered when a stock location is deleted.
	

Object of the following format:

{
  id // string ID of the stock location
}

Swap Events

This section holds all events related to swaps.

Event Name
	

Description
	

Event Data Payload

swap.created
	

Triggered when a swap is created.
	

Object of the following format:

{
  id, // string ID of swap
  no_notification // boolean indicating whether a notification should be sent
}

swap.received
	

Triggered when a swap is registered as received.
	

Object of the following format:

{
  id, // string ID of swap
  order_id, // string ID of order
  no_notification // boolean indicating whether a notification should be sent
}

swap.fulfillment_created
	

Triggered when fulfillment is created for a swap.
	

Object of the following format:

{
  id, // string ID of swap
  fulfillment_id, // string ID of fulfillment
  no_notification // boolean indicating whether a notification should be sent
}

swap.shipment_created
	

Triggered when a shipment is created for a swap and the fulfillment associated with it is set as “shipped”.
	

Object of the following format:

{
  id, // string ID of swap
  fulfillment_id, // string ID of fulfillment
  no_notification // boolean indicating whether a notification should be sent
}

swap.payment_completed
	

Triggered when payment is completed for a swap which happens when the cart associated with the swap is registered as completed.
	

Object of the following format:

{
  id, // string ID of swap
  no_notification // boolean indicating whether a notification should be sent
}

swap.payment_captured
	

Triggered when the payment is captured for a swap.
	

Object of the following format:

{
  id, // string ID of swap
  no_notification // boolean indicating whether a notification should be sent
}

swap.payment_capture_failed
	

Triggered when the capturing of the payment of a swap fails.
	

Object of the following format:

{
  id, // string ID of swap
  no_notification // boolean indicating whether a notification should be sent
}

swap.refund_processed
	

Triggered when a swap’s amount difference is processed and refunded.
	

Object of the following format:

{
  id, // string ID of swap
  no_notification // boolean indicating whether a notification should be sent
}

swap.process_refund_failed
	

Triggered when processing and refunding a swap’s amount difference fails.
	

Object of the following format:

{
  id, // string ID of swap
  no_notification // boolean indicating whether a notification should be sent
}

Token Events

This section holds all events related to tokens.

Event Name
	

Description
	

Event Data Payload

order-update-token.created
	

Triggered when a customer requests to claim an order and a token is created.
	

Object of the following format:

{
  old_email, //string email of order
  new_customer_id, //string ID of customer
  orders, //array of string IDs of orders
  token, //string token used for verification
}

User Events

This section holds all events related to users.

Event Name
	

Description
	

Event Data Payload

user.created
	

Triggered when a user is created.
	

Object of the following format:

{
  id // string ID of user
}

user.updated
	

Triggered when a user is updated.
	

Object of the following format:

{
  id // string ID of user
}

user.password_reset
	

Triggered when a user requests to reset their password.
	

Object of the following format:

{
  email, // string email of user requesting to reset their password
  token // token create to reset the password
}

user.deleted
	

Triggered when a user is deleted.
	

Object of the following format:

{
  id // string ID of user
}



How to Create a Subscriber

In this document, you’ll learn how to create a Subscriber in Medusa that listens to events to perform an action.
Tip

v1.18 of @medusajs/medusa introduced a new approach to create a subscriber. If you're looking for the old guide, you can find it here. However, it's highly recommended you follow this new approach, as the old one is deprecated.
Implementation

A subscriber is a TypeScript or JavaScript file that is created under src/subscribers. It can be created under subdirectories of src/subscribers as well. For example, you can place all subscribers to product events under the src/subscribers/products directory.

The subscriber file exports a default handler function, and the subscriber's configurations.

For example:
src/subscribers/product-update-handler.ts

import { 
  ProductService,
  type SubscriberConfig, 
  type SubscriberArgs, 
} from "@medusajs/medusa"

export default async function productUpdateHandler({ 
  data, eventName, container, pluginOptions, 
}: SubscriberArgs<Record<string, any>>) {
  const productService: ProductService = container.resolve(
    "productService"
  )

  const { id } = data

  const product = await productService.retrieve(id)

  // do something with the product...
}

export const config: SubscriberConfig = {
  event: ProductService.Events.UPDATED,
  context: {
    subscriberId: "product-update-handler",
  },
}

Subscriber Configuration

The exported configuration object of type SubscriberConfig must include the following properties:

    event: A string or an array of strings, each being the name of the event that the subscriber handler function listens to.
    context: An object that defines the context of the subscriber. It can accept any properties along with the subscriberId property. Learn more about the subscriber ID and the context object in this section.

Subscriber Handler Function

The default-export of the subscriber file is a handler function that is executed when the events specified in the exported configuration is triggered.

The function accepts a parameter of type SubscriberArgs, which has the following properties:

    data: The data payload of the emitted event. Its type is different for each event. So, make sure to check the events reference for the expected payload of the events your subscriber listens to. You can then pass the expected payload type as a type parameter to SubscriberArgs, for example, Record<string, string>.
    eventName: A string indicating the name of the event. This is useful if your subscriber listens to more than one event and you want to differentiate between them.
    container: The dependency container that allows you to resolve Medusa resources, such as services.
    pluginOptions: When the subscriber is created within a plugin, this object holds the plugin's options defined in the Medusa configurations.

Context with Subscriber ID

The context property of the subscriber configuration object is passed to the eventBusService. You can pass the subscriberId and any custom data in it.
Note

The subscriber ID is useful when there is more than one handler function attached to a single event or if you have multiple Medusa backends running. This allows the events bus service to differentiate between handler functions when retrying a failed one, avoiding retrying all subscribers which can lead to data inconsistencies or general unwanted behavior in your system.
Inferred Subscriber ID

If you don't pass a subscriber ID to the subscriber configurations, the name of the subscriber function is used as the subscriber ID. If the subscriber function is an anonymous function, the name of the subscriber file is used instead.
Caveats for Local Event Bus

If you use the event-bus-local as your event bus service, note the following:

    The subscriberId passed in the context is overwritten to a random ID when using event-bus-local. So, setting the subscriber ID in the context won't have any effect in this case.
    The eventName passed to the handler function will be undefined when using event-bus-local as it doesn't pass the event name properly.

Note

While the local event bus is a good option for development, it's highly recommended to use the Redis Event Module in production.
Retrieve Medusa Configurations

Within your subscriber, you may need to access the Medusa configuration exported from medusa-config.js. To do that, you can access configModule using dependency injection.

For example:
src/subscribers/product-update-handler.ts

import { 
  ProductService,
  type SubscriberConfig, 
  type SubscriberArgs,
  type ConfigModule, 
} from "@medusajs/medusa"

export default async function productUpdateHandler({ 
  data, eventName, container, pluginOptions, 
}: SubscriberArgs) {
  const configModule: ConfigModule = container.resolve(
    "configModule"
  )
  
  // ...
}

export const config: SubscriberConfig = {
  event: ProductService.Events.UPDATED,
  context: {
    subscriberId: "product-update-handler",
  },
}



Notification Architecture Overview

This document gives an overview of the notification architecture and how it works.
Introduction

Medusa provides a Notification API to mainly handle sending and resending notifications when an event occurs. For example, sending an email to the customer when they place an order.

The Notification architecture is made up of two main components: the Notification Provider and the Notification. Simply put, the Notification Provider handles the sending and resending of a Notification.
Notification Provider

A Notification Provider is a provider that handles sending and resending of notifications. You can either create and integrate your own provider or install a Notification Provider through a third-party plugin.

An example of a notification provider is SendGrid. When an order is placed, the SendGrid plugin sends an email to the customer.
How Notification Provider is Created

A Notification Provider is essentially a Medusa Service with a unique identifier, and it extends the NotificationService provided by the medusa-interfaces package. It can be created as part of a Plugin, or it can be created just as a Service file in your Medusa backend.

As a developer, you mainly work with the Notification Provider when integrating a third-party service that handles notifications in Medusa.

When you run your Medusa backend, the Notification Provider is registered in your backend. If it's a new Notification Provider, it will be inserted into the notification_provider table in your database.
NotificationProvider Entity Overview

The NotificationProvider entity only has 2 attributes: id and is_installed.

id is the value of the static property identifier defined inside the notification Service class.

is_installed indicates whether the Notification Provider is installed or not. When you install a Notification Provider, the value of this attribute is true.

If you installed a Notification provider and then removed the Service files or plugin that registered the Notification Provider, the Notification Provider remains in your database, but the value of the is_installed field changes to false.
Notification

A notification is a form of an alert sent to the customers or users to inform them of an action that has occurred. For example, if an order is placed, the notification, in this case, can be an email that confirms their order and lists the order details.

Notifications can take on other forms such as an SMS or a Slack message.
How Notification is Created

Notifications are created in the NotificationService class in Medusa’s core after the Notification has been handled by the Notification Provider.

The data and additional details that the Notification Provider returns to the NotificationService is used to fill some of the attributes of the Notification in the database.

A Notification also represents a resent notification. So, when a notification is resent, a new one is created that references the original Notification as a parent. This Notification is also created by the NotificationService class.
Notification Entity Overview

The two most important properties in the Notification entity are the to and data properties.

The to property is a string that represents the receiver of the Notification. For example, if the Notification was sent to an email address, the to property holds the email address the Notification was sent to.

The to property can alternatively be a phone number or a chat username. It depends on the Notification Provider and how it sends the Notification.

The data property is an object that holds all the data necessary to send the Notification. For example, in the case of an order confirmation Notification, it can hold data related to the order.

The data property is useful when a notification is resent later. The same data can be used to resend the notification.

In the case of resent notifications, the resent notification has a parent_id set to the ID of the original Notification. The value of the parent_id property in the original Notification is null.

The Notification entity has some properties that determine the context of this Notification. This includes the event_name property which is the event that triggered the sending of this notification.

Additionally, the resource_type property is used to determine what resource this event is associated with. For example, if the event_name is order.placed, the resource_type is order.

You can also access the specific resource using the resource_id property, which is the ID of the resource. So, in case of the order.placed event, the resource_id is the ID of the order that was created.

The Notification entity also includes properties related to the receiver of the Notification. In case the receiver is a customer, the customer_id property is used to identify which customer.
Automating Flows with Notifications

With Medusa you can create notifications as a reaction to a wide spectrum of events, allowing you to automate communication and processes.

An example of a flow that can be implemented using Medusa's Notification API is automated return flows:

    A customer requests a return by sending a POST request to the /store/returns API Route.
    The Notification Provider listens to the order.return_requested event and sends an email to the customer with a return invoice and return label generated by the Fulfillment Provider.
    The customer returns the items triggering the return.received event.
    The Notification Provider listens to the return.received event and sends an email to the customer with confirmation that their items have been received and that a refund has been issued.


How to Create a Notification Provider

In this document, you’ll learn how to create a notification provider in the Medusa backend and the methods you must implement in it. Learn more about the notification architecture in this documentation
Overview
Prerequisites

Before creating a Notification Provider, install an event bus module.

A Notification Provider is a provider that handles sending and resending of notifications.

To create a Notification Provider, create a TypeScript or JavaScript file in src/services. The name of the file is the name of the provider (for example, sendgrid.ts). The file must export a class that extends the AbstractNotificationService class imported from @medusajs/medusa.

For example, create the file src/services/email-sender.ts with the following content:
src/services/email-sender.ts

import { AbstractNotificationService } from "@medusajs/medusa"
import { EntityManager } from "typeorm"

class EmailSenderService extends AbstractNotificationService {
  protected manager_: EntityManager
  protected transactionManager_: EntityManager

  sendNotification(
    event: string,
    data: unknown,
    attachmentGenerator: unknown
  ): Promise<{
      to: string;
      status: string;
      data: Record<string, unknown>;
    }> {
    throw new Error("Method not implemented.")
  }
  resendNotification(
    notification: unknown,
    config: unknown,
    attachmentGenerator: unknown
  ): Promise<{
      to: string;
      status: string;
      data: Record<string, unknown>;
    }> {
    throw new Error("Method not implemented.")
  }

}

export default EmailSenderService

Identifier Property

The NotificationProvider entity has 2 properties: identifier and is_installed. The value of the identifier property in the notification provider class is used when the Notification Provider is created in the database.

The value of this property is also used later when you want to subscribe the Notification Provider to events in a Loader.

For example:

class EmailSenderService extends AbstractNotificationService {
  static identifier = "email-sender"
  // ...
}

constructor

You can use the constructor of your notification provider to access the different services in Medusa through dependency injection.

You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs, you can initialize it in the constructor and use it in other methods in the service.

Additionally, if you’re creating your notification provider as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin, you can access it in the constructor.
Example

// ...
import { AbstractNotificationService, OrderService } from "@medusajs/medusa"
import { EntityManager } from "typeorm"

class EmailSenderService extends AbstractNotificationService {
  // ...
  protected orderService: OrderService

  constructor(container, options) {
    super(container)
    // you can access options here in case you're
    // using a plugin

    this.orderService = container.orderService

    // you can also initialize a client that
    // communicates with a third-party service.
    this.client = new Client(options)
  }

  // ...
}

export default EmailSenderService

Parameters
containerRecord<string, unknown>Required
An instance of MedusaContainer that allows you to access other resources, such as services, in your Medusa backend.
configRecord<string, unknown>
If this notification provider is created in a plugin, the plugin's options are passed in this parameter.
Methods
sendNotification

When an event is triggered that your Notification Provider is registered as a handler for, the NotificationService in the Medusa backend executes this method of your Notification Provider.

In this method, you can perform the necessary operation to send the Notification. For example, you can send an email to the customer when they place an order.
Example

class EmailSenderService extends AbstractNotificationService {
  // ...
  async sendNotification(
    event: string,
    data: any,
    attachmentGenerator: unknown
  ): Promise<{
      to: string;
      status: string;
      data: Record<string, unknown>;
    }> {
    if (event === "order.placed") {
      // retrieve order
      const order = await this.orderService.retrieve(data.id)
      // TODO send email

      console.log("Notification sent")
      return {
        to: order.email,
        status: "done",
        data: {
          // any data necessary to send the email
          // for example:
          subject: "You placed a new order!",
          items: order.items,
        },
      }
    }
  }
  // ...
}

Parameters
eventstringRequired
The name of the event that was triggered. For example, order.placed.
dataunknownRequired
The data payload of the event that was triggered. For example, if the order.placed event is triggered, the eventData object contains the property id which is the ID of the order that was placed. You can refer to the Events reference for information on all events and their payloads.
attachmentGeneratorunknownRequired
If you’ve previously register an attachment generator to the NotificationService using the registerAttachmentGenerator method, you have access to it here. You can use the attachmentGenerator to generate on-demand invoices or other documents. The default value of this parameter is null.
Returns
PromisePromise<ReturnedData>Required
The sending details.
resendNotification

This method is used to resend notifications, which is typically triggered by the Resend Notification API Route.
Example

class EmailSenderService extends AbstractNotificationService {
  // ...
  async resendNotification(
    notification: any,
    config: any,
    attachmentGenerator: unknown
  ): Promise<{
      to: string;
      status: string;
      data: Record<string, unknown>;
    }> {
    // check if the receiver should be changed
    const to: string = config.to || notification.to

    // TODO resend the notification using the same data
    // that is saved under notification.data

    console.log("Notification resent")
    return {
      to,
      status: "done",
      data: notification.data, // make changes to the data
    }
  }
}

Parameters
notificationunknownRequired
The original Notification record that was created after you sent the notification with sendNotification. It includes the to and data attributes which are populated originally using the to and data properties of the object you return in sendNotification.
configunknownRequired
The new configuration used to resend the notification. The Resend Notification API Route, allows you to pass a new to field. If specified, it will be available in this config object.
attachmentGeneratorunknownRequired
f you’ve previously register an attachment generator to the NotificationService using the registerAttachmentGenerator method, you have access to it here. You can use the attachmentGenerator to generate on-demand invoices or other documents. The default value of this parameter is null.
Returns
PromisePromise<ReturnedData>Required
The resend details.
Subscribe with Loaders

After creating your Notification Provider Service, you must create a Loader that registers this Service as a notification handler of events.

For example, to register the email-sender Notification Provider as a handler for the order.placed event, create the file src/loaders/notification.ts with the following content:
src/loaders/notification.ts

import { 
  MedusaContainer, 
  NotificationService,
} from "@medusajs/medusa"

export default async (
  container: MedusaContainer
): Promise<void> => {
  const notificationService = container.resolve<
    NotificationService
  >("notificationService")

  notificationService.subscribe(
    "order.placed", 
    "email-sender"
  )
}

This loader accesses the notificationService through the MedusaContainer. The notificationService has a subscribe method that accepts 2 parameters. The first one is the name of the event to subscribe to, and the second is the identifier of the Notification Provider that's subscribing to that event.
Test Sending a Notification

Make sure you have an event bus module configured in your Medusa backend. You can learn more on how to do that in the Configurations guide.

Then:

1. Run the build command in the root directory of your Medusa backend:

    npm
    Yarn
    pnpm

yarn build

2. Start your Medusa backend:

    npm
    Yarn
    pnpm

npx medusa develop

3. Place an order either using the REST APIs or using the storefront.

4. After placing an order, you can see in your console the message “Notification Sent”. If you added your own notification sending logic, you should receive an email or alternatively the type of notification you’ve set up.
Test Resending a Notification

To test resending a notification:

    Retrieve the ID of the notification you just sent using the List Notifications API Route. You can pass as a body parameter the to or event_name parameters to filter out the notification you just sent.

    Send a request to the Resend Notification API Route using the ID retrieved from the previous request. You can pass the to parameter in the body to change the receiver of the notification.

    You should see the message “Notification Resent” in your console.