From dd9a6442727a5f93a5c72fe3fa220698cc057372 Mon Sep 17 00:00:00 2001 From: Shahed Nasser Date: Fri, 1 Aug 2025 16:17:43 +0300 Subject: [PATCH] docs: added guide on sending invite user emails + updates to reset password guide (#13122) * docs: added guide on sending invite user emails + updates to reset password guide * fix vale error --- www/apps/book/public/llms-full.txt | 884 +++++++++++++++++- .../auth/reset-password/page.mdx | 453 ++++++++- .../cart/cart-totals/page.mdx | 6 +- .../order/order-totals/page.mdx | 10 +- .../user/invite-user-subscriber/page.mdx | 453 +++++++++ .../notification/local/page.mdx | 4 - .../app/integrations/guides/resend/page.mdx | 9 + www/apps/resources/generated/edit-dates.mjs | 9 +- www/apps/resources/generated/files-map.mjs | 4 + .../generated-commerce-modules-sidebar.mjs | 16 +- .../generated-how-to-tutorials-sidebar.mjs | 10 +- ...nerated-infrastructure-modules-sidebar.mjs | 24 +- .../generated/generated-tools-sidebar.mjs | 8 - www/apps/resources/sidebars/user.mjs | 5 + .../app/settings/users/invites/page.mdx | 2 +- www/apps/user-guide/generated/edit-dates.mjs | 2 +- www/packages/tags/src/tags/how-to.ts | 6 +- www/packages/tags/src/tags/notification.ts | 6 +- www/packages/tags/src/tags/server.ts | 6 +- 19 files changed, 1775 insertions(+), 142 deletions(-) create mode 100644 www/apps/resources/app/commerce-modules/user/invite-user-subscriber/page.mdx diff --git a/www/apps/book/public/llms-full.txt b/www/apps/book/public/llms-full.txt index 0c7819ac84..968cfe30ca 100644 --- a/www/apps/book/public/llms-full.txt +++ b/www/apps/book/public/llms-full.txt @@ -24919,23 +24919,72 @@ Medusa provides the following authentication providers out-of-the-box. You can u *** -# How to Handle Password Reset Token Event +# Send Reset Password Email Notification -In this guide, you'll learn how to handle the `auth.password_reset` event, which is emitted when a request is sent to the [Generate Reset Password Token API route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#generate-reset-password-token-route/index.html.md). +In this guide, you'll learn how to handle the `auth.password_reset` event to send a reset password email (or other notification type) to users. Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/reset-password/index.html.md) to learn how to reset your user admin password using the dashboard. -You'll create a subscriber that listens to the event. When the event is emitted, the subscriber sends an email notification to the user. +## Reset Password Flow Overview -### Prerequisites +![Diagram showcasing the reset password flow detailed below](https://res.cloudinary.com/dza7lstvk/image/upload/v1754050032/Medusa%20Resources/reset-password_qheixj.jpg) -- [A notification provider module, such as SendGrid](https://docs.medusajs.com/infrastructure-modules/notification/sendgrid/index.html.md) +Users of any actor type (admin, customer, or custom actor type) can request to reset their password. The flow for resetting a password is as follows: -## 1. Create Subscriber +1. The user requests to reset their password either through the frontend (for example, [Medusa Admin](https://docs.medusajs.com/user-guide/reset-password/index.html.md)) or the [Generate Reset Password Token API route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#generate-reset-password-token-route/index.html.md). +2. The Medusa application generates a password reset token and emits the `auth.password_reset` event. + - At this point, you can handle the event to send a notification to the user with instructions on how to reset their password. +3. The user receives the notification and clicks on the link to reset their password. + - The user can reset their password either through the frontend (for example, [Medusa Admin](https://docs.medusajs.com/user-guide/reset-password/index.html.md)) or the [Reset Password API route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#reset-password-route/index.html.md). -The first step is to create a subscriber that listens to the `auth.password_reset` and sends the user a notification with instructions to reset their password. +In this guide, you'll implement a subscriber that handles the `auth.password_reset` event to send an email notification to the user with instructions on how to reset their password. -Create the file `src/subscribers/handle-reset.ts` with the following content: +After adding the subscriber, you will have a complete reset password flow you can utilize using the Medusa admin, storefront, or API routes. + +*** + +## Prerequisites: Notification Module Provider + +To send an email or notification to the user, you must have a Notification Module Provider set up. + +Medusa provides providers like [SendGrid](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/notification/sendgrid/index.html.md) and [Resend](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/integrations/guides/resend/index.html.md), and you can also [create your own custom provider](https://docs.medusajs.com/references/notification-provider-module/index.html.md). + +Refer to the [Notification Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/notification#what-is-a-notification-module-provider/index.html.md) documentation for a list of available providers and how to set them up. + +### Testing with the Local Notification Module Provider + +For testing purposes, you can use the [Local Notification Module Provider](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/notification/local/index.html.md) by adding this to your `medusa-config.ts`: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/medusa/notification", + options: { + providers: [ + // ... + { + resolve: "@medusajs/medusa/notification-local", + id: "local", + options: { + channels: ["email"], + }, + }, + ], + }, + }, + ], +}) +``` + +The Local provider logs email details to your terminal instead of sending actual emails, which is useful for development and testing. + +*** + +## Create the Reset Password Subscriber + +To create a [subscriber](https://docs.medusajs.com/docs/learn/fundamentals/events-and-subscribers/index.html.md) that handles the `auth.password_reset` event, create the file `src/subscribers/password-reset.ts` with the following content: ```ts title="src/subscribers/handle-reset.ts" highlights={highlights} collapsibleLines="1-6" expandMoreLabel="Show Imports" import { @@ -24955,18 +25004,27 @@ export default async function resetPasswordTokenHandler({ const notificationModuleService = container.resolve( Modules.NOTIFICATION ) + const config = container.resolve("configModule") - const urlPrefix = actor_type === "customer" ? - "https://storefront.com" : - "https://admin.com/app" + let urlPrefix = "" + + if (actor_type === "customer") { + urlPrefix = config.admin.storefrontUrl || "https://storefront.com" + } else { + const backendUrl = config.admin.backendUrl !== "/" ? config.admin.backendUrl : + "http://localhost:9000" + const adminPath = config.admin.path + urlPrefix = `${backendUrl}${adminPath}` + } await notificationModuleService.createNotifications({ to: email, channel: "email", - template: "reset-password-template", + // TODO replace with template ID in notification provider + template: "password-reset", data: { // a URL to a frontend application - url: `${urlPrefix}/reset-password?token=${token}&email=${email}`, + reset_url: `${urlPrefix}/reset-password?token=${token}&email=${email}`, }, }) } @@ -24976,7 +25034,7 @@ export const config: SubscriberConfig = { } ``` -You subscribe to the `auth.password_reset` event. The event has a data payload object with the following properties: +The subscriber receives the following data through the event payload: - `entity_id`: The identifier of the user. When using the `emailpass` provider, it's the user's email. - `token`: The token to reset the user's password. @@ -24984,41 +25042,46 @@ You subscribe to the `auth.password_reset` event. The event has a data payload o This event's payload previously had an `actorType` field. It was renamed to `actor_type` after [Medusa v2.0.7](https://github.com/medusajs/medusa/releases/tag/v2.0.7). -In the subscriber, you: +### Reset Password URL -- Decide the frontend URL based on whether the user is a customer or admin user by checking the value of `actor_type`. -- Resolve the Notification Module and use its `createNotifications` method to send the notification. -- You pass to the `createNotifications` method an object having the following properties: - - `to`: The identifier to send the notification to, which in this case is the email. - - `channel`: The channel to send the notification through, which in this case is email. - - `template`: The template ID in the third-party service. - - `data`: The data payload to pass to the template. You pass the URL to redirect the user to. You must pass the token and email in the URL so that the frontend can send them later to the Medusa application when reseting the password. +Based on the user's actor type, you set the URL prefix to redirect the user to the appropriate frontend page to reset their password: -*** +- If the user is a customer, you set the URL prefix to the storefront URL. +- If the user is an admin, you set the URL prefix to the backend URL, which is constructed from the `config.admin.backendUrl` and `config.admin.path` values. -## 2. Test it Out: Generate Reset Password Token +Note that the Medusa Admin has a reset password form at `/reset-password?token={token}&email={email}`. -To test the subscriber out, send a request to the `/auth/{actor_type}/{auth_provider}/reset-password` API route, replacing `{actor_type}` and `{auth_provider}` with the user's actor type and provider used for authentication respectively. +### Notification Configurations -For example, to generate a reset password token for an admin user using the `emailpass` provider, send the following request: +For the notification, you can configure the following fields: -```bash -curl --location 'http://localhost:9000/auth/user/emailpass/reset-password' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "identifier": "admin-test@gmail.com" -}' +- `to`: The identifier to send the notification to, which in this case is the email. +- `channel`: The channel to send the notification through, which in this case is email. +- `template`: The template ID of the email to send. This ID depends on the Notification Module provider you use. For example, if you use SendGrid, this would be the ID of the SendGrid template. + - Refer to the [Example Notification Templates](#example-notification-templates) section below for examples of notification templates to use. +- `data`: The data payload to pass to the template. You can pass additional fields, if necessary. + +### Test It Out + +After you set up the Notification Module Provider, create a template in the provider, and create the subscriber, you can test the reset password flow. + +Start the Medusa application with the following command: + +```bash npm2yarn +npm run dev ``` -In the request body, you must pass an `identifier` parameter. Its value is the user's identifier, which is the email in this case. +Then, open the Medusa Admin (locally at `http://localhost:9000/app`) and click on "Reset" in the login form. Then, enter the email of the user you want to reset the password for. -If the token is generated successfully, the request returns a response with `201` status code. In the terminal, you'll find the following message indicating that the `auth.password_reset` event was emitted and your subscriber ran: +Once you reset the password, you should see that the `auth.password_reset` event is emitted in the server's logs: -```plain +```bash info: Processing auth.password_reset which has 1 subscribers ``` -The notification is sent to the user with the frontend URL to enter a new password. +If you're using an email Notification Module Provider, check the user's email inbox for the reset password email with the link to reset their password. + +If you're using the Local provider, check your terminal for the logged email details. *** @@ -25028,10 +25091,305 @@ In your frontend, you must have a page that accepts `token` and `email` query pa The page shows the user password fields to enter their new password, then submits the new password, token, and email to the [Reset Password Route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#reset-password-route/index.html.md). +The Medusa Admin already has a reset password page at `/reset-password?token={token}&email={email}`. So, you only need to implement this page in your storefront or custom admin dashboard. + ### Examples - [Storefront Guide: Reset Customer Password](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/reset-password/index.html.md) +*** + +## Example Notification Templates + +The following section provides example notification templates for some Notification Module Providers. + +### SendGrid + +Refer to the [SendGrid Notification Module Provider](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/notification/sendgrid/index.html.md) documentation for more details on how to set up SendGrid. + +The following HTML template can be used with SendGrid to send a reset password email: + +```html + + + + + + Reset Your Password + + + +
+
+

Reset Your Password

+
+ +
+

+ Hello{{#if email}} {{email}}{{/if}}, +

+

+ We received a request to reset your password. Click the button below to create a new password for your account. +

+
+ + + +
+

+ Or copy and paste this URL into your browser: +

+ + {{reset_url}} + +
+ +
+

+ This password reset link will expire soon for security reasons. +

+

+ If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged. +

+
+ + +
+ + +``` + +Make sure to pass the `reset_url` variable to the template, which contains the URL to reset the password. + +You can also customize the template further to show other information. + +### Resend + +If you've integrated Resend as explained in the [Resend Integration Guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/integrations/guides/resend/index.html.md), you can add a new template for password reset emails at `src/modules/resend/emails/password-reset.tsx`: + +```tsx title="src/modules/resend/emails/password-reset.tsx" +import { + Text, + Container, + Heading, + Html, + Section, + Tailwind, + Head, + Preview, + Body, + Link, + Button, +} from "@react-email/components" + +type PasswordResetEmailProps = { + reset_url: string + email?: string +} + +function PasswordResetEmailComponent({ reset_url, email }: PasswordResetEmailProps) { + return ( + + + Reset your password + + + +
+ + Reset Your Password + +
+ +
+ + Hello{email ? ` ${email}` : ""}, + + + We received a request to reset your password. Click the button below to create a new password for your account. + +
+ +
+ +
+ +
+ + Or copy and paste this URL into your browser: + + + {reset_url} + +
+ +
+ + This password reset link will expire soon for security reasons. + + + If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged. + +
+ +
+ + For security reasons, never share this reset link with anyone. If you're having trouble with the button above, copy and paste the URL into your web browser. + +
+
+ +
+ + ) +} + +export const passwordResetEmail = (props: PasswordResetEmailProps) => ( + +) + +// Mock data for preview/development +const mockPasswordReset: PasswordResetEmailProps = { + reset_url: "https://your-app.com/reset-password?token=sample-reset-token-123", + email: "user@example.com", +} + +export default () => +``` + +Feel free to customize the email template further to match your branding and style, or to add additional information. + +Then, in the Resend Module's service at `src/modules/resend/service.ts`, add the new template to the `templates` object and `Templates` type: + +```ts title="src/modules/resend/service.ts" +// other imports... +import { passwordResetEmail } from "./emails/password-reset" + +enum Templates { + // ... + PASSWORD_RESET = "password-reset", +} + +const templates: {[key in Templates]?: (props: unknown) => React.ReactNode} = { + // ... + [Templates.PASSWORD_RESET]: passwordResetEmail, +} +``` + +Finally, find the `getTemplateSubject` function in the `ResendNotificationProviderService` and add a case for the `USER_INVITED` template: + +```ts title="src/modules/resend/service.ts" +class ResendNotificationProviderService extends AbstractNotificationProviderService { + // ... + + private getTemplateSubject(template: Templates) { + // ... + switch (template) { + // ... + case Templates.PASSWORD_RESET: + return "Reset Your Password" + } + } +} +``` + # Retrieve Cart Totals using Query @@ -25135,7 +25493,7 @@ const { data: [cart] } = await query.graph({ ], filters: { id: "cart_123", // Specify the cart ID - } + }, }) ``` @@ -25289,7 +25647,7 @@ const { data: [cart] } = await query.graph({ fields: ["*"], filters: { id: "cart_123", - } + }, }) // cart doesn't include cart totals @@ -25321,7 +25679,7 @@ const { data: [cart] } = await query.graph({ fields: ["*", "total"], filters: { id: "cart_123", - } + }, }) // cart doesn't include cart totals @@ -29154,7 +29512,7 @@ export const myWorkflow = createWorkflow( "gift_card_tax_total", "items.*", "shipping_methods.*", - "summary.*" + "summary.*", ], filters: { id: "order_123", // Specify the order ID @@ -29198,11 +29556,11 @@ const { data: [order] } = await query.graph({ "gift_card_tax_total", "items.*", "shipping_methods.*", - "summary.*" + "summary.*", ], filters: { id: "order_123", // Specify the order ID - } + }, }) ``` @@ -29388,7 +29746,7 @@ const { data: [order] } = await query.graph({ fields: ["*"], filters: { id: "order_123", - } + }, }) // order doesn't include order totals @@ -29420,7 +29778,7 @@ const { data: [order] } = await query.graph({ fields: ["*", "total"], filters: { id: "order_123", - } + }, }) // order doesn't include order totals @@ -36505,6 +36863,431 @@ You can use Medusa's default tax provider or create a custom one, allowing you t Learn more about tax providers in the [Tax Provider](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/tax/tax-provider/index.html.md) guide. +# Send Invite User Email Notification + +In this guide, you'll learn how to handle the `invite.created` and `invite.resent` events to send an invite email (or other notification type) to the user. + +Refer to the [Manage Invites](https://docs.medusajs.com/user-guide/settings/users/invites/index.html.md) user guide to learn how to manage invites using the Medusa Admin. + +## User Invite Flow Overview + +![Diagram showcasing the user invite flow detailed below](https://res.cloudinary.com/dza7lstvk/image/upload/v1754047213/Medusa%20Resources/invite-user_uqspsv.jpg) + +Admin users can add new users to their store by sending them an invite. The flow for inviting users is as follows: + +1. An admin user invites a user either through the [Medusa Admin](https://docs.medusajs.com/user-guide/settings/users/invites/index.html.md) or using the [Create Invite API route](https://docs.medusajs.com/api/admin#invites_postinvites). +2. The invite is created and the `invite.created` event is emitted. + - At this point, you can handle the event to send an email or notification to the user. +3. The invited user receives the invite and can accept it, which creates a new user. + - The invited user can accept the invite either through the Medusa Admin or using the [Accept Invite API route](https://docs.medusajs.com/api/admin#invites_postinvitesaccept). + +The admin user can also resend the invite if the invited user doesn't receive the invite or doesn't accept it before expiry, which emits the `invite.resent` event. + +In this guide, you'll implement a subscriber that handles the `invite.created` and `invite.resent` events to send an email to the user. + +After adding the subscriber, you will have a complete user invite flow that you can utilize either through the Medusa Admin or using the Admin APIs. + +*** + +## Prerequisites: Notification Module Provider + +To send an email or notification to the user, you must have a Notification Module Provider set up. + +Medusa provides providers like [SendGrid](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/notification/sendgrid/index.html.md) and [Resend](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/integrations/guides/resend/index.html.md), and you can also [create your own custom provider](https://docs.medusajs.com/references/notification-provider-module/index.html.md). + +Refer to the [Notification Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/notification#what-is-a-notification-module-provider/index.html.md) documentation for a list of available providers and how to set them up. + +### Testing with the Local Notification Module Provider + +For testing purposes, you can use the [Local Notification Module Provider](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/notification/local/index.html.md) by adding this to your `medusa-config.ts`: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/medusa/notification", + options: { + providers: [ + // ... + { + resolve: "@medusajs/medusa/notification-local", + id: "local", + options: { + channels: ["email"], + }, + }, + ], + }, + }, + ], +}) +``` + +The Local provider logs email details to your terminal instead of sending actual emails, which is useful for development and testing. + +*** + +## Create the Invite User Subscriber + +To create a [subscriber](https://docs.medusajs.com/docs/learn/fundamentals/events-and-subscribers/index.html.md) that handles the `invite.created` and `invite.resent` events, create the file `src/subscribers/user-invited.ts` with the following content: + +```ts title="src/subscribers/user-invited.ts" highlights={highlights} +import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework" + +export default async function inviteCreatedHandler({ + event: { data }, + container, +}: SubscriberArgs<{ + id: string +}>) { + const query = container.resolve("query") + const notificationModuleService = container.resolve( + "notification" + ) + const config = container.resolve("configModule") + + const { data: [invite] } = await query.graph({ + entity: "invite", + fields: [ + "email", + "token", + ], + filters: { + id: data.id, + }, + }) + + const backend_url = config.admin.backendUrl !== "/" ? config.admin.backendUrl : + "http://localhost:9000" + const adminPath = config.admin.path + + await notificationModuleService.createNotifications({ + to: invite.email, + // TODO replace with template ID in notification provider + template: "user-invited", + channel: "email", + data: { + invite_url: `${backend_url}${adminPath}/invite?token=${invite.token}`, + }, + }) +} + +export const config: SubscriberConfig = { + event: [ + "invite.created", + "invite.resent", + ], +} +``` + +The subscriber receives the ID of the invite in the event payload. You resolve [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md) to fetch the invite details, including the email and token. + +### Invite URL + +You then use the Notification Module's service to send an email to the user with the invite link. The invite link is constructed using: + +1. The backend URL of the Medusa application, since the Medusa Admin is hosted on the same URL. +2. The admin path, which is the path to the Medusa Admin (for example, `/app`). + +Note that the Medusa Admin has an invite form at `/invite?token=`, which accepts the invite token as a query parameter. + +### Notification Configurations + +For the notification, you can configure the following fields: + +- `template`: The template ID of the email to send. This ID depends on the Notification Module provider you use. For example, if you use SendGrid, this would be the ID of the SendGrid template. + - Refer to the [Example Notification Templates](#example-notification-templates) section below for examples of notification templates to use. +- `channel`: The channel to send the notification through. In this case, it's set to `email`. +- `data`: The data to pass to the notification template. You can add additional fields as needed, such as the invited user's email. + +### Test It Out + +After you set up the Notification Module Provider, create a template in the provider, and create the subscriber, you can test the full invite flow. + +Start the Medusa application with the following command: + +```bash npm2yarn +npm run dev +``` + +Then, open the Medusa Admin (locally at `http://localhost:9000/app`) and go to Settings → Users. Create an invite as explained in the [Manage Invites](https://docs.medusajs.com/user-guide/settings/users/invites/index.html.md) user guide. + +Once you create the invite, you should see that the `invite.created` event is emitted in the server's logs: + +```bash +info: Processing invite.created which has 1 subscribers +``` + +If you're using an email Notification Module Provider, check the recipient's email inbox for the invite email with the link to accept the invite. + +If you're using the Local provider, check your terminal for the logged email details. + +*** + +## Example Notification Templates + +The following section provides example notification templates for some Notification Module Providers. + +### SendGrid + +Refer to the [SendGrid Notification Module Provider](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/notification/sendgrid/index.html.md) documentation for more details on how to set up SendGrid. + +The following HTML template can be used with SendGrid to send an invite email: + +```html + + + + + + You're Invited! + + + +
+
+

You're Invited!

+
+ +
+

+ Hello{{#if email}} {{email}}{{/if}}, +

+

+ You've been invited to join our platform. Click the button below to accept your invitation and set up your account. +

+
+ + + +
+

+ Or copy and paste this URL into your browser: +

+ + {{invite_url}} + +
+ + +
+ + +``` + +Make sure to pass the `invite_url` variable to the template, which contains the URL to accept the invite. + +You can also customize the template further to show other information, such as the user's email. + +### Resend + +If you've integrated Resend as explained in the [Resend Integration Guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/integrations/guides/resend/index.html.md), you can add a new template for user invites at `src/modules/resend/emails/user-invited.tsx`: + +```tsx title="src/modules/resend/emails/user-invited.tsx" +import { + Text, + Container, + Heading, + Html, + Section, + Tailwind, + Head, + Preview, + Body, + Link, + Button, +} from "@react-email/components" + +type UserInvitedEmailProps = { + invite_url: string + email?: string +} + +function UserInvitedEmailComponent({ invite_url, email }: UserInvitedEmailProps) { + return ( + + + You've been invited to join our platform + + + +
+ + You're Invited! + +
+ +
+ + Hello{email ? ` ${email}` : ""}, + + + You've been invited to join our platform. Click the button below to accept your invitation and set up your account. + +
+ +
+ +
+ +
+ + Or copy and paste this URL into your browser: + + + {invite_url} + +
+ +
+ + If you weren't expecting this invitation, you can ignore this email. + +
+
+ +
+ + ) +} + +export const userInvitedEmail = (props: UserInvitedEmailProps) => ( + +) + +// Mock data for preview/development +const mockInvite: UserInvitedEmailProps = { + invite_url: "https://your-app.com/app/invite/sample-token-123", + email: "user@example.com", +} + +export default () => +``` + +Feel free to customize the email template further to match your branding and style, or to add additional information such as the user's email. + +Then, in the Resend Module's service at `src/modules/resend/service.ts`, add the new template to the `templates` object and `Templates` type: + +```ts title="src/modules/resend/service.ts" +// other imports... +import { userInvitedEmail } from "./emails/user-invited" + +enum Templates { + // ... + USER_INVITED = "user-invited", +} + +const templates: {[key in Templates]?: (props: unknown) => React.ReactNode} = { + // ... + [Templates.USER_INVITED]: userInvitedEmail, +} +``` + +Finally, find the `getTemplateSubject` function in the `ResendNotificationProviderService` and add a case for the `USER_INVITED` template: + +```ts title="src/modules/resend/service.ts" +class ResendNotificationProviderService extends AbstractNotificationProviderService { + // ... + + private getTemplateSubject(template: Templates) { + // ... + switch (template) { + // ... + case Templates.USER_INVITED: + return "You've been invited to join our platform" + } + } +} +``` + + # User Module Options In this document, you'll learn about the options of the User Module. @@ -38627,10 +39410,6 @@ Add the module into the `providers` array of the Notification Module: Only one provider can be defined for a channel. ```ts title="medusa-config.ts" -import { Modules } from "@medusajs/framework/utils" - -// ... - module.exports = defineConfig({ // ... modules: [ @@ -71273,6 +72052,15 @@ If you check the inbox of the email address you specified in the shipping addres You've now integrated Medusa with Resend. You can add more templates for other emails, such as customer registration confirmation, user invites, and more. Check out the [Events Reference](https://docs.medusajs.com/references/events/index.html.md) for a list of all events that the Medusa application emits. +### More Resend Email Templates + +Find more email templates to use with the Resend Module Provider in the following guides: + +- [Send Invite User Email](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/user/invite-user-subscriber/index.html.md). +- [Send Reset Password Email](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/reset-password/index.html.md). + +### Learn More About Medusa + If you're new to Medusa, check out the [main documentation](https://docs.medusajs.com/docs/learn/index.html.md), where you'll get a more in-depth learning of all the concepts you've used in this guide and more. To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md). diff --git a/www/apps/resources/app/commerce-modules/auth/reset-password/page.mdx b/www/apps/resources/app/commerce-modules/auth/reset-password/page.mdx index 4703ae6965..4547c4479c 100644 --- a/www/apps/resources/app/commerce-modules/auth/reset-password/page.mdx +++ b/www/apps/resources/app/commerce-modules/auth/reset-password/page.mdx @@ -1,5 +1,5 @@ --- -sidebar_label: "Handle Password Reset Event" +sidebar_label: "Reset Password Email" tags: - notification - how to @@ -9,12 +9,12 @@ tags: import { Prerequisites } from "docs-ui" export const metadata = { - title: `How to Handle Password Reset Token Event`, + title: `Send Reset Password Email Notification`, } # {metadata.title} -In this guide, you'll learn how to handle the `auth.password_reset` event, which is emitted when a request is sent to the [Generate Reset Password Token API route](../authentication-route/page.mdx#generate-reset-password-token-route). +In this guide, you'll learn how to handle the `auth.password_reset` event to send a reset password email (or other notification type) to users. @@ -22,34 +22,78 @@ Refer to this [Medusa Admin User Guide](!user-guide!/reset-password) to learn ho -You'll create a subscriber that listens to the event. When the event is emitted, the subscriber sends an email notification to the user. +## Reset Password Flow Overview - + resolve: "@medusajs/medusa/notification", + options: { + providers: [ + // ... + { + resolve: "@medusajs/medusa/notification-local", + id: "local", + options: { + channels: ["email"], + }, + }, + ], + }, + }, + ], +}) +``` -## 1. Create Subscriber +The Local provider logs email details to your terminal instead of sending actual emails, which is useful for development and testing. -The first step is to create a subscriber that listens to the `auth.password_reset` and sends the user a notification with instructions to reset their password. +--- -Create the file `src/subscribers/handle-reset.ts` with the following content: +## Create the Reset Password Subscriber + +To create a [subscriber](!docs!/learn/fundamentals/events-and-subscribers) that handles the `auth.password_reset` event, create the file `src/subscribers/password-reset.ts` with the following content: export const highlights=[ ["8", "data", "The data payload of the event."], ["9", "entity_id", "The user's identifier, which is the email when using the `emailpass` provider."], ["10", "token", "The password reset token."], ["11", "actor_type", "The user's actor type."], - ["19", "urlPrefix", "Set the page's URL based on the user's actor type."], - ["23", "createNotifications", "Send a notification to the user."], - ["25", `"email"`, "The channel to send the notification through."], - ["26", "template", "The template defined in the third-party provider."], - ["27", "data", "The data to pass to the template in the third-party provider."], - ["29", "url", "The frontend URL to redirect the user to reset their password."] + ["20", "urlPrefix", "Set the page's URL based on the user's actor type."], + ["30", "createNotifications", "Send a notification to the user."], + ["32", `"email"`, "The channel to send the notification through."], + ["34", "template", "The template defined in the third-party provider."], + ["35", "data", "The data to pass to the template in the third-party provider."], + ["37", "reset_url", "The frontend URL to redirect the user to reset their password."] ] ```ts title="src/subscribers/handle-reset.ts" highlights={highlights} collapsibleLines="1-6" expandMoreLabel="Show Imports" @@ -70,18 +114,27 @@ export default async function resetPasswordTokenHandler({ const notificationModuleService = container.resolve( Modules.NOTIFICATION ) + const config = container.resolve("configModule") - const urlPrefix = actor_type === "customer" ? - "https://storefront.com" : - "https://admin.com/app" + let urlPrefix = "" + + if (actor_type === "customer") { + urlPrefix = config.admin.storefrontUrl || "https://storefront.com" + } else { + const backendUrl = config.admin.backendUrl !== "/" ? config.admin.backendUrl : + "http://localhost:9000" + const adminPath = config.admin.path + urlPrefix = `${backendUrl}${adminPath}` + } await notificationModuleService.createNotifications({ to: email, channel: "email", - template: "reset-password-template", + // TODO replace with template ID in notification provider + template: "password-reset", data: { // a URL to a frontend application - url: `${urlPrefix}/reset-password?token=${token}&email=${email}`, + reset_url: `${urlPrefix}/reset-password?token=${token}&email=${email}`, }, }) } @@ -91,7 +144,7 @@ export const config: SubscriberConfig = { } ``` -You subscribe to the `auth.password_reset` event. The event has a data payload object with the following properties: +The subscriber receives the following data through the event payload: - `entity_id`: The identifier of the user. When using the `emailpass` provider, it's the user's email. - `token`: The token to reset the user's password. @@ -103,41 +156,46 @@ This event's payload previously had an `actorType` field. It was renamed to `act -In the subscriber, you: +### Reset Password URL -- Decide the frontend URL based on whether the user is a customer or admin user by checking the value of `actor_type`. -- Resolve the Notification Module and use its `createNotifications` method to send the notification. -- You pass to the `createNotifications` method an object having the following properties: - - `to`: The identifier to send the notification to, which in this case is the email. - - `channel`: The channel to send the notification through, which in this case is email. - - `template`: The template ID in the third-party service. - - `data`: The data payload to pass to the template. You pass the URL to redirect the user to. You must pass the token and email in the URL so that the frontend can send them later to the Medusa application when reseting the password. +Based on the user's actor type, you set the URL prefix to redirect the user to the appropriate frontend page to reset their password: ---- +- If the user is a customer, you set the URL prefix to the storefront URL. +- If the user is an admin, you set the URL prefix to the backend URL, which is constructed from the `config.admin.backendUrl` and `config.admin.path` values. -## 2. Test it Out: Generate Reset Password Token +Note that the Medusa Admin has a reset password form at `/reset-password?token={token}&email={email}`. -To test the subscriber out, send a request to the `/auth/{actor_type}/{auth_provider}/reset-password` API route, replacing `{actor_type}` and `{auth_provider}` with the user's actor type and provider used for authentication respectively. +### Notification Configurations -For example, to generate a reset password token for an admin user using the `emailpass` provider, send the following request: +For the notification, you can configure the following fields: -```bash -curl --location 'http://localhost:9000/auth/user/emailpass/reset-password' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "identifier": "admin-test@gmail.com" -}' +- `to`: The identifier to send the notification to, which in this case is the email. +- `channel`: The channel to send the notification through, which in this case is email. +- `template`: The template ID of the email to send. This ID depends on the Notification Module provider you use. For example, if you use SendGrid, this would be the ID of the SendGrid template. + - Refer to the [Example Notification Templates](#example-notification-templates) section below for examples of notification templates to use. +- `data`: The data payload to pass to the template. You can pass additional fields, if necessary. + +### Test It Out + +After you set up the Notification Module Provider, create a template in the provider, and create the subscriber, you can test the reset password flow. + +Start the Medusa application with the following command: + +```bash npm2yarn +npm run dev ``` -In the request body, you must pass an `identifier` parameter. Its value is the user's identifier, which is the email in this case. +Then, open the Medusa Admin (locally at `http://localhost:9000/app`) and click on "Reset" in the login form. Then, enter the email of the user you want to reset the password for. -If the token is generated successfully, the request returns a response with `201` status code. In the terminal, you'll find the following message indicating that the `auth.password_reset` event was emitted and your subscriber ran: +Once you reset the password, you should see that the `auth.password_reset` event is emitted in the server's logs: -```plain +```bash info: Processing auth.password_reset which has 1 subscribers ``` -The notification is sent to the user with the frontend URL to enter a new password. +If you're using an email Notification Module Provider, check the user's email inbox for the reset password email with the link to reset their password. + +If you're using the Local provider, check your terminal for the logged email details. --- @@ -147,6 +205,305 @@ In your frontend, you must have a page that accepts `token` and `email` query pa The page shows the user password fields to enter their new password, then submits the new password, token, and email to the [Reset Password Route](../authentication-route/page.mdx#reset-password-route). +The Medusa Admin already has a reset password page at `/reset-password?token={token}&email={email}`. So, you only need to implement this page in your storefront or custom admin dashboard. + ### Examples - [Storefront Guide: Reset Customer Password](../../../storefront-development/customers/reset-password/page.mdx) + +--- + +## Example Notification Templates + +The following section provides example notification templates for some Notification Module Providers. + +### SendGrid + + + +Refer to the [SendGrid Notification Module Provider](../../../infrastructure-modules/notification/sendgrid/page.mdx) documentation for more details on how to set up SendGrid. + + + +The following HTML template can be used with SendGrid to send a reset password email: + +```html + + + + + + Reset Your Password + + + +
+
+

Reset Your Password

+
+ +
+

+ Hello{{#if email}} {{email}}{{/if}}, +

+

+ We received a request to reset your password. Click the button below to create a new password for your account. +

+
+ + + +
+

+ Or copy and paste this URL into your browser: +

+ + {{reset_url}} + +
+ +
+

+ This password reset link will expire soon for security reasons. +

+

+ If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged. +

+
+ + +
+ + +``` + +Make sure to pass the `reset_url` variable to the template, which contains the URL to reset the password. + +You can also customize the template further to show other information. + +### Resend + +If you've integrated Resend as explained in the [Resend Integration Guide](../../../integrations/guides/resend/page.mdx), you can add a new template for password reset emails at `src/modules/resend/emails/password-reset.tsx`: + +```tsx title="src/modules/resend/emails/password-reset.tsx" +import { + Text, + Container, + Heading, + Html, + Section, + Tailwind, + Head, + Preview, + Body, + Link, + Button, +} from "@react-email/components" + +type PasswordResetEmailProps = { + reset_url: string + email?: string +} + +function PasswordResetEmailComponent({ reset_url, email }: PasswordResetEmailProps) { + return ( + + + Reset your password + + + +
+ + Reset Your Password + +
+ +
+ + Hello{email ? ` ${email}` : ""}, + + + We received a request to reset your password. Click the button below to create a new password for your account. + +
+ +
+ +
+ +
+ + Or copy and paste this URL into your browser: + + + {reset_url} + +
+ +
+ + This password reset link will expire soon for security reasons. + + + If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged. + +
+ +
+ + For security reasons, never share this reset link with anyone. If you're having trouble with the button above, copy and paste the URL into your web browser. + +
+
+ +
+ + ) +} + +export const passwordResetEmail = (props: PasswordResetEmailProps) => ( + +) + +// Mock data for preview/development +const mockPasswordReset: PasswordResetEmailProps = { + reset_url: "https://your-app.com/reset-password?token=sample-reset-token-123", + email: "user@example.com", +} + +export default () => +``` + +Feel free to customize the email template further to match your branding and style, or to add additional information. + +Then, in the Resend Module's service at `src/modules/resend/service.ts`, add the new template to the `templates` object and `Templates` type: + +```ts title="src/modules/resend/service.ts" +// other imports... +import { passwordResetEmail } from "./emails/password-reset" + +enum Templates { + // ... + PASSWORD_RESET = "password-reset", +} + +const templates: {[key in Templates]?: (props: unknown) => React.ReactNode} = { + // ... + [Templates.PASSWORD_RESET]: passwordResetEmail, +} +``` + +Finally, find the `getTemplateSubject` function in the `ResendNotificationProviderService` and add a case for the `USER_INVITED` template: + +```ts title="src/modules/resend/service.ts" +class ResendNotificationProviderService extends AbstractNotificationProviderService { + // ... + + private getTemplateSubject(template: Templates) { + // ... + switch (template) { + // ... + case Templates.PASSWORD_RESET: + return "Reset Your Password" + } + } +} +``` \ No newline at end of file diff --git a/www/apps/resources/app/commerce-modules/cart/cart-totals/page.mdx b/www/apps/resources/app/commerce-modules/cart/cart-totals/page.mdx index c469f87c7c..274a2143e4 100644 --- a/www/apps/resources/app/commerce-modules/cart/cart-totals/page.mdx +++ b/www/apps/resources/app/commerce-modules/cart/cart-totals/page.mdx @@ -123,7 +123,7 @@ const { data: [cart] } = await query.graph({ ], filters: { id: "cart_123", // Specify the cart ID - } + }, }) ``` @@ -280,7 +280,7 @@ const { data: [cart] } = await query.graph({ fields: ["*"], filters: { id: "cart_123", - } + }, }) // cart doesn't include cart totals @@ -315,7 +315,7 @@ const { data: [cart] } = await query.graph({ fields: ["*", "total"], filters: { id: "cart_123", - } + }, }) // cart doesn't include cart totals diff --git a/www/apps/resources/app/commerce-modules/order/order-totals/page.mdx b/www/apps/resources/app/commerce-modules/order/order-totals/page.mdx index 7effd43bdf..2edcf4cf53 100644 --- a/www/apps/resources/app/commerce-modules/order/order-totals/page.mdx +++ b/www/apps/resources/app/commerce-modules/order/order-totals/page.mdx @@ -75,7 +75,7 @@ export const myWorkflow = createWorkflow( "gift_card_tax_total", "items.*", "shipping_methods.*", - "summary.*" + "summary.*", ], filters: { id: "order_123", // Specify the order ID @@ -119,11 +119,11 @@ const { data: [order] } = await query.graph({ "gift_card_tax_total", "items.*", "shipping_methods.*", - "summary.*" + "summary.*", ], filters: { id: "order_123", // Specify the order ID - } + }, }) ``` @@ -312,7 +312,7 @@ const { data: [order] } = await query.graph({ fields: ["*"], filters: { id: "order_123", - } + }, }) // order doesn't include order totals @@ -347,7 +347,7 @@ const { data: [order] } = await query.graph({ fields: ["*", "total"], filters: { id: "order_123", - } + }, }) // order doesn't include order totals diff --git a/www/apps/resources/app/commerce-modules/user/invite-user-subscriber/page.mdx b/www/apps/resources/app/commerce-modules/user/invite-user-subscriber/page.mdx new file mode 100644 index 0000000000..ca21097dc7 --- /dev/null +++ b/www/apps/resources/app/commerce-modules/user/invite-user-subscriber/page.mdx @@ -0,0 +1,453 @@ +--- +sidebar_label: "Invite User Email" +tags: + - server + - how to + - notification +--- + +export const metadata = { + title: `Send Invite User Email Notification`, +} + +# {metadata.title} + +In this guide, you'll learn how to handle the `invite.created` and `invite.resent` events to send an invite email (or other notification type) to the user. + + + +Refer to the [Manage Invites](!user-guide!/settings/users/invites) user guide to learn how to manage invites using the Medusa Admin. + + + +## User Invite Flow Overview + +![Diagram showcasing the user invite flow detailed below](https://res.cloudinary.com/dza7lstvk/image/upload/v1754047213/Medusa%20Resources/invite-user_uqspsv.jpg) + +Admin users can add new users to their store by sending them an invite. The flow for inviting users is as follows: + +1. An admin user invites a user either through the [Medusa Admin](!user-guide!/settings/users/invites) or using the [Create Invite API route](!api!/admin#invites_postinvites). +2. The invite is created and the `invite.created` event is emitted. + - At this point, you can handle the event to send an email or notification to the user. +3. The invited user receives the invite and can accept it, which creates a new user. + - The invited user can accept the invite either through the Medusa Admin or using the [Accept Invite API route](!api!/admin#invites_postinvitesaccept). + +The admin user can also resend the invite if the invited user doesn't receive the invite or doesn't accept it before expiry, which emits the `invite.resent` event. + +In this guide, you'll implement a subscriber that handles the `invite.created` and `invite.resent` events to send an email to the user. + +After adding the subscriber, you will have a complete user invite flow that you can utilize either through the Medusa Admin or using the Admin APIs. + +--- + +## Prerequisites: Notification Module Provider + +To send an email or notification to the user, you must have a Notification Module Provider set up. + +Medusa provides providers like [SendGrid](../../../infrastructure-modules/notification/sendgrid/page.mdx) and [Resend](../../../integrations/guides/resend/page.mdx), and you can also [create your own custom provider](/references/notification-provider-module). + +Refer to the [Notification Module](../../../infrastructure-modules/notification/page.mdx#what-is-a-notification-module-provider) documentation for a list of available providers and how to set them up. + +### Testing with the Local Notification Module Provider + +For testing purposes, you can use the [Local Notification Module Provider](../../../infrastructure-modules/notification/local/page.mdx) by adding this to your `medusa-config.ts`: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/medusa/notification", + options: { + providers: [ + // ... + { + resolve: "@medusajs/medusa/notification-local", + id: "local", + options: { + channels: ["email"], + }, + }, + ], + }, + }, + ], +}) +``` + +The Local provider logs email details to your terminal instead of sending actual emails, which is useful for development and testing. + +--- + +## Create the Invite User Subscriber + +To create a [subscriber](!docs!/learn/fundamentals/events-and-subscribers) that handles the `invite.created` and `invite.resent` events, create the file `src/subscribers/user-invited.ts` with the following content: + +export const highlights = [ + ["15", "invite", "Retrieve the invite's details."], + ["26", "backend_url", "Construct the backend URL."], + ["28", "adminPath", "Get the admin path from the config."], + ["30", "createNotifications", "Send the notification to the user."], + ["34", "channel", "Use the Notification Module Provider configured for the email channel."], + ["35", "data", "Pass the invite URL to the notification template."], + ["42", "event", "Subscribe to the invite events."] +] + +```ts title="src/subscribers/user-invited.ts" highlights={highlights} +import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework" + +export default async function inviteCreatedHandler({ + event: { data }, + container, +}: SubscriberArgs<{ + id: string +}>) { + const query = container.resolve("query") + const notificationModuleService = container.resolve( + "notification" + ) + const config = container.resolve("configModule") + + const { data: [invite] } = await query.graph({ + entity: "invite", + fields: [ + "email", + "token", + ], + filters: { + id: data.id, + }, + }) + + const backend_url = config.admin.backendUrl !== "/" ? config.admin.backendUrl : + "http://localhost:9000" + const adminPath = config.admin.path + + await notificationModuleService.createNotifications({ + to: invite.email, + // TODO replace with template ID in notification provider + template: "user-invited", + channel: "email", + data: { + invite_url: `${backend_url}${adminPath}/invite?token=${invite.token}`, + }, + }) +} + +export const config: SubscriberConfig = { + event: [ + "invite.created", + "invite.resent", + ], +} +``` + +The subscriber receives the ID of the invite in the event payload. You resolve [Query](!docs!/learn/fundamentals/module-links/query) to fetch the invite details, including the email and token. + +### Invite URL + +You then use the Notification Module's service to send an email to the user with the invite link. The invite link is constructed using: + +1. The backend URL of the Medusa application, since the Medusa Admin is hosted on the same URL. +2. The admin path, which is the path to the Medusa Admin (for example, `/app`). + +Note that the Medusa Admin has an invite form at `/invite?token=`, which accepts the invite token as a query parameter. + +### Notification Configurations + +For the notification, you can configure the following fields: + +- `template`: The template ID of the email to send. This ID depends on the Notification Module provider you use. For example, if you use SendGrid, this would be the ID of the SendGrid template. + - Refer to the [Example Notification Templates](#example-notification-templates) section below for examples of notification templates to use. +- `channel`: The channel to send the notification through. In this case, it's set to `email`. +- `data`: The data to pass to the notification template. You can add additional fields as needed, such as the invited user's email. + +### Test It Out + +After you set up the Notification Module Provider, create a template in the provider, and create the subscriber, you can test the full invite flow. + +Start the Medusa application with the following command: + +```bash npm2yarn +npm run dev +``` + +Then, open the Medusa Admin (locally at `http://localhost:9000/app`) and go to Settings → Users. Create an invite as explained in the [Manage Invites](!user-guide!/settings/users/invites) user guide. + +Once you create the invite, you should see that the `invite.created` event is emitted in the server's logs: + +```bash +info: Processing invite.created which has 1 subscribers +``` + +If you're using an email Notification Module Provider, check the recipient's email inbox for the invite email with the link to accept the invite. + +If you're using the Local provider, check your terminal for the logged email details. + +--- + +## Example Notification Templates + +The following section provides example notification templates for some Notification Module Providers. + +### SendGrid + + + +Refer to the [SendGrid Notification Module Provider](../../../infrastructure-modules/notification/sendgrid/page.mdx) documentation for more details on how to set up SendGrid. + + + +The following HTML template can be used with SendGrid to send an invite email: + +```html + + + + + + You're Invited! + + + +
+
+

You're Invited!

+
+ +
+

+ Hello{{#if email}} {{email}}{{/if}}, +

+

+ You've been invited to join our platform. Click the button below to accept your invitation and set up your account. +

+
+ + + +
+

+ Or copy and paste this URL into your browser: +

+ + {{invite_url}} + +
+ + +
+ + +``` + +Make sure to pass the `invite_url` variable to the template, which contains the URL to accept the invite. + +You can also customize the template further to show other information, such as the user's email. + +### Resend + +If you've integrated Resend as explained in the [Resend Integration Guide](../../../integrations/guides/resend/page.mdx), you can add a new template for user invites at `src/modules/resend/emails/user-invited.tsx`: + +```tsx title="src/modules/resend/emails/user-invited.tsx" +import { + Text, + Container, + Heading, + Html, + Section, + Tailwind, + Head, + Preview, + Body, + Link, + Button, +} from "@react-email/components" + +type UserInvitedEmailProps = { + invite_url: string + email?: string +} + +function UserInvitedEmailComponent({ invite_url, email }: UserInvitedEmailProps) { + return ( + + + You've been invited to join our platform + + + +
+ + You're Invited! + +
+ +
+ + Hello{email ? ` ${email}` : ""}, + + + You've been invited to join our platform. Click the button below to accept your invitation and set up your account. + +
+ +
+ +
+ +
+ + Or copy and paste this URL into your browser: + + + {invite_url} + +
+ +
+ + If you weren't expecting this invitation, you can ignore this email. + +
+
+ +
+ + ) +} + +export const userInvitedEmail = (props: UserInvitedEmailProps) => ( + +) + +// Mock data for preview/development +const mockInvite: UserInvitedEmailProps = { + invite_url: "https://your-app.com/app/invite/sample-token-123", + email: "user@example.com", +} + +export default () => +``` + +Feel free to customize the email template further to match your branding and style, or to add additional information such as the user's email. + +Then, in the Resend Module's service at `src/modules/resend/service.ts`, add the new template to the `templates` object and `Templates` type: + +```ts title="src/modules/resend/service.ts" +// other imports... +import { userInvitedEmail } from "./emails/user-invited" + +enum Templates { + // ... + USER_INVITED = "user-invited", +} + +const templates: {[key in Templates]?: (props: unknown) => React.ReactNode} = { + // ... + [Templates.USER_INVITED]: userInvitedEmail, +} +``` + +Finally, find the `getTemplateSubject` function in the `ResendNotificationProviderService` and add a case for the `USER_INVITED` template: + +```ts title="src/modules/resend/service.ts" +class ResendNotificationProviderService extends AbstractNotificationProviderService { + // ... + + private getTemplateSubject(template: Templates) { + // ... + switch (template) { + // ... + case Templates.USER_INVITED: + return "You've been invited to join our platform" + } + } +} +``` \ No newline at end of file diff --git a/www/apps/resources/app/infrastructure-modules/notification/local/page.mdx b/www/apps/resources/app/infrastructure-modules/notification/local/page.mdx index 55deb810dc..82311e952c 100644 --- a/www/apps/resources/app/infrastructure-modules/notification/local/page.mdx +++ b/www/apps/resources/app/infrastructure-modules/notification/local/page.mdx @@ -27,10 +27,6 @@ Only one provider can be defined for a channel. ```ts title="medusa-config.ts" -import { Modules } from "@medusajs/framework/utils" - -// ... - module.exports = defineConfig({ // ... modules: [ diff --git a/www/apps/resources/app/integrations/guides/resend/page.mdx b/www/apps/resources/app/integrations/guides/resend/page.mdx index 8e7b4ad9d7..90ef849d5d 100644 --- a/www/apps/resources/app/integrations/guides/resend/page.mdx +++ b/www/apps/resources/app/integrations/guides/resend/page.mdx @@ -1391,6 +1391,15 @@ If you check the inbox of the email address you specified in the shipping addres You've now integrated Medusa with Resend. You can add more templates for other emails, such as customer registration confirmation, user invites, and more. Check out the [Events Reference](/references/events) for a list of all events that the Medusa application emits. +### More Resend Email Templates + +Find more email templates to use with the Resend Module Provider in the following guides: + +- [Send Invite User Email](../../../commerce-modules/user/invite-user-subscriber/page.mdx). +- [Send Reset Password Email](../../../commerce-modules/auth/reset-password/page.mdx). + +### Learn More About Medusa + If you're new to Medusa, check out the [main documentation](!docs!/learn), where you'll get a more in-depth learning of all the concepts you've used in this guide and more. To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](../../../commerce-modules/page.mdx). diff --git a/www/apps/resources/generated/edit-dates.mjs b/www/apps/resources/generated/edit-dates.mjs index 4b18f49b04..e0e1e4d391 100644 --- a/www/apps/resources/generated/edit-dates.mjs +++ b/www/apps/resources/generated/edit-dates.mjs @@ -208,7 +208,7 @@ export const generatedEditDates = { "app/infrastructure-modules/event/local/page.mdx": "2025-03-27T14:53:13.309Z", "app/infrastructure-modules/workflow-engine/in-memory/page.mdx": "2024-11-19T16:37:47.262Z", "app/infrastructure-modules/cache/in-memory/page.mdx": "2024-11-19T16:37:47.261Z", - "app/infrastructure-modules/notification/local/page.mdx": "2024-11-19T16:37:47.262Z", + "app/infrastructure-modules/notification/local/page.mdx": "2025-08-01T11:42:33.657Z", "app/infrastructure-modules/file/local/page.mdx": "2025-05-20T07:51:40.714Z", "app/infrastructure-modules/notification/send-notification/page.mdx": "2025-05-20T07:51:40.714Z", "app/infrastructure-modules/file/page.mdx": "2025-04-17T08:29:00.672Z", @@ -2098,7 +2098,7 @@ export const generatedEditDates = { "app/admin-components/layouts/single-column/page.mdx": "2024-10-07T11:16:06.435Z", "app/admin-components/layouts/two-column/page.mdx": "2024-10-07T11:16:10.092Z", "app/admin-components/components/forms/page.mdx": "2024-10-09T12:48:04.229Z", - "app/commerce-modules/auth/reset-password/page.mdx": "2025-04-17T08:29:01.697Z", + "app/commerce-modules/auth/reset-password/page.mdx": "2025-08-01T12:07:32.023Z", "app/storefront-development/customers/reset-password/page.mdx": "2025-03-27T14:46:51.424Z", "app/commerce-modules/api-key/links-to-other-modules/page.mdx": "2025-04-17T15:39:39.374Z", "app/commerce-modules/cart/extend/page.mdx": "2024-12-25T12:48:59.149Z", @@ -5524,7 +5524,7 @@ export const generatedEditDates = { "references/workflows/classes/workflows.WorkflowResponse/page.mdx": "2025-04-11T09:04:53.140Z", "references/workflows/interfaces/workflows.ApplyStepOptions/page.mdx": "2025-05-20T07:51:41.181Z", "references/workflows/types/workflows.WorkflowData/page.mdx": "2024-12-23T13:57:08.059Z", - "app/integrations/guides/resend/page.mdx": "2025-06-26T12:38:47.063Z", + "app/integrations/guides/resend/page.mdx": "2025-08-01T12:09:08.483Z", "references/api_key_models/variables/api_key_models.ApiKey/page.mdx": "2025-07-24T08:20:54.787Z", "references/cart/ICartModuleService/methods/cart.ICartModuleService.updateShippingMethods/page.mdx": "2025-06-25T10:11:35.302Z", "references/cart/interfaces/cart.UpdateShippingMethodDTO/page.mdx": "2025-06-25T10:11:35.052Z", @@ -6563,5 +6563,6 @@ export const generatedEditDates = { "references/js_sdk/admin/Order/methods/js_sdk.admin.Order.archive/page.mdx": "2025-07-24T08:20:57.709Z", "references/js_sdk/admin/Order/methods/js_sdk.admin.Order.complete/page.mdx": "2025-07-24T08:20:57.714Z", "app/commerce-modules/cart/cart-totals/page.mdx": "2025-07-31T15:18:13.978Z", - "app/commerce-modules/order/order-totals/page.mdx": "2025-07-31T15:12:10.633Z" + "app/commerce-modules/order/order-totals/page.mdx": "2025-07-31T15:12:10.633Z", + "app/commerce-modules/user/invite-user-subscriber/page.mdx": "2025-08-01T12:01:54.551Z" } \ No newline at end of file diff --git a/www/apps/resources/generated/files-map.mjs b/www/apps/resources/generated/files-map.mjs index 43dd97adec..bc6092bb40 100644 --- a/www/apps/resources/generated/files-map.mjs +++ b/www/apps/resources/generated/files-map.mjs @@ -651,6 +651,10 @@ export const filesMap = [ "filePath": "/www/apps/resources/app/commerce-modules/user/admin-widget-zones/page.mdx", "pathname": "/commerce-modules/user/admin-widget-zones" }, + { + "filePath": "/www/apps/resources/app/commerce-modules/user/invite-user-subscriber/page.mdx", + "pathname": "/commerce-modules/user/invite-user-subscriber" + }, { "filePath": "/www/apps/resources/app/commerce-modules/user/js-sdk/page.mdx", "pathname": "/commerce-modules/user/js-sdk" diff --git a/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs b/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs index 584d34fa0c..f6572abe51 100644 --- a/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs +++ b/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs @@ -6102,14 +6102,6 @@ const generatedgeneratedCommerceModulesSidebarSidebar = { "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/gift-message", "children": [] }, - { - "loaded": true, - "isPathHref": true, - "type": "ref", - "title": "Generate Invoices", - "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/invoice-generator", - "children": [] - }, { "loaded": true, "isPathHref": true, @@ -16771,6 +16763,14 @@ const generatedgeneratedCommerceModulesSidebarSidebar = { "sort_sidebar": "alphabetize", "description": "Learn how to use the User Module in your customizations on the Medusa application server.", "children": [ + { + "loaded": true, + "isPathHref": true, + "type": "link", + "path": "/commerce-modules/user/invite-user-subscriber", + "title": "Invite User Email", + "children": [] + }, { "loaded": true, "isPathHref": true, diff --git a/www/apps/resources/generated/generated-how-to-tutorials-sidebar.mjs b/www/apps/resources/generated/generated-how-to-tutorials-sidebar.mjs index 6286cb7b31..c805964697 100644 --- a/www/apps/resources/generated/generated-how-to-tutorials-sidebar.mjs +++ b/www/apps/resources/generated/generated-how-to-tutorials-sidebar.mjs @@ -138,7 +138,15 @@ const generatedgeneratedHowToTutorialsSidebarSidebar = { "loaded": true, "isPathHref": true, "type": "ref", - "title": "Handle Password Reset Event", + "title": "Invite User Email", + "path": "https://docs.medusajs.com/resources/commerce-modules/user/invite-user-subscriber", + "children": [] + }, + { + "loaded": true, + "isPathHref": true, + "type": "ref", + "title": "Reset Password Email", "path": "https://docs.medusajs.com/resources/commerce-modules/auth/reset-password", "children": [] }, diff --git a/www/apps/resources/generated/generated-infrastructure-modules-sidebar.mjs b/www/apps/resources/generated/generated-infrastructure-modules-sidebar.mjs index 0c759081de..f1c6febbe4 100644 --- a/www/apps/resources/generated/generated-infrastructure-modules-sidebar.mjs +++ b/www/apps/resources/generated/generated-infrastructure-modules-sidebar.mjs @@ -410,14 +410,6 @@ const generatedgeneratedInfrastructureModulesSidebarSidebar = { "title": "Create Notification Provider", "children": [] }, - { - "loaded": true, - "isPathHref": true, - "type": "ref", - "title": "Handle Password Reset Event", - "path": "https://docs.medusajs.com/resources/commerce-modules/auth/reset-password", - "children": [] - }, { "loaded": true, "isPathHref": true, @@ -442,6 +434,22 @@ const generatedgeneratedInfrastructureModulesSidebarSidebar = { "path": "https://docs.medusajs.com/resources/integrations/guides/slack", "children": [] }, + { + "loaded": true, + "isPathHref": true, + "type": "ref", + "title": "Invite User Email", + "path": "https://docs.medusajs.com/resources/commerce-modules/user/invite-user-subscriber", + "children": [] + }, + { + "loaded": true, + "isPathHref": true, + "type": "ref", + "title": "Reset Password Email", + "path": "https://docs.medusajs.com/resources/commerce-modules/auth/reset-password", + "children": [] + }, { "loaded": true, "isPathHref": true, diff --git a/www/apps/resources/generated/generated-tools-sidebar.mjs b/www/apps/resources/generated/generated-tools-sidebar.mjs index d912a536b9..600c14d321 100644 --- a/www/apps/resources/generated/generated-tools-sidebar.mjs +++ b/www/apps/resources/generated/generated-tools-sidebar.mjs @@ -819,14 +819,6 @@ const generatedgeneratedToolsSidebarSidebar = { "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/first-purchase-discounts", "children": [] }, - { - "loaded": true, - "isPathHref": true, - "type": "ref", - "title": "Generate Invoices", - "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/invoice-generator", - "children": [] - }, { "loaded": true, "isPathHref": true, diff --git a/www/apps/resources/sidebars/user.mjs b/www/apps/resources/sidebars/user.mjs index 60c9d65f08..63239bc830 100644 --- a/www/apps/resources/sidebars/user.mjs +++ b/www/apps/resources/sidebars/user.mjs @@ -32,6 +32,11 @@ export const userSidebar = [ path: "/commerce-modules/user/user-creation-flows", title: "User Creation Flows", }, + { + type: "link", + path: "/commerce-modules/user/invite-user-subscriber", + title: "Invite User Email", + }, ], }, { diff --git a/www/apps/user-guide/app/settings/users/invites/page.mdx b/www/apps/user-guide/app/settings/users/invites/page.mdx index a1f58de80c..3fae861c62 100644 --- a/www/apps/user-guide/app/settings/users/invites/page.mdx +++ b/www/apps/user-guide/app/settings/users/invites/page.mdx @@ -24,7 +24,7 @@ To add a new user to your team, you must invite them. The user gets a notificati -The form of notification sent (for example, an email) depends on what's configured in your store. If you're unsure about this, contact your technical team to integrate a [notification service](!resources!/infrastructure-modules/notification). +The form of notification sent (for example, an email) depends on what's configured in your store. If you're unsure about this, contact your technical team to [set up user invite notifications](!resources!/commerce-modules/user/invite-user-subscriber). diff --git a/www/apps/user-guide/generated/edit-dates.mjs b/www/apps/user-guide/generated/edit-dates.mjs index 6265f2cc3e..287da7d641 100644 --- a/www/apps/user-guide/generated/edit-dates.mjs +++ b/www/apps/user-guide/generated/edit-dates.mjs @@ -10,7 +10,7 @@ export const generatedEditDates = { "app/settings/return-reasons/page.mdx": "2025-05-30T13:31:05.596Z", "app/settings/regions/page.mdx": "2025-05-30T13:30:59.228Z", "app/orders/page.mdx": "2025-05-30T13:28:32.793Z", - "app/settings/users/invites/page.mdx": "2025-07-31T14:32:55.850Z", + "app/settings/users/invites/page.mdx": "2025-08-01T11:25:25.130Z", "app/settings/developer/page.mdx": "2025-02-25T15:11:55.392Z", "app/settings/profile/page.mdx": "2025-05-30T13:30:55.079Z", "app/settings/store/page.mdx": "2025-05-30T13:31:17.095Z", diff --git a/www/packages/tags/src/tags/how-to.ts b/www/packages/tags/src/tags/how-to.ts index 8937879586..0add92c897 100644 --- a/www/packages/tags/src/tags/how-to.ts +++ b/www/packages/tags/src/tags/how-to.ts @@ -4,7 +4,7 @@ export const howTo = [ "path": "https://docs.medusajs.com/resources/commerce-modules/auth/create-actor-type" }, { - "title": "Handle Password Reset Event", + "title": "Reset Password Email", "path": "https://docs.medusajs.com/resources/commerce-modules/auth/reset-password" }, { @@ -27,6 +27,10 @@ export const howTo = [ "title": "Get Variant Inventory", "path": "https://docs.medusajs.com/resources/commerce-modules/product/guides/variant-inventory" }, + { + "title": "Invite User Email", + "path": "https://docs.medusajs.com/resources/commerce-modules/user/invite-user-subscriber" + }, { "title": "Create Cache Module", "path": "https://docs.medusajs.com/resources/infrastructure-modules/cache/create" diff --git a/www/packages/tags/src/tags/notification.ts b/www/packages/tags/src/tags/notification.ts index 2b3b0211ef..547594c6e6 100644 --- a/www/packages/tags/src/tags/notification.ts +++ b/www/packages/tags/src/tags/notification.ts @@ -1,8 +1,12 @@ export const notification = [ { - "title": "Handle Password Reset Event", + "title": "Reset Password Email", "path": "https://docs.medusajs.com/resources/commerce-modules/auth/reset-password" }, + { + "title": "Invite User Email", + "path": "https://docs.medusajs.com/resources/commerce-modules/user/invite-user-subscriber" + }, { "title": "Abandoned Cart Notification", "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/abandoned-cart" diff --git a/www/packages/tags/src/tags/server.ts b/www/packages/tags/src/tags/server.ts index d02677ade7..a5231e223c 100644 --- a/www/packages/tags/src/tags/server.ts +++ b/www/packages/tags/src/tags/server.ts @@ -4,7 +4,7 @@ export const server = [ "path": "https://docs.medusajs.com/resources/commerce-modules/auth/create-actor-type" }, { - "title": "Handle Password Reset Event", + "title": "Reset Password Email", "path": "https://docs.medusajs.com/resources/commerce-modules/auth/reset-password" }, { @@ -43,6 +43,10 @@ export const server = [ "title": "Extend Promotion", "path": "https://docs.medusajs.com/resources/commerce-modules/promotion/extend" }, + { + "title": "Invite User Email", + "path": "https://docs.medusajs.com/resources/commerce-modules/user/invite-user-subscriber" + }, { "title": "Implement Custom Line Item Pricing in Medusa", "path": "https://docs.medusajs.com/resources/examples/guides/custom-item-price"