From a61c3654cc8ea71aad5c9b168aa7627548b27b4a Mon Sep 17 00:00:00 2001 From: Krzysztof Polak Date: Sat, 14 Feb 2026 10:42:39 +0100 Subject: [PATCH 1/2] chore: update docs --- README.md | 141 +++------- docs/admin.md | 48 ++-- docs/blocks.md | 465 +++++++++------------------------ docs/configuration.md | 242 +++++++++-------- docs/templates.md | 433 +++++++++++++++--------------- docs/templates/contact-form.md | 115 -------- docs/templates/order-placed.md | 148 ----------- 7 files changed, 531 insertions(+), 1061 deletions(-) delete mode 100644 docs/templates/contact-form.md delete mode 100644 docs/templates/order-placed.md diff --git a/README.md b/README.md index 4ea8687..fbedd81 100755 --- a/README.md +++ b/README.md @@ -1,26 +1,17 @@ # Medusa plugin notification emails -A comprehensive notification plugin for Medusa v2 that provides a flexible, block-based template system for multiple channels (Email and Slack) with internationalization support, custom translations, and seamless integration with Medusa's notification module. - -## Features - -- **Multi-Channel Templates**: Pre-built templates for Email and Slack channels -- **Block-Based System**: Templates are built using a flexible block system that can be stored in a database for future builder functionality -- **Email Templates**: Customizable email templates built with [React Email](https://react.email) for common use cases -- **Slack Templates**: Slack Block Kit compatible templates for Slack notifications -- **Template Service Architecture**: Unified `AbstractTemplateService` with channel-specific implementations (`EmailTemplateService`, `SlackTemplateService`) -- **Automatic Interpolation**: Smart variable interpolation system that processes `{{data.*}}` and `{{translations.*}}` placeholders recursively -- **Internationalization**: Built-in support for multiple locales (Polish, English) -- **Customizable**: Override translations and customize templates without modifying core files -- **Integration**: Integrates with Medusa's notification module -- **Admin Panel**: Preview and test templates directly from Medusa Admin -- **Type-Safe**: Full TypeScript support with exported types -- **HTML & Plain Text**: Automatically generates both HTML and plain text versions of emails +Medusa v2 plugin for notification templating with support for: + +- email and slack channels +- template types (`system`, `db`, `external`) +- admin template builder (`/app/mpn/templates`) +- workflow-first rendering +- translation overrides and external templates ## Compatibility -- **Medusa Version**: `>= 2.8.8` -- **Node Version**: `>= 20` +- Medusa: `>= 2.8.8` +- Node.js: `>= 20` ## Installation @@ -30,110 +21,46 @@ npm install @codee-sh/medusa-plugin-notification-emails yarn add @codee-sh/medusa-plugin-notification-emails ``` -## Quick Start - -### 1. Register the Plugin +## Basic setup -Add to your `medusa-config.ts`: +Register plugin in `medusa-config.ts`: ```typescript module.exports = defineConfig({ plugins: [ - "@codee-sh/medusa-plugin-notification-emails" - ] -}) -``` - -### 2. Configure Notification Provider - -Set up a notification provider - see [Configuration Documentation](./docs/configuration.md) for details. - -### 3. Use Templates - -The plugin includes built-in subscribers that automatically send email notifications for various events. You can also use templates directly in your code: - -#### Email Templates - -```typescript -import { emailService, TEMPLATES_NAMES } from "@codee-sh/medusa-plugin-notification-emails/templates/emails" - -const { html, text, subject } = await emailService.render({ - templateName: TEMPLATES_NAMES.ORDER_PLACED, - data: templateData, - options: { locale: "pl" } -}) -``` - -#### Slack Templates - -```typescript -import { slackService, TEMPLATES_NAMES } from "@codee-sh/medusa-plugin-notification-emails/templates/slack" - -const { blocks } = await slackService.render({ - templateName: TEMPLATES_NAMES.INVENTORY_LEVEL, - data: templateData, - options: { locale: "en" } + { + resolve: "@codee-sh/medusa-plugin-notification-emails", + options: { + customTranslations: {}, + extend: { + services: [], + }, + }, + }, + ], }) ``` -**Note**: Templates use a block-based system where each template is defined as an array of blocks. The system automatically interpolates variables like `{{data.order.id}}` and `{{translations.headerTitle}}` throughout the blocks. - -See [Templates Documentation](./docs/templates.md) for detailed usage examples. - -## Available Templates - -### Email Templates - -- **[Order Placed](./docs/templates/order-placed.md)** (`order-placed`) - Order confirmation email template -- **Order Completed** (`order-completed`) - Order completion notification template -- **[Contact Form](./docs/templates/contact-form.md)** (`contact-form`) - Contact form submission email template -- **Order Updated** (`order-updated`) - Order update notification template -- **Inventory Level** (`inventory-level`) - Inventory level notification template - -### Slack Templates - -- **Inventory Level** (`inventory-level`) - Inventory level notification for Slack -- **Product** (`product`) - Product notification for Slack -- **Product Variant** (`product-variant`) - Product variant notification for Slack - -See [Templates Documentation](./docs/templates.md) for general template information and [Blocks Documentation](./docs/blocks.md) for details on the block system. - -## Built-in Subscribers - -The plugin includes automatic email notifications for the following events: - -- **`order.placed`** - Sends order confirmation email when an order is placed -- **`order.completed`** - Sends order completion notification when an order is completed - -These subscribers automatically: -- Fetch order data from Medusa -- Render email templates using React Email -- Send notifications via Medusa's notification module -- Respect custom translations configured in plugin options - -See [Configuration Documentation](./docs/configuration.md) for details on customizing subscriber behavior. - -## Admin Panel - -Access the template preview in Medusa Admin at `/app/notifications/render`. See [Admin Panel Documentation](./docs/admin.md) for details. +Then continue with configuration and usage guides in `docs/`. ## Documentation -- [Templates](./docs/templates.md) - Using templates and creating custom subscribers -- [Blocks System](./docs/blocks.md) - Understanding the block-based template system -- [Translations](./docs/translations.md) - Internationalization and custom translations -- [Configuration](./docs/configuration.md) - Plugin configuration options -- [Admin Panel](./docs/admin.md) - Admin interface usage -- [Creating Custom Templates](./docs/contributing/creating-templates.md) - Guide for creating new templates +- [Configuration](./docs/configuration.md) +- [Templates](./docs/templates.md) +- [Blocks](./docs/blocks.md) +- [Translations](./docs/translations.md) +- [Admin](./docs/admin.md) +- [Creating Templates](./docs/contributing/creating-templates.md) ## Exports -The plugin exports the following: +Public entrypoints: -- `@codee-sh/medusa-plugin-notification-emails/templates/emails` - Email template service and types -- `@codee-sh/medusa-plugin-notification-emails/templates/slack` - Slack template service and types -- `@codee-sh/medusa-plugin-notification-emails/templates/shared` - Shared template utilities -- `@codee-sh/medusa-plugin-notification-emails/utils` - Utility functions +- `@codee-sh/medusa-plugin-notification-emails/workflows` +- `@codee-sh/medusa-plugin-notification-emails/modules/*` +- `@codee-sh/medusa-plugin-notification-emails/providers/*` +- `@codee-sh/medusa-plugin-notification-emails/utils` +- `@codee-sh/medusa-plugin-notification-emails/admin` ## License diff --git a/docs/admin.md b/docs/admin.md index 87c9dd6..0325b59 100644 --- a/docs/admin.md +++ b/docs/admin.md @@ -1,36 +1,32 @@ -# Admin Panel Documentation +# Admin Panel Guide -The plugin provides an admin interface for previewing and testing email templates. +This page describes the administrator experience in the plugin panel. -## Accessing the Admin Panel +## What you can do -Navigate to **Notifications > Preview Template** in your Medusa Admin dashboard, or directly access: +### Notifications -``` -/app/notifications/render -``` +- view notification history +- review notification details +- verify which events triggered notifications -## Features +### Templates -### Template Selection +- browse available templates +- create and edit template content +- organize block order and structure +- update text and content fields -Choose from available templates: -- **Order Placed**: Order confirmation email template -- **Contact Form**: Contact form submission email template +## Typical admin workflow -### Template Preview +1. Open templates and select the template you want to update. +2. Edit content and block layout. +3. Save changes. +4. Trigger or wait for a notification event. +5. Check notification history to confirm output. -Each template group provides: +## Related documentation -1. **Live Preview**: See how the email will look with sample data -2. **Locale Switching**: Test different language versions -3. **Custom Data**: Modify template data to see how it affects the output -4. **HTML/Text View**: Toggle between HTML and plain text versions - -## Using the Preview - -1. **Select Template**: Choose a template type from the dropdown -2. **Fill Data**: Enter or modify template data -3. **Choose Locale**: Select language (Polish or English) -4. **Preview**: View the rendered email template -5. **Test**: Use the preview to verify translations and formatting \ No newline at end of file +- [Templates Documentation](./templates.md) +- [Blocks Documentation](./blocks.md) +- [Configuration Documentation](./configuration.md) \ No newline at end of file diff --git a/docs/blocks.md b/docs/blocks.md index 2ea8e3d..a5d56bf 100644 --- a/docs/blocks.md +++ b/docs/blocks.md @@ -1,228 +1,82 @@ -# Blocks System Documentation +# Blocks Documentation -The plugin uses a block-based template system that allows templates to be defined as arrays of blocks. This architecture enables flexibility, reusability, and prepares the system for future database storage and visual template builders. +This document is split into three parts: -## Overview +1. `db` templates (builder storage + admin fields) +2. `system` and `external` templates (code authoring) +3. final block catalog used by all template types -Templates are defined as arrays of blocks. +## 1) `db` templates - how blocks work in builder -Blocks support automatic variable interpolation using `{{data.*}}` and `{{translations.*}}` syntax. +In builder mode, blocks are persisted in a universal DB model (channel-agnostic): -## Email Blocks +- block identity and tree: `type`, `parent_id`, `position` +- block payload: `metadata` +- template language: `locale` on template record -Email templates use a custom block system that renders to React Email components. Available block types: +Builder endpoints: -### Section Block +- `GET /api/admin/mpn/templates/:id/blocks` - load DB block tree +- `POST /api/admin/mpn/templates/:id/blocks` - save DB block tree -Container block that groups other blocks together. +### Where do we define what block saves in DB and what is shown in Admin? -```typescript -{ - type: "section", - id: "section-1", - props: { - blocks: [ - // Nested blocks - ] - } -} -``` +Block field definitions are configured in service block schemas: -### Heading Block +- `src/modules/mpn-builder/services-local/email-template-service.ts` +- `src/modules/mpn-builder/services-local/slack-template-service.ts` -Renders a heading text. +Those schemas define: -```typescript -{ - type: "heading", - id: "heading-1", - props: { - value: "{{translations.headerTitle}}" - } -} -``` +- block type (`heading`, `row`, `repeater`, etc.) +- field keys (`value`, `label`, `arrayPath`, etc.) +- field input types in Admin (`text`, `textarea`, ...) -### Text Block +Admin form fields map directly to `metadata` keys in DB. -Renders plain text content. +### DB -> final rendering flow -```typescript -{ - type: "text", - id: "text-1", - props: { - value: "{{translations.headerDescription}}" - } -} -``` +For `db` templates: -### Row Block +- email flow transforms DB `metadata` into final email block `props` +- slack flow transforms DB `metadata` into final Slack Block Kit structure +- after transformation, rendering uses the same final block contract as `system`/`external` -Renders a label-value pair in a row layout. +This means: -```typescript -{ - type: "row", - id: "row-1", - props: { - label: "{{translations.labels.orderNumber}}", - value: "{{data.order.transformed.order_number}}" - } -} -``` +- `db` differs by source/storage +- final rendering behavior is shared -### Product Item Block +### `db` practical note about translations -Renders a product item with thumbnail, title, and details. +For `db` blocks, prefer: -```typescript -{ - type: "product-item", - id: "product-item-1", - props: { - label: "{{translations.labels.product}}", - thumbnail: "{{data.order.transformed.items.thumbnail}}", - value: "{{data.order.transformed.items.title}} - {{data.order.transformed.items.quantity}}x {{data.order.transformed.items.price}}" - } -} -``` - -### Repeater Block +- `{{data.*}}` placeholders +- explicit user text in `metadata` -Repeats a set of blocks for each item in an array. - -```typescript -{ - type: "repeater", - id: "repeater-1", - props: { - arrayPath: "order.transformed.items", - itemBlocks: [ - { - type: "product-item", - props: { - label: "{{translations.labels.product}}", - thumbnail: "{{data.order.transformed.items.thumbnail}}", - value: "{{data.order.transformed.items.title}}" - } - } - ] - } -} -``` +`{{translations.*}}` is mainly for `system`/`external` templates where translation dictionaries are defined in code. -**Properties**: -- `arrayPath` - Dot-notation path to the array in the data object (e.g., `"order.transformed.items"`) -- `itemBlocks` - Array of blocks to repeat for each item in the array - -**Note**: Inside `itemBlocks`, `{{data.*}}` variables are resolved relative to the current array item. The `arrayPath` is automatically prepended to data variable paths. - -### Separator Block - -Renders a horizontal separator line. - -```typescript -{ - type: "separator", - id: "separator-1" -} -``` +## 2) `system` and `external` templates - authoring in code -### Variable Interpolation in Email Blocks - -All string properties in block `props` are automatically interpolated. The interpolation uses `multiInterpolate` function which processes both `{{data.*}}` and `{{translations.*}}` variables. - -**How it works for Email**: -- All string values in `props` are processed -- Nested blocks are processed recursively -- Variables are resolved using the same `multiInterpolate` function as other channels - -## Slack Blocks - -Slack templates use [Slack Block Kit](https://api.slack.com/block-kit) format. The system automatically interpolates all `text` and `url` properties recursively throughout the block structure. - -### Header Block - -```typescript -{ - type: "header", - text: { - type: "plain_text", - text: "{{translations.header.title}}" - } -} -``` - -### Section Block - -```typescript -{ - type: "section", - text: { - type: "mrkdwn", - text: "{{translations.section.text}}" - } -} -``` - -### Section with Fields - -You can create dynamic fields using `fieldsPath` and `fieldTemplate`: - -```typescript -{ - type: "section", - fieldsPath: "inventory_level.stock_locations", - fieldTemplate: { - type: "plain_text", - text: "{{data.inventory_level.stock_locations.name}}" - } -} -``` - -**Properties**: -- `fieldsPath` - Dot-notation path to an array in the data object -- `fieldTemplate` - Template object for each field (will be interpolated for each array item) - -The system automatically creates a `fields` array from the array at `fieldsPath`, using `fieldTemplate` as a template for each item. - -### Actions Block - -```typescript -{ - type: "actions", - elements: [ - { - type: "button", - text: { - type: "plain_text", - text: "{{translations.actions.openInPanel}}" - }, - url: "https://example.com/app/inventory/{{data.inventory_level.inventory_item.id}}", - style: "primary" - } - ] -} -``` +For `system` and `external`, you author final blocks directly in code (`blocks` + `translations`). -### Variable Interpolation in Slack Blocks +Important distinction: -All `text` and `url` properties in Slack blocks are automatically interpolated recursively throughout the entire block structure. The interpolation uses `multiInterpolate` function which processes both `{{data.*}}` and `{{translations.*}}` variables. +- `section` is a valid final block in code templates (`system`/`external`) +- `section` is not exposed as a DB builder block type +- in DB builder, use `group` as container; it is later transformed to final channel structure -**How it works for Slack**: -- Only `text` and `url` properties are processed (not all properties) -- The system recursively traverses the entire block tree -- Variables are resolved using the same `multiInterpolate` function as other channels +Example locations: -## Block Structure Example +- system templates: plugin template config files +- external templates: your package/file imported via `options.extend.services` -### Email Template Example +### Final email example ```typescript export const templateBlocks = [ { type: "section", - id: "section-1", props: { blocks: [ { @@ -232,28 +86,26 @@ export const templateBlocks = [ } }, { - type: "text", + type: "row", props: { - value: "{{translations.headerDescription}}" + label: "{{translations.labels.orderNumber}}", + value: "{{data.order.transformed.order_number}}" } } ] } }, { - type: "separator", - id: "separator-1" - }, - { - type: "section", - id: "section-2", + type: "repeater", props: { - blocks: [ + arrayPath: "order.transformed.items", + itemBlocks: [ { - type: "row", + type: "product-item", props: { - label: "{{translations.labels.orderNumber}}", - value: "{{data.order.transformed.order_number}}" + label: "{{translations.labels.product}}", + thumbnail: "{{data.order.transformed.items.thumbnail}}", + value: "{{data.order.transformed.items.title}} - {{data.order.transformed.items.quantity}}x" } } ] @@ -262,7 +114,7 @@ export const templateBlocks = [ ] ``` -### Slack Template Example +### Final slack example ```typescript export const templateBlocks = [ @@ -270,7 +122,14 @@ export const templateBlocks = [ type: "header", text: { type: "plain_text", - text: "{{translations.header.title}}" + text: "{{translations.headerTitle}}" + } + }, + { + type: "section", + text: { + type: "mrkdwn", + text: "{{translations.headerDescription}}" } }, { @@ -278,147 +137,83 @@ export const templateBlocks = [ fieldsPath: "inventory_level.stock_locations", fieldTemplate: { type: "plain_text", - text: "{{data.inventory_level.stock_locations.name}}" + text: "{{data.inventory_level.stock_locations.name}}: {{data.inventory_level.stock_locations.quantity}}" } }, { - type: "actions", - elements: [ - { - type: "button", - text: { - type: "plain_text", - text: "{{translations.actions.openInPanel}}" - }, - url: "/app/inventory/{{data.inventory_level.inventory_item.id}}", - style: "primary" - } - ] + type: "divider" } ] ``` -## Variable Interpolation in Blocks - -All blocks support automatic variable interpolation using `{{data.*}}` and `{{translations.*}}` syntax. The system uses the `multiInterpolate` function which processes variables recursively for both Email and Slack channels. - -### Data Variables - -Access data from the template data object using `{{data.*}}` prefix: - -```typescript -// Example in Email block -{ - type: "text", - props: { - value: "Order {{data.order.id}}" - } -} - -// Example in Slack block -{ - type: "section", - text: { - type: "mrkdwn", - text: "Order {{data.order.id}}" - } -} -``` - -### Translation Variables - -Access translations using `{{translations.*}}` prefix: - -```typescript -// Example in Email block -{ - type: "heading", - props: { - value: "{{translations.headerTitle}}" - } -} - -// Example in Slack block -{ - type: "header", - text: { - type: "plain_text", - text: "{{translations.headerTitle}}" - } -} -``` - -### Nested Access - -Both `{{data.*}}` and `{{translations.*}}` support nested property access using dot notation: - -```typescript -// Access nested data -{{data.order.transformed.summary.total}} - -// Access nested translations -{{translations.labels.orderTotal}} -``` - -### Channel-Specific Processing - -While the interpolation function (`multiInterpolate`) is the same for both channels, the processing method differs: - -- **Email blocks**: All string properties in `props` are interpolated -- **Slack blocks**: Only `text` and `url` properties are recursively interpolated throughout the block tree - -For channel-specific details, see the "Variable Interpolation" sections within [Email Blocks](#email-blocks) and [Slack Blocks](#slack-blocks) sections above. - -## Custom Blocks - -You can override template blocks by providing custom blocks in the render options: - -```typescript -const { html, text, subject } = await emailService.render({ - templateName: TEMPLATES_NAMES.ORDER_PLACED, - data: templateData, - options: { - blocks: [ - { - type: "section", - props: { - blocks: [ - { - type: "heading", - props: { - value: "Custom Header" - } - } - ] - } - } - ] - } -}) -``` - -When custom blocks are provided, they completely replace the template's default blocks. Variables in custom blocks are still automatically interpolated. +## 3) Final block catalog (shared target model) + +This is the final block model used during rendering, regardless of source (`db`, `system`, `external`). + +### Email final blocks + +- `section` (code templates) + - note: used directly in `system`/`external` configs + - in `db` flow, container behavior usually comes from `group` transformed to `props.blocks` +- `section` + - purpose: container + - required data: `props.blocks` +- `heading` + - purpose: title + - required data: `props.value` +- `text` + - purpose: paragraph/content + - required data: `props.value` +- `row` + - purpose: label/value pair + - required data: `props.label`, `props.value` +- `separator` + - purpose: visual divider + - required data: none +- `group` + - purpose: nested grouping + - required data: `props.blocks` +- `repeater` + - purpose: repeat nested blocks for array + - required data: `props.arrayPath`, `props.itemBlocks` +- `product-item` (email) + - purpose: product row + - required data: `props.label`, `props.thumbnail`, `props.value` + +### Slack final blocks (Block Kit) + +- `header` + - purpose: header text + - required data: `text.type`, `text.text` +- `section` with `text` + - purpose: body text + - required data: `text.type`, `text.text` +- `section` with `fields` + - purpose: compact list/key-value display + - required data: `fields[]` +- `section` with `fieldsPath` + `fieldTemplate` (plugin helper) + - purpose: dynamically generate `fields` from array data + - required data: `fieldsPath`, `fieldTemplate` +- `actions` + - purpose: buttons/actions + - required data: `elements[]` +- `divider` + - purpose: visual separation + - required data: none ## Best Practices -1. **Use IDs**: Always provide unique `id` values for blocks when possible -2. **Use Translations**: Always use `{{translations.*}}` for user-facing text -3. **Use Data Variables**: Use `{{data.*}}` for dynamic content from your data -4. **Test Interpolation**: Verify that all variables are correctly interpolated - -## Future: Database Storage - -The block-based system is designed to support future database storage. Templates can be stored as JSON in a database and loaded dynamically, enabling: - -- Visual template builders -- User-customizable templates -- A/B testing of templates -- Template versioning +1. Use `db` when template should be editable in Admin. +2. Use `system`/`external` when template should be managed in code with translation dictionaries. +3. Keep block payload minimal and block-specific. +4. In `db`, configure admin-editable keys via service block field definitions. +5. In `db`, prefer `group` for nesting; in `system`/`external`, use `section` directly where needed. +6. Design final blocks first, then map DB metadata to those final fields. ## See Also -- [Templates Documentation](./templates.md) - Using templates with blocks -- [Translations Documentation](./translations.md) - Variable interpolation details -- [Creating Custom Templates](./contributing/creating-templates.md) - Creating templates with blocks -- [Slack Block Kit Documentation](https://api.slack.com/block-kit) - Official Slack Block Kit reference +- [Templates Documentation](./templates.md) - Template types and workflows +- [Translations Documentation](./translations.md) - Interpolation and locale behavior +- [Creating Custom Templates](./contributing/creating-templates.md) - Contributor guide +- [Slack Block Kit Documentation](https://api.slack.com/block-kit) - Slack format reference diff --git a/docs/configuration.md b/docs/configuration.md index 238f34d..b41e025 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,10 +1,10 @@ # Configuration Documentation -Complete guide to configuring the `@codee-sh/medusa-plugin-notification-emails` plugin. +This page describes plugin configuration only. -## Plugin Registration +## Plugin registration -Register the plugin in your `medusa-config.ts`: +Register the plugin in `medusa-config.ts`: ```typescript module.exports = defineConfig({ @@ -12,163 +12,195 @@ module.exports = defineConfig({ { resolve: "@codee-sh/medusa-plugin-notification-emails", options: { - // Plugin options here - } - } - ] + customTranslations: {}, + extend: { + services: [], + }, + }, + }, + ], }) ``` -## Configuration Options - -### `customTranslations` - -Override default translations for templates. +## Options -**Type**: `Record>>` +## `customTranslations` -See [Translations Documentation](./translations.md) for detailed information, examples, and best practices. +Overrides for translation dictionaries. -## Notification Provider Configuration +Type: -The plugin works with Medusa's notification module. You need to configure a notification provider to send emails. +`Record>>` -The template system is compatible with any Medusa notification provider, including SendGrid, Resend, and other email providers. When using `content` instead of `template` in `createNotifications`, providers will use your custom HTML instead of their own templates. +Guidelines: -See [Templates Documentation](./templates.md) for examples of using templates with notification providers. +- use template id as first key (for example `system_order-placed`) +- use locale (`pl`, `en`, ...) as second key +- provide translation object as value -## Subscribers +Example: -The plugin includes built-in subscribers that automatically send email notifications for various Medusa events. These subscribers are registered automatically when the plugin is loaded. +```typescript +module.exports = defineConfig({ + plugins: [ + { + resolve: "@codee-sh/medusa-plugin-notification-emails", + options: { + customTranslations: { + "system_order-placed": { + pl: { + headerTitle: "#{{data.order.transformed.order_number}} - Zamowienie", + labels: { + orderNumber: "Numer zamowienia", + }, + }, + }, + }, + }, + }, + ], +}) +``` -### Available Subscribers +More details: [Translations Documentation](./translations.md) -#### `order.placed` +## `backend_url` (project value for links) -Sends an order confirmation email when a new order is placed. +In many projects, a backend/admin base URL is stored in plugin options: -- **Event**: `order.placed` -- **Template**: `order-placed` -- **Trigger**: Automatically triggered when an order is created -- **Email sent to**: Customer email address from the order +```typescript +backend_url: process.env.POS_BACKEND_URL +``` -**Template Data Includes**: -- Order number and date -- Sales channel information -- Order items with prices and quantities -- Shipping and billing addresses -- Order summary (totals, taxes, discounts) -- Order URL (if available) +Use this value when your templates build links (especially Slack buttons/URLs), for example via `{{data.backend_url}}` or `{{data.backendUrl}}` in template blocks. -#### `order.completed` +Recommended pattern: -Sends an order completion notification when an order is marked as completed. +- keep URL in config (`backend_url`) +- pass it to template render input data/options in your project flow +- use consistent variable naming in template blocks -- **Event**: `order.completed` -- **Template**: `order-completed` -- **Trigger**: Automatically triggered when an order status changes to completed -- **Email sent to**: Customer email address from the order +## `extend.services` -**Template Data Includes**: -- Order number and dates (order date and completion date) -- Sales channel information -- Order items with prices and quantities -- Shipping and billing addresses -- Order summary (totals, taxes, discounts) -- Order URL (if available) +Register external templates for services (`email`, `slack`). -### Customizing Subscribers +Type: -To customize subscriber behavior: +```typescript +{ + extend?: { + services?: Array<{ + id: string + templates?: Array<{ + name: string + path: string + }> + }> + } +} +``` -1. **Override translations**: Use `customTranslations` in plugin options to customize email content (see [Translations Documentation](./translations.md)) +Example: ```typescript +import path from "path" + module.exports = defineConfig({ plugins: [ { resolve: "@codee-sh/medusa-plugin-notification-emails", options: { - customTranslations: { - "order-placed": { - pl: { - headerTitle: "#{{orderNumber}} - Twoje zamówienie" - } - }, - "order-completed": { - pl: { - headerTitle: "#{{orderNumber}} - Zamówienie zrealizowane" - } - } - } - } - } - ] + extend: { + services: [ + { + id: "slack", + templates: [ + { + name: "external_inventory-alert", + path: path.resolve( + require.resolve("@your-package/templates/slack/inventory-alert") + ), + }, + ], + }, + ], + }, + }, + }, + ], }) ``` -2. **Create custom subscribers**: Create your own subscribers using template rendering functions (see [Templates Documentation](./templates.md)) - -### How Subscribers Work - -1. **Event Detection**: Subscribers listen to Medusa events (`order.placed`, `order.completed`) -2. **Data Fetching**: When an event is triggered, the subscriber fetches relevant data (order details, customer info, etc.) -3. **Template Rendering**: The subscriber uses `renderTemplate()` to generate HTML and plain text versions using React Email -4. **Notification Sending**: The rendered email is sent via Medusa's notification module using the configured provider -5. **Translation Support**: Custom translations from plugin options are automatically applied - -### Disabling Subscribers +Notes: -If you need to disable a subscriber, you can create your own subscriber that handles the same event and it will take precedence, or modify the plugin's subscriber files directly. +- templates are loaded during module initialization +- if service id is unknown, template registration is skipped +- registered templates are available as type `external` -## Complete Configuration Example +## Minimal complete example ```typescript +import path from "path" + module.exports = defineConfig({ plugins: [ { resolve: "@codee-sh/medusa-plugin-notification-emails", options: { + backend_url: process.env.POS_BACKEND_URL, customTranslations: { - "order-placed": { - pl: { - // Use string interpolation with {{variable}} syntax - headerTitle: "#{{orderNumber}} - Zamówienie zostało złożone", + "system_order-placed": { + en: { labels: { - orderNumber: "Numer zamówienia" - } - } - } - } - } - } + orderNumber: "Order number", + }, + }, + }, + }, + extend: { + services: [ + { + id: "email", + templates: [ + { + name: "external_order-summary", + path: path.resolve( + require.resolve("@your-scope/templates/email/order-summary") + ), + }, + ], + }, + ], + }, + }, + }, ], - // Configure your notification provider in modules - // See Medusa documentation for provider setup }) ``` -**Note**: Custom translations use JSON structure with string interpolation. Variables are replaced using `{{variable}}` syntax, where `variable` matches properties in the template data object. +## Notification providers + +This plugin renders notification content. +Delivery is still handled by configured Medusa notification providers. -See [Translations Documentation](./translations.md) for more examples and details about the translation system and variable interpolation. +Provider setup is configured in Medusa modules. ## Troubleshooting -### Translations Not Applied +### Translations not applied -- Ensure `customTranslations` structure matches: `templateName > locale > translations` -- Check that locale matches supported locales (`pl`, `en`) -- Verify that base translations are preserved when overriding +- make sure template key in `customTranslations` matches template id used at render time +- verify locale key (`pl`, `en`, ...) +- verify translation keys used in templates exist -### Templates Not Rendering +### External template not visible -- Verify template name matches `TEMPLATES_NAMES` constants -- Check that all required data fields are provided -- Ensure notification provider is properly configured +- verify `extend.services[].id` matches a registered service (`email` or `slack`) +- verify `path` resolves correctly and module can be imported +- check startup logs for warning messages during template registration -### Emails Not Sending +### Notification not sent -- Verify notification provider is properly configured -- Check that you're using `content` instead of `template` in `createNotifications` -- Ensure provider credentials and settings are correct +- verify Medusa notification provider is configured and working +- verify your notification flow/subscriber is triggered for expected event diff --git a/docs/templates.md b/docs/templates.md index f307546..9897e60 100644 --- a/docs/templates.md +++ b/docs/templates.md @@ -1,299 +1,282 @@ # Templates Documentation -This plugin provides a flexible, block-based template system for multiple channels (Email and Slack). Templates are defined as arrays of blocks with automatic variable interpolation and translation support. This guide explains how to use existing templates in your code and create custom subscribers. +This plugin supports three template sources in `v1.0.0`: -## Template Service Architecture +- `system` - built-in templates shipped with the plugin +- `db` - templates created and edited in the builder (stored in database) +- `external` - templates registered from your own packages/files via plugin options -The plugin uses a service-based architecture with channel-specific template services: +The primary rendering path is workflow-based and uses `template_id`. -- **`AbstractTemplateService`** - Base abstract class providing common functionality -- **`EmailTemplateService`** - Email template service (extends `AbstractTemplateService`) -- **`SlackTemplateService`** - Slack template service (extends `AbstractTemplateService`) +## Template Types and Services -Each service is a singleton instance exported from the respective channel module: -- `emailService` - Email template service instance -- `slackService` - Slack template service instance +### Template types -## Available Templates +- `system` +- `db` +- `external` -### Email Templates +### Template services (channels) -- **[Order Placed](./templates/order-placed.md)** (`order-placed`) - Order confirmation email template -- **Order Completed** (`order-completed`) - Order completion notification email template -- **[Contact Form](./templates/contact-form.md)** (`contact-form`) - Contact form submission email template -- **Order Updated** (`order-updated`) - Order update notification template -- **Inventory Level** (`inventory-level`) - Inventory level notification template +- `email` +- `slack` -### Slack Templates +Type/service combinations are available through Admin API: -- **Inventory Level** (`inventory-level`) - Inventory level notification for Slack -- **Product** (`product`) - Product notification for Slack -- **Product Variant** (`product-variant`) - Product variant notification for Slack +- `GET /api/admin/mpn/templates/types` +- `GET /api/admin/mpn/templates/types/:type/services` +- `GET /api/admin/mpn/templates/types/:type/services/:service/templates` -## Using Email Templates +## How templates are resolved at runtime -### Basic Usage +Rendering workflows read `template_id` and resolve source automatically: -Use the `emailService` singleton to render email templates: +- `template_id` starting with `system_` -> `system` template registry +- any other `template_id` -> DB lookup (`db` template) +- `external` templates are available in service registry when registered in config -```typescript -import { emailService, TEMPLATES_NAMES } from "@codee-sh/medusa-plugin-notification-emails/templates/emails" - -const { html, text, subject } = await emailService.render({ - templateName: TEMPLATES_NAMES.ORDER_PLACED, - data: { - order: { - id: "order_123", - transformed: { - order_number: "ORD-12345", - order_date: "2024-01-15", - // ... other order data - } - } - }, - options: { - locale: "pl" - } -}) -``` +Main workflows: + +- `emailServiceWorkflow` (`mpn-builder-email-service`) +- `slackServiceWorkflow` (`mpn-builder-slack-service`) + +## Translations vs locale by template type + +`v1.0.0` uses two different mechanisms depending on template source: + +- `system` and `external` templates: can use `{{translations.*}}`, because they provide translation dictionaries in template config +- `db` templates: language is selected by template `locale` stored in DB (`channel` + `locale` are set during template creation) + +In practice for `db` templates: + +- prefer `{{data.*}}` and explicit text in block `metadata` +- do not rely on `{{translations.*}}` in DB block payload +- create separate DB templates per language when needed (e.g. `pl`, `en`) + +## Creating templates + +## 1) System templates (code-owned) + +Use this when template should be part of plugin codebase and versioned with release. + +System templates are registered inside local services: + +- `src/modules/mpn-builder/services-local/email-template-service.ts` +- `src/modules/mpn-builder/services-local/slack-template-service.ts` + +Each template provides: + +- `blocks` +- `translations` + +Example IDs: + +- email: `system_order-placed`, `system_contact-form` +- slack: `system_inventory-level`, `system_product` -**Returns**: -- `html` - HTML version of the email (string) -- `text` - Plain text version of the email (string) -- `subject` - Email subject line (string, generated from translations) +System templates are read-only from builder DB perspective. -### Synchronous Rendering (for Previews) +## 2) DB templates (builder-owned) -For React-based previews, use `renderSync`: +Use this when template should be editable from Admin UI. + +### Create template record + +Endpoint: + +- `POST /api/admin/mpn/templates` + +Payload shape: ```typescript -const { reactNode } = emailService.renderSync({ - templateName: TEMPLATES_NAMES.ORDER_PLACED, - data: templateData, - options: { locale: "en" } -}) +{ + items: [ + { + name: "my-order-template", + label: "My order template", + description: "DB editable template", + channel: "email", // or "slack" + locale: "pl", // language assigned to this DB template + subject: "Order {{data.order.id}}", + is_active: true + } + ] +} ``` -**Returns**: -- `reactNode` - React node for rendering in preview components +### Save/update blocks -## Using Slack Templates +Endpoint: -### Basic Usage +- `POST /api/admin/mpn/templates/:id/blocks` -Use the `slackService` singleton to render Slack templates: +Payload shape: ```typescript -import { slackService, TEMPLATES_NAMES } from "@codee-sh/medusa-plugin-notification-emails/templates/slack" - -const { blocks } = await slackService.render({ - templateName: TEMPLATES_NAMES.INVENTORY_LEVEL, - data: { - inventory_level: { - id: "il_123", - stock_locations: [ - { name: "Warehouse A", quantity: 10 } - ], - inventory_item: { - id: "item_123" +{ + template_id: "tmpl_123", + blocks: [ + { + type: "heading", + position: 0, + parent_id: null, + metadata: { + value: "Podsumowanie zamowienia" + } + }, + { + type: "row", + position: 1, + parent_id: null, + metadata: { + label: "Numer zamowienia", + value: "{{data.order.transformed.order_number}}" } } - }, - options: { - locale: "en" - } -}) + ] +} ``` -**Returns**: -- `blocks` - Array of Slack Block Kit blocks ready to send to Slack API +DB model stores blocks as: + +- `type` +- `position` +- `parent_id` +- `metadata` (block config) -**Supported locales**: `pl` (Polish), `en` (English, default) +## 3) External templates (config-owned) -### Custom Blocks +Use this when templates live outside plugin repo (e.g. dedicated package). -You can override template blocks by providing custom blocks in options: +Register via `options.extend.services` in `medusa-config.ts`: ```typescript -const { html, text, subject } = await emailService.render({ - templateName: TEMPLATES_NAMES.ORDER_PLACED, - data: templateData, - options: { - locale: "pl", - blocks: [ - { - type: "section", - props: { - blocks: [ +import path from "path" + +module.exports = defineConfig({ + plugins: [ + { + resolve: "@codee-sh/medusa-plugin-notification-emails", + options: { + extend: { + services: [ { - type: "heading", - props: { - value: "Custom Header" - } + id: "email", + templates: [ + { + name: "external_order-summary", + path: path.resolve( + require.resolve("@your-scope/templates/email/order-summary") + ) + } + ] } ] } } - ] - } + } + ] }) ``` -When custom blocks are provided, they replace the template's default blocks. Variables in custom blocks are still interpolated automatically. - -## Variable Interpolation +After registration, template becomes available as type `external` for that service. -Templates use a powerful interpolation system that processes variables in two formats. For detailed information about the interpolation system, see [Translations Documentation](./translations.md). +## Rendering templates in your code -## Creating Custom Subscribers +### Email workflow -You can create your own subscribers that use the plugin's templates. This is useful when you want to send notifications for custom events or with custom logic. +```typescript +import { emailServiceWorkflow } from "@codee-sh/medusa-plugin-notification-emails/workflows/mpn-builder-services/email-service" + +const { + result: { html, text, subject }, +} = await emailServiceWorkflow(container).run({ + input: { + template_id: "system_order-placed", // or db template id + data: templateData, + options: { + locale: "en", + translations: { + headerTitle: "Custom title" + } + } + } +}) +``` -### Example: Custom Email Event Subscriber +### Slack workflow ```typescript -import { - SubscriberArgs, - type SubscriberConfig, -} from "@medusajs/medusa" -import { Modules } from "@medusajs/framework/utils" -import { emailService, TEMPLATES_NAMES } from "@codee-sh/medusa-plugin-notification-emails/templates/emails" +import { slackServiceWorkflow } from "@codee-sh/medusa-plugin-notification-emails/workflows/mpn-builder-services/slack-service" + +const { + result: { blocks }, +} = await slackServiceWorkflow(container).run({ + input: { + template_id: "system_inventory-level", + data: templateData, + options: { + locale: "en" + } + } +}) +``` -export default async function customEventHandler({ - event: { data }, - container, -}: SubscriberArgs<{ userId: string, message: string }>) { - const notificationModuleService = container.resolve(Modules.NOTIFICATION) +## Working with template lists - // Use contact form template for custom notifications - const { html, text, subject } = await emailService.render({ - templateName: TEMPLATES_NAMES.CONTACT_FORM, - data: { - name: "System Notification", - email: "system@example.com", - message: data.message, - }, - options: { locale: "en" } - }) +Useful API endpoints: - await notificationModuleService.createNotifications({ - to: "admin@example.com", - channel: "email", - content: { - subject, - html, - text, - }, - }) -} +- `GET /api/admin/mpn/templates` - DB templates +- `GET /api/admin/mpn/templates/types` - available template types +- `GET /api/admin/mpn/templates/types/:type/services` - services by type +- `GET /api/admin/mpn/templates/types/:type/services/:service/templates` - templates by type+service +- `GET /api/admin/mpn/available-templates` - template services and builder metadata -export const config: SubscriberConfig = { - event: "custom.event.name", -} -``` +## Custom subscribers (workflow-first) -### Example: Custom Slack Event Subscriber +In custom subscriber logic, call rendering workflow first, then pass rendered content to notification module. ```typescript -import { - SubscriberArgs, - type SubscriberConfig, -} from "@medusajs/medusa" import { Modules } from "@medusajs/framework/utils" -import { slackService, TEMPLATES_NAMES } from "@codee-sh/medusa-plugin-notification-emails/templates/slack" +import { emailServiceWorkflow } from "@codee-sh/medusa-plugin-notification-emails/workflows/mpn-builder-services/email-service" -export default async function inventoryEventHandler({ - event: { data }, - container, -}: SubscriberArgs<{ inventory_level: any }>) { +export default async function customEventHandler({ event: { data }, container }: any) { const notificationModuleService = container.resolve(Modules.NOTIFICATION) - const { blocks } = await slackService.render({ - templateName: TEMPLATES_NAMES.INVENTORY_LEVEL, - data: { - inventory_level: data.inventory_level, + const { + result: { html, text, subject }, + } = await emailServiceWorkflow(container).run({ + input: { + template_id: "system_contact-form", + data: { + name: "System Notification", + email: "system@example.com", + message: data.message, + }, + options: { locale: "en" }, }, - options: { locale: "en" } }) await notificationModuleService.createNotifications({ - to: "#inventory-alerts", - channel: "slack", - content: { - text: "Inventory level update", - blocks: blocks, - }, + to: "admin@example.com", + channel: "email", + content: { subject, html, text }, }) } - -export const config: SubscriberConfig = { - event: "inventory_level.updated", -} -``` - -## Registering Custom Templates - -You can register custom templates at runtime using the service's `registerTemplate` method: - -```typescript -import { emailService } from "@codee-sh/medusa-plugin-notification-emails/templates/emails" - -// Register a custom template -emailService.registerTemplate("custom-template", { - ...emailService.getBaseTemplate(), - getConfig: () => ({ - blocks: [ - { - type: "section", - props: { - blocks: [ - { - type: "heading", - props: { - value: "{{translations.title}}" - } - } - ] - } - } - ], - translations: { - pl: { - general: { - title: "Tytuł" - } - }, - en: { - general: { - title: "Title" - } - } - } - }) -}) - -// Use the custom template -const { html, text, subject } = await emailService.render({ - templateName: "custom-template", - data: {}, - options: { locale: "pl" } -}) ``` ## Best Practices -1. **Use existing templates**: Check available templates before creating custom ones -2. **Follow data structure**: Ensure template data matches the expected structure (see template-specific docs) -3. **Handle errors**: Always check if data exists before rendering -4. **Use translations**: Leverage the translation system for multi-language support -5. **Test rendering**: Test templates with sample data before deploying -6. **Use block system**: Define templates as blocks for flexibility and future database storage -7. **Leverage interpolation**: Use `{{data.*}}` and `{{translations.*}}` for dynamic content +1. Prefer workflow-based rendering (`emailServiceWorkflow`, `slackServiceWorkflow`) +2. Use `system` for release-owned templates, `db` for admin-editable templates, `external` for package-owned templates +3. Keep template IDs stable and explicit (`system_*` for built-ins) +4. For `db` templates keep user-facing text directly in block `metadata` and control language through template `locale` +5. Validate template data shape before rendering +6. Test rendering for all locales and for each template source (`system/db/external`) ## See Also -- [Blocks System Documentation](./blocks.md) - Understanding the block-based template system -- [Template-specific Documentation](./templates/) - Detailed docs for each template -- [Translations Documentation](./translations.md) - How translations and interpolation work -- [Configuration Documentation](./configuration.md) - Plugin configuration -- [Creating Custom Templates](./contributing/creating-templates.md) - Guide for creating new templates +- [Blocks Documentation](./blocks.md) - Block types, behavior, and DB structure +- [Translations Documentation](./translations.md) - Interpolation and translation overrides +- [Configuration Documentation](./configuration.md) - Plugin options and external template registration +- [Creating Custom Templates](./contributing/creating-templates.md) - Contributor-focused guide diff --git a/docs/templates/contact-form.md b/docs/templates/contact-form.md deleted file mode 100644 index 5c654cd..0000000 --- a/docs/templates/contact-form.md +++ /dev/null @@ -1,115 +0,0 @@ -# Contact Form Template - -Email template for contact form submissions. - -## Template Name - -- Constant: `TEMPLATES_NAMES.CONTACT_FORM` -- String: `"contact-form"` - -## Required Data - -```typescript -{ - name: string - email: string - phone?: string - message: string -} -``` - -## Example Usage - -### Basic Usage - -```typescript -import { renderTemplate, TEMPLATES_NAMES } from "@codee-sh/medusa-plugin-notification-emails/templates/emails" - -const { html, text, subject } = await renderTemplate( - TEMPLATES_NAMES.CONTACT_FORM, - { - name: "John Doe", - email: "john@example.com", - phone: "123456789", - message: "I have a question about..." - }, - { - locale: "en" - } -) -``` - -**Note**: `renderTemplate` is an async function that uses React Email to generate both HTML and plain text versions of the email. - -### With Custom Translations - -```typescript -const { html, text, subject } = await renderTemplate( - TEMPLATES_NAMES.CONTACT_FORM, - { - name: "John Doe", - email: "john@example.com", - message: "I have a question..." - }, - { - locale: "pl", - customTranslations: { - "contact-form": { - pl: { - // Use string interpolation with {{variable}} syntax - headerTitle: "Nowa wiadomość od {{name}}", - labels: { - name: "Imię i nazwisko", - email: "Adres e-mail" - } - } - } - } - } -) -``` - -**Note**: Custom translations use the same JSON structure as base translations. Variables are interpolated using `{{variable}}` syntax, where `variable` matches properties in the template data object. - -### With Notification Module - -```typescript -import { renderTemplate, TEMPLATES_NAMES } from "@codee-sh/medusa-plugin-notification-emails/templates/emails" -import { Modules } from "@medusajs/framework/utils" - -const notificationModuleService = container.resolve(Modules.NOTIFICATION) - -const { html, text, subject } = await renderTemplate( - TEMPLATES_NAMES.CONTACT_FORM, - { - name: "John Doe", - email: "john@example.com", - message: "I have a question..." - }, - { locale: "en" } -) - -await notificationModuleService.createNotifications({ - to: "support@example.com", - channel: "email", - content: { - subject, // Subject is automatically generated from translations - html, // HTML version generated by React Email - text // Plain text version generated by React Email - } -}) -``` - -## Template Fields - -The template displays the following information: - -- Contact name -- Contact email address -- Optional subject -- Message content - -## Translations - -See [Translations Documentation](../translations.md) for available translation keys and how to customize them. - diff --git a/docs/templates/order-placed.md b/docs/templates/order-placed.md deleted file mode 100644 index 67daaf3..0000000 --- a/docs/templates/order-placed.md +++ /dev/null @@ -1,148 +0,0 @@ -# Order Placed Template - -Email template sent when an order is placed. Includes order details, items, shipping information, and summary. - -## Template Name - -- Constant: `TEMPLATES_NAMES.ORDER_PLACED` -- String: `"order-placed"` - -## Required Data - -```typescript -{ - orderNumber: string - customerEmail: string - orderDate: string - items: Array<{ - title: string - quantity: number - price: string - thumbnail?: string - }> - sales_channel: { - name: string - description?: string - } - shippingAddress?: string - billingAddress?: string - summary: { - total: string - paid_total: string - tax_total: string - discount_total: string - currency_code: string - } - orderUrl?: string -} -``` - -## Example Usage - -### Basic Usage - -```typescript -import { renderTemplate, TEMPLATES_NAMES } from "@codee-sh/medusa-plugin-notification-emails/templates/emails" - -const { html, text, subject } = await renderTemplate( - TEMPLATES_NAMES.ORDER_PLACED, - { - orderNumber: "#12345", - customerEmail: "customer@example.com", - orderDate: "2024-01-15 10:30:00", - items: [ - { - title: "Product Name", - quantity: 2, - price: "$50.00", - thumbnail: "https://example.com/image.jpg" - } - ], - sales_channel: { - name: "Online Store" - }, - shippingAddress: "123 Main St, City, Country", - summary: { - total: "$100.00", - paid_total: "$100.00", - tax_total: "$10.00", - discount_total: "$0.00", - currency_code: "USD" - } - }, - { - locale: "pl", - theme: "default" - } -) -``` - -**Note**: `renderTemplate` is an async function that uses React Email to generate both HTML and plain text versions of the email. - -### With Custom Translations - -```typescript -const { html, text, subject } = await renderTemplate( - TEMPLATES_NAMES.ORDER_PLACED, - templateData, - { - locale: "pl", - customTranslations: { - "order-placed": { - pl: { - // Use string interpolation with {{variable}} syntax - headerTitle: "#{{orderNumber}} - Twoje zamówienie", - labels: { - orderNumber: "Numer zamówienia", - orderDate: "Data zamówienia" - } - } - } - } - } -) -``` - -**Note**: Custom translations use the same JSON structure as base translations. Variables are interpolated using `{{variable}}` syntax, where `variable` matches properties in the template data object. - -### With Notification Module - -```typescript -import { renderTemplate, TEMPLATES_NAMES } from "@codee-sh/medusa-plugin-notification-emails/templates/emails" -import { Modules } from "@medusajs/framework/utils" - -const notificationModuleService = container.resolve(Modules.NOTIFICATION) - -const { html, text, subject } = await renderTemplate( - TEMPLATES_NAMES.ORDER_PLACED, - templateData, - { locale: "pl" } -) - -await notificationModuleService.createNotifications({ - to: "customer@example.com", - channel: "email", - content: { - subject, // Subject is automatically generated from translations - html, // HTML version generated by React Email - text // Plain text version generated by React Email - } -}) -``` - -## Template Fields - -The template displays the following information: - -- Order number and date -- Sales channel information -- Order items with thumbnails, titles, quantities, and prices -- Shipping and billing addresses -- Order summary (totals, taxes, discounts) -- Optional order URL button -- Footer with contact information - -## Translations - -See [Translations Documentation](../translations.md) for available translation keys and how to customize them. - From 11c9b7eedfc9c8c6d77c5a23820cef5badbd426a Mon Sep 17 00:00:00 2001 From: Krzysztof Polak Date: Sat, 14 Feb 2026 10:52:59 +0100 Subject: [PATCH 2/2] chore: update docs --- README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index fbedd81..0fb0784 100755 --- a/README.md +++ b/README.md @@ -1,12 +1,16 @@ -# Medusa plugin notification emails +# Medusa plugin notification -Medusa v2 plugin for notification templating with support for: +Medusa v2 plugin for building and sending transactional notifications from one place. +It provides a template system, admin builder, and rendering workflows for email and Slack. -- email and slack channels -- template types (`system`, `db`, `external`) -- admin template builder (`/app/mpn/templates`) -- workflow-first rendering -- translation overrides and external templates +## Features + +- Build and manage notification templates for two channels: email and Slack +- Use three template sources: built-in (`system`), database-managed (`db`), and code-registered (`external`) +- Build templates quickly with a reusable block system instead of writing full markup from scratch +- Edit `db` templates in Medusa Admin using a block-based builder +- Render notifications through dedicated workflows and pass output to Medusa notification providers +- Customize translations and register external templates without changing core plugin code ## Compatibility