-Install the [Mintlify CLI](https://www.npmjs.com/package/mintlify) to preview the documentation changes locally. To install, use the following command
+
+
+
+
+### 👩💻 Docs Contribution
+
+[Checkout this video on how to contribute in Novu's documentation](https://www.loom.com/share/1667ee280c2448c7a40838b72b1399af)
+
+First, install the [Mintlify CLI](https://www.npmjs.com/package/mintlify) locally. To install, use the following command
```
npm i -g mintlify
```
-Run the following command at the root of your documentation (where mint.json is)
+**Note**: Please install Node.js (version 18 or higher) before proceeding.
+
+Run the following command at the root of the documentation (where mint.json is) to preview the documentation changes
```
mintlify dev
@@ -24,9 +65,9 @@ mintlify dev
### 😎 Publishing Changes
-Changes will be deployed to production automatically after pushing to the default branch.
+All changes to the docs should be submitted against the `staging` branch. [Preview the documentation changes](https://novu-preview.mintlify.app/introduction).
-You can also preview changes using PRs, which generates a preview link of the docs.
+PRs from `staging` to `main` will be deployed to production automatically after merge.
#### Troubleshooting
diff --git a/_snippets/snippet-example.mdx b/_snippets/snippet-example.mdx
deleted file mode 100644
index 089334c5..00000000
--- a/_snippets/snippet-example.mdx
+++ /dev/null
@@ -1,3 +0,0 @@
-## My Snippet
-
-This is an example of a reusable snippet
diff --git a/account/authentication.mdx b/account/authentication.mdx
new file mode 100644
index 00000000..301b9a01
--- /dev/null
+++ b/account/authentication.mdx
@@ -0,0 +1,44 @@
+---
+title: "Authentication"
+description: "Novu supports advanced user authentication capabilities like OAuth, SSO, MFA and more, providing enterprise customers with a robust, secure, and user-friendly authentication solution."
+---
+
+
+This feature is only available to Enterprise customers on our [Cloud platform](https://dashboard-v2.novu.co/?utm_campaign=docs_account_authentication). For more information, please see our [Enterprise plan](https://novu.co/pricing) in the pricing page.
+
+
+## Key capabilities
+
+### Out-of-the-box OAuth providers
+
+Currently providing authentication via Github, with the plan to expand to a number of other providers such as:
+- Google
+- Facebook
+- Apple
+- Microsoft
+- LinkedIn
+
+### SSO, SAML and OpenID connect
+
+Novu provides a streamlined implementation of SAML and OpenID Connect protocols, allowing integration with a wide range of enterprise IdPs. Arrange setup of known providers like Okta, Microsoft Entra ID, Google workspace or a custom SSO provider.
+
+Novu adheres to industry-standard security protocols to ensure secure and reliable user authentication.
+
+### Advanced security features
+
+- **Multi-Factor Authentication (MFA):** Robust MFA options including SMS passcodes, authenticator apps, hardware keys, and recovery codes, enhancing security and reducing the risk of unauthorized access.
+
+- **Session Management:** Comprehensive session management features including active device monitoring, session revocation, and adaptive session durations to balance security and user convenience.
+
+### User management and customization
+
+- Roles and permissions: Detailed control over user roles and permissions to tailor access levels and ensure appropriate access control.
+- User invitations and Onboarding: Smooth onboarding processes with customizable invitation workflows, ensuring a positive initial user experience.
+- Profile management: Intuitive profile management for users to update their information and authentication methods easily.
+
+### Enterprise benefits
+
+- Scalability and flexibility: Solutions that grow with your enterprise, offering flexibility to adapt to evolving needs.
+- Streamlined user management: Centralized user access control reduces administrative effort and complexity.
+- Compliance and security: Meets stringent security standards, protecting sensitive data and ensuring regulatory compliance.
+- Enhanced User experience: Simplified and secure authentication processes improve productivity and user satisfaction.
diff --git a/account/billing.mdx b/account/billing.mdx
new file mode 100644
index 00000000..f2145382
--- /dev/null
+++ b/account/billing.mdx
@@ -0,0 +1,22 @@
+---
+title: "Billing and Payments"
+sidebarTitle: "Billing"
+description: "Manage your billing and payment information, view invoices, and upgrade your subscription plan."
+---
+
+
+## Frequently Asked Questions
+
+### How do I purchase the business or enterprise tier?
+Business or enterprise tier subscriptions can be purchased from billing settings in the Novu dashboard. Click on the avatar icon in the top right corner of the dashboard, then click on **Billing Plans** in the left side menu to access the billing settings. From there, you can select the desired subscription tier and complete the purchase.
+
+
+
+
+
+### How to access past invoices?
+Past invoices can be accessed from the billing settings in the Novu dashboard. Click on the avatar icon in the top right corner of the dashboard, then click on **Billing Plans** on the side menu to access the billing settings. From there, click on the **Manage Subscription** option in your subscribed tier to view past invoices.
+
+
+
+
\ No newline at end of file
diff --git a/account/sso.mdx b/account/sso.mdx
new file mode 100644
index 00000000..66805fa2
--- /dev/null
+++ b/account/sso.mdx
@@ -0,0 +1,19 @@
+---
+title: "Single Sign-On (SSO) for your account"
+sidebarTitle: "SAML SSO"
+description: "Learn how to enable Single Sign-On (SSO) for your account."
+---
+
+
+This feature is only available to Enterprise customers on our Cloud platform. For more information, please visit our [Enterprise Plan](https://novu.co/pricing) page.
+
+
+Novu provides a SAML SSO (Single Sign-On) integration, enabling you to authenticate your organization's users with your existing SAML infrastructure.
+
+Novu is compatible with any SAML 2.0 compliant Identity Provider (IdP), including but not limited to:
+- Okta
+- Microsoft Entra ID (formerly Azure AD)
+- Google Workspace
+- Any other SAML 2.0 compliant IdP
+
+To enable SAML SSO for your account, please reach out to your account manager or contact us at [sales@novu.co](mailto:sales@novu.co).
\ No newline at end of file
diff --git a/additional-resources/data-migrations.mdx b/additional-resources/data-migrations.mdx
new file mode 100644
index 00000000..946685fd
--- /dev/null
+++ b/additional-resources/data-migrations.mdx
@@ -0,0 +1,42 @@
+---
+title: "Data Migrations"
+description: "Learn how to update your database data through migrations."
+icon: "database"
+---
+
+On occasion, Novu may introduce features that require changes to the database schema or data.
+This usually happens when a feature has a hard dependency on some data being available on a database entity.
+You can use migrations to make these changes to your database.
+
+## Running Migrations
+
+To run data migrations, enter the following sequence of commands in your terminal from the [`novuhq/novu`](https://github.com/novuhq/novu) repository root:
+
+```bash
+npm run setup:project
+cd apps/api
+npm run migration -- ./migrations/.ts
+# e.g. npm run migration -- ./migrations/add-user-contact/add-user-phone.ts
+```
+
+Some features may have multiple migrations, in which case you will need to run each migration in the order shown below.
+
+## Migrations History
+
+Below you will find a list of migrations introduced in previous versions of Novu, alongside the migration path to use in the script above.
+
+In some cases, it's necessary for Novu to remodel the data access layer to improve application performance as new data access patterns emerge. It is therefore important that each migration at the specified release tag commit is performed sequentially during version upgrades to ensure the necessary application code is available for the migration to succeed.
+
+| Version | Feature | Migration Path(s) |
+| ------------------------------------------------------------ | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
+| [v2.1](https://github.com/novuhq/novu/releases/tag/v2.1.0) | Preference centralization | `./preference-centralization/preference-centralization-migration.ts` |
+| [v0.23](https://github.com/novuhq/novu/releases/tag/v0.23.0) | API keys encryption | `./encrypt-api-keys/encrypt-api-keys-migration.ts` |
+| [v0.18](https://github.com/novuhq/novu/releases/tag/v0.18.0) | Multi-Provider | `./integration-scheme-update/add-primary-priority-migration.ts` `./integration-scheme-update/add-integration-identifier-migration.ts` |
+| | Integration Store | `./novu-integrations/novu-integrations.migration.ts` |
+| [v0.16](https://github.com/novuhq/novu/releases/tag/v0.16.0) | In-App Integration | `./in-app-integration/in-app-integration.migration.ts` |
+| | Secure Flag Fix | `./secure-to-boolean/secure-to-boolean-migration.ts` |
+| [v0.15](https://github.com/novuhq/novu/releases/tag/v0.15.0) | Database TTL | `./expire-at/expire-at.migration.ts` |
+| [v0.12](https://github.com/novuhq/novu/releases/tag/v0.12.0) | Organization Invite Fix | `./normalize-users-email/normalize-users-email.migration.ts` |
+| [v0.9](https://github.com/novuhq/novu/releases/tag/v0.9.0) | Seen/Read Support | `./seen-read-support/seen-read-support.migration.ts` |
+| [v0.8](https://github.com/novuhq/novu/releases/tag/v0.8.0) | Secure Credentials | `./fcm-credentials/fcm-credentials-migration.ts` `./encrypt-credentials/encrypt-credentials-migration.ts` |
+| [v0.4](https://github.com/novuhq/novu/releases/tag/v0.4.0) | Change Promotion | `./changes-migration.ts` |
diff --git a/additional-resources/glossary.mdx b/additional-resources/glossary.mdx
new file mode 100644
index 00000000..01228d4a
--- /dev/null
+++ b/additional-resources/glossary.mdx
@@ -0,0 +1,150 @@
+---
+title: "Glossary"
+description: "Definitions"
+icon: "book-open"
+---
+
+## Introduction
+
+In this section, you’ll find a list of key terms, their definitions and various concepts associated with Novu. Familiarising yourself with these will help you understand and use Novu better. They will help you navigate our docs more effectively and utilise Novu to its maximum potential.
+
+If you have any questions or need further clarification on any of the terms listed above, please feel free to reach out to our support team or join our community!
+
+## List of key terms and definitions
+
+## Notification
+
+A brief message or alert that informs users about events, updates, or some other information
+
+## Channels
+
+Novu lets you send notifications across different communication mediums, including emails, in-app messages, push notifications, SMS, and chat. Each of these five communication mediums is referred to as a notification ‘channel’.
+
+
+ {" "}
+
+## Providers
+
+Providers are responsible for handling message delivery across various channels. Novu currently supports multiple notification channels, each with its own set of providers.
+
+- Chat: This channel offers these providers:
+ - Discord
+ - MS Teams
+ - Slack
+ - Zulip
+- Email: The Email channel features these providers:
+ - Sendgrid
+ - Amazon SES
+ - Brevo
+ - Resend
+ - SparkPost
+ - Postmark
+ - Mailjet
+ - Mailtrap
+ - Plunk
+ - Braze
+ - Mailersend
+ - Outlook 365 (based on a custom SMTP server)
+ - Mailgun
+ - Mandrill
+ - Netcore
+ - Infobip, and
+ - Custom SMTP
+- SMS: The SMS channel features these providers:
+ - Twilio SMS
+ - SMS77
+ - Africa’s Talking
+ - Infobip
+ - Nexmo
+ - Plivo
+ - Sendchamp
+ - AWS SNS
+ - Telnyx
+ - Termii
+ - Firetext
+ - Gupshup
+ - Clickatell
+ - Azure SMS
+ - BulkSMS
+ - SimpleTexting
+ - MessageBird
+- Push Notification Providers: This channel includes these providers:
+ - Firebase Cloud Messaging (FCM)
+ - Expo Push
+ - Apple Push Notification Service (APNS)
+ - One Signal
+ - Pushpad
+ - Pusher Beams
+ - Push Webhook
+- Inbox: Novu provides you with a set of APIs and components to create rich customized inbox experiences:
+ - React component
+ - Angular component
+ - Vue component
+ - Web component
+ - iFrame embed
+ - Custom styling
+ - Headless Inbox
+
+## Subscribers
+
+Subscribers are entities designated to receive the notifications you send. Each subscriber in Novu is uniquely identified by their `subscriberId`.
+
+## Actor
+
+An `actor` refers to a user or subscriber who initiates actions that trigger events within the system. Each actor is uniquely identified by an "actorId," also known as "subscriberId," which distinguishes them from others.
+
+Actors hold user-related variables, such as subscriber properties and data payload, containing information like names, emails, and custom data.
+
+Additionally, actors can enhance notifications by allowing their avatars to be displayed, improving the context and identification of notifications.
+
+Including actors in event data enables precise tracking and personalization of actions and events in the Novu platform.
+
+## Workflow
+
+Workflow templates define the flow of messages sent to subscribers.
+
+## Topics
+
+Topics facilitate bulk notifications to multiple subscribers simultaneously, streamlining communication.
+
+## Digest Engine
+
+The digest engine aggregates multiple trigger events into a single message, ensuring efficient communication.
+
+## Delay Actions
+
+Delay actions introduce time intervals between workflow steps, optimizing message delivery timing.
+
+## Step Filter
+
+Step filters customize workflow by specifying notification criteria, enhancing communication efficiency.
+
+## Organizations
+
+Organizations allow separation of notifications across multiple products, managed through the Novu web dashboard.
+
+
+ {" "}
+
+
+## Environments
+
+Novu runs all your requests in the context of an environment. By default, Novu creates two environments when your account was created, `development` and `production`. 1. `Development environment`: The development environment is used for testing purposes and validating notification changes prior to committing them to the production environment. 2. `Production environment`: It will be your live/production environment, you cannot make changes to this environment directly. You will first have to make the changes in the `development` environment and then promote it to `production`. This is a read-only environment.
+
+## Data associated with an environment
+
+ - Subscribers (can’t be promoted to production)
+ - Workflows (can be promoted to production)
+ - Messages
+ - Execution logs
+ - Connected integrations (can’t be promoted to production)
+ - Notification feeds (can be promoted to production)
+ - Brand-related assets and settings
+
+## Team members
+
+Members of a team have access to the Novu web dashboard. This allows you to have individuals work on and manage templates and notifications.
+
+## Layouts
+
+Layouts are HTML designs or structures to wrap the content of email notifications. Layouts can be manipulated and assigned to new or existing workflows within the Novu platform, allowing users to create, manage, and assign these layouts to workflows, so they can be reused to structure the appearance of notifications sent through the platform.
diff --git a/additional-resources/idempotency.mdx b/additional-resources/idempotency.mdx
new file mode 100644
index 00000000..5177b007
--- /dev/null
+++ b/additional-resources/idempotency.mdx
@@ -0,0 +1,115 @@
+---
+title: "Idempotent Requests"
+description: "Retry API requests fearlessly, ensuring the operation performs just once!"
+icon: "repeat-1"
+---
+
+Our platform has a seamless integration of optional [idempotent](https://en.wikipedia.org/wiki/Idempotence) requests for `POST` and `PATCH` operations.
+This integration closely adheres to the [Idempotency-key ietf draft](https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/).
+These idempotent operations deliver the same result regardless of the number of requests made, using an idempotent key.
+
+Leveraging this feature guarantees the safety and reliability of your requests, especially in cases where network or communication issues might inject uncertainty into request outcomes.
+Idempotent requests are critical for workflows that require the insurance of only one successful delivery request.
+
+
+ This feature was introduced in version 0.22.0 and is fully operable from
+ 0.24.0 and beyond. Currently, the Idempotency headers are not enabled on the
+ cloud platform but are available for self-hosting.
+
+
+
+ Please be aware that idempotency might not be supported in all community SDKs.
+
+
+## How to Perform an Idempotent Request with SDKs
+
+Supporting SDKS will implement Idempotency for you as long as you have retries turned on.
+Not only is this seamless but it also ensures that no event request is dropped provided that your system stays up.
+
+Idempotency is only implemented for /events/trigger endpoint.
+
+### Idempotent request with api
+
+To make an idempotent request, simply include the `Idempotency-Key: ` in your request header.
+This key should be a unique client-generated value, boasting enough entropy to prevent collisions;
+we recommend a collision-resistant Unique Identifier, such as UUIDv7, CUID, or ULID, as your idempotency keys.
+
+## Understanding Response Replay
+
+Our API stores the status code and response body from your initial idempotent request.
+This way if an identical idempotency key appears in a later request,
+the API will replay the saved response from the original request - even if it involves client or server error responses.
+
+In the event of a response replay, the response will carry an `Idempotency-Replay: true` header.
+
+## Idempotent Request Responces
+
+Here are some unique pieces constraints you should be aware of especially if you are not using the sdk:
+
+1. A `` exceeding 255 characters will trigger a `400 Bad Request` error.
+2. An inconsistent request body compared to the initial request will return a `422 Unprocessable Entity` error.
+3. A ongoing processing of the initial request that hasn't yet responded will result in a `409 Conflict` error. This will include a `Retry-After` header.
+
+
+ Results will not saved if an API request is invalid.
+
+
+## Expiring Keys
+
+All idempotency keys are ensured to be automatically removed once they are 24 hours and 1 minute old.
+After this period, the reused key will be treated as a new request and response.
+
+## Practical Examples
+
+### Example 1 - No idempotency
+
+
+
+ ```javascript
+ import { Novu } from '@novu/node';
+
+ const novu = new Novu("");
+
+ await novu.trigger("", {
+ to: {
+ subscriberId: "",
+ },
+ payload: {
+ name: "Hello World",
+ },
+ actor: "actorId",
+ tenant: "tenantIdentifier",
+ });
+ ```
+
+
+
+
+### Example 2 - Idempotency Active
+
+
+
+ ```javascript
+ import { Novu } from '@novu/node';
+
+ const novu = new Novu("", {
+ retryConfig: {
+ retryMax: 5
+ }
+ });
+
+ // This request is now idempotent
+ await novu.trigger("", {
+ to: {
+ subscriberId: "",
+ },
+ payload: {
+ name: "Hello World",
+ },
+ actor: "actorId",
+ tenant: "tenantIdentifier",
+ });
+ ```
+
+
+
diff --git a/additional-resources/posts-videos-and-articles.mdx b/additional-resources/posts-videos-and-articles.mdx
new file mode 100644
index 00000000..cb7295fd
--- /dev/null
+++ b/additional-resources/posts-videos-and-articles.mdx
@@ -0,0 +1,26 @@
+---
+title: "Blog Posts, Articles & Videos"
+description: "Blog posts, articles, and videos"
+---
+
+## Blog Posts / Articles
+
+ - [🪄✨Building a blog with a liking feature using React, Hanko and Novu 🔥](https://dev.to/novu/building-a-blog-with-a-liking-feature-using-react-hanko-and-novu-1m81)
+ - [How to add In-App notifications to any web app!](https://dev.to/novu/how-to-add-in-app-notifications-to-any-web-app-1b4n)
+ - [How To Add In-App Notifications To Your Angular App](https://dev.to/novu/how-to-add-in-app-notifications-to-your-angular-app-2dp3)
+ - [Creating A Hot New Food Delivery App with Novu](https://dev.to/novu/creating-a-hot-new-food-delivery-app-with-novu-2e75)
+
+## Videos
+
+ - [How to add In-App Notifications to your Web app with Novu](https://www.youtube.com/watch?v=KD8zoUb132k)
+ - [Subscriber (User) Management & Bulk Import Guide](https://www.youtube.com/watch?v=m5nhYEuz86I&t=92s)
+ - [Workflow Optimization with Delay Actions](https://www.youtube.com/watch?v=kCMB-WdbzJo)
+
+## External Resources
+
+ - [Simplify configuration management with Novu and Configu](https://configu.com/blog/video-first-steps-with-configu/)
+ - [Vercel and Novu integration](https://vercel.com/integrations/novu)
+ - [Novu is building open-source notification infrastructure for developers](https://venturebeat.com/business/novu-is-building-open-source-notification-infrastructure-for-developers/)
+ - [Novu - An open-source notification infrastructure for developers](https://reactjsexample.com/-f0-9f-9a-80-open-source-notification-infrastructure-for-products/)
+ - [Novu Tackles Notification Infrastructure Management](https://thenewstack.io/novu-tackles-notification-infrastructure-management/)
+ - [Novu: Omni-channel notification infrastructure for developers](https://www.decibel.vc/articles/novu-omni-channel-notification-infrastructure-for-developers)
diff --git a/additional-resources/security.mdx b/additional-resources/security.mdx
new file mode 100644
index 00000000..1f6d3410
--- /dev/null
+++ b/additional-resources/security.mdx
@@ -0,0 +1,49 @@
+---
+title: "Security and Compliance"
+description: "Common questions about security, compliance, privacy policy, and terms and conditions"
+icon: "shield"
+---
+
+### What should I do if I have regulatory or security issues with PII?
+
+We regularly work with big companies and are happy to help and support you with guidance, and various compliances including reports to ease your security and legal team. If you have concerns about PII, you can use our OS version, Novu Hybrid-Cloud enterprise plan, or reach out to us at sales@novu.co, support@novu.co, or Discord.
+
+### I need my data to reside in the EU, can it be done?
+
+Yes, as part of our GDPR compliance we have our cloud version available on both EU (Frankfurt), as well as US (Virginia).
+
+### Are you GDPR compliant?
+
+Yes, you can see the complete compliance report on our [security page](https://trust.novu.co/). Novu also decided to take the extra step and provide separate data residency in both the EU and the US.
+
+### I created my account in the EU/US, can I switch to the other one?
+
+Not exactly, to keep data residency intact we cannot simply copy or move data between data warehouses across US and EU. However if you have the need please contact us at sales@novu.co
+
+### Are you SOC 2 compliant?
+
+Yes, Novu Cloud is SOC 2 Type II compliant, we have made sure to do penetration tests, security training, evidence collection, and SDL. You can see live control updated on our [security page](https://trust.novu.co/), and ask for our security report as well at sales@novu.co.
+
+### Are you ISO 27001 compliant?
+
+Yes, Novu Cloud is ISO27001 compliant, we have made sure to go through both Stage 1 and Stage 2 audits, and fully define ISMS requirements. From entirely creating our organization processes, defining organization risk assessment policies, and building organization Incident Response & Disaster Recovery plans.
+
+### Where is my data stored?
+
+Based on the selected solution there are a couple of options. On the OS option based on where you choose to store it :) As to the Novu Cloud solution, you can choose the EU (Frankfurt) or the US (Virginia). In case you are working on the Novu Hybrid-Cloud solution we will help you deploy your data inside your select network.
+
+### For how long user data is stored?
+
+By default, data is stored using the following TTL values
+
+- Notifications (for 1 month)
+- Jobs (for 1 month)
+- Message (for in-app messages - 12 months, for all other messages - 1 month)
+- Execution details (for 1 month)
+- Subscribers, workflows, feeds, layouts (not deleted automatically, can be deleted by the user at any time)
+
+If you want to delete any specific data or information, reach out to us at support@novu.co
+
+### How to report any vulnerability or security issue?
+
+We are equally committed to our users and their data’s security. We highly appreciate it if someone shares security vulnerabilities with us. Feel free to use the [github issue](https://github.com/novuhq/novu/security/advisories/new) or email us at security@novu.co.
diff --git a/api-reference/authentication.mdx b/api-reference/authentication.mdx
deleted file mode 100644
index 4578f69b..00000000
--- a/api-reference/authentication.mdx
+++ /dev/null
@@ -1,22 +0,0 @@
----
-title: "Authentication"
-description: "Example overview page before API endpoints"
----
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas et eros iaculis tortor dapibus cursus. Curabitur quis sapien nec tortor dictum gravida.
-
-```bash
-'Authorization': 'Token '
-```
-
-## API Tokens
-
-Nullam convallis mauris at nunc consectetur, ac imperdiet leo rutrum. Maecenas cursus purus a pellentesque blandit. Pellentesque vitae lacinia libero, non mollis metus.
-
-Nam id ullamcorper urna, at rutrum enim. [Maecenas vulputate](/introduction) vehicula libero, vitae sodales augue pretium nec. Quisque a magna tempor, semper risus vel, fermentum nunc. Pellentesque fermentum interdum ex, eu convallis massa blandit sed. Aliquam bibendum ipsum vel laoreet auctor.
-
-### Permissions
-
-Etiam lobortis ut odio ut fermentum. Nunc odio velit, sollicitudin at consectetur id, tristique eget turpis. Aliquam at risus vitae dolor sodales venenatis. In hac habitasse platea dictumst.
-
-Aenean consequat diam eget mollis fermentum. [Quisque eu malesuada](/introduction) felis, non dignissim libero.
diff --git a/api-reference/endpoint/create.mdx b/api-reference/endpoint/create.mdx
deleted file mode 100644
index 7e7f86b9..00000000
--- a/api-reference/endpoint/create.mdx
+++ /dev/null
@@ -1,84 +0,0 @@
----
-title: "Create User"
-api: "POST https://api.mintlify.com/api/user"
-description: "This endpoint creates a new user"
----
-
-### Body
-
-
- This is the current user group token you have for the user group that you want
- to rotate.
-
-
-### Response
-
-
- Indicates whether the call was successful. 1 if successful, 0 if not.
-
-
-
-
-The contents of the user group
-
-
-
-
- This is the internal ID for this user group. You don't need to record this
- information, since you will not need to use it.
-
-
-
- This is the user group token (userGroupToken or USER_GROUP_TOKEN) that will be
- used to identify which user group is viewing the dashboard. You should save
- this on your end to use when rendering an embedded dashboard.
-
-
-
- This is the name of the user group provided in the request body.
-
-
-
- This is the user_group_id provided in the request body.
-
-
-
- This is the environment tag of the user group. Possible values are 'Customer'
- and 'Testing'. User group id's must be unique to each environment, so you can
- not create multiple user groups with with same id. If you have a production
- customer and a test user group with the same id, you will be required to label
- one as 'Customer' and another as 'Testing'
-
-
-
-
-
-
-
-
-```bash Example Request
-curl --location --request POST 'https://api.mintlify.com/api/user' \
---header 'Content-Type: application/json' \
---header 'Authorization: Token ' \
---data-raw '{
- "current_token": ""
-}'
-```
-
-
-
-
-
-```json Response
-{
- "success": 1,
- "user_group": {
- "team_id": 3,
- "token": "",
- "name": "Example 1",
- "provided_id": "example_1"
- }
-}
-```
-
-
diff --git a/api-reference/endpoint/delete.mdx b/api-reference/endpoint/delete.mdx
deleted file mode 100644
index cb0eb8bb..00000000
--- a/api-reference/endpoint/delete.mdx
+++ /dev/null
@@ -1,47 +0,0 @@
----
-title: "Delete User"
-api: "DELETE https://api.mintlify.com/api/user"
-description: "This endpoint deletes an existing user."
----
-
-### Body
-
-
- The data source ID provided in the data tab may be used to identify the data
- source for the user group
-
-
-
- This is the current user group token you have for the user group you want to
- delete
-
-
-### Response
-
-
- Indicates whether the call was successful. 1 if successful, 0 if not.
-
-
-
-
-```bash Example Request
-curl --location --request DELETE 'https://api.mintlify.com/api/user' \
---header 'Content-Type: application/json' \
---header 'Authorization: Token ' \
---data-raw '{
- "user_group_id": "example_1"
- "current_token": "abcdef"
-}'
-```
-
-
-
-
-
-```json Response
-{
- "success": 1
-}
-```
-
-
diff --git a/api-reference/endpoint/get.mdx b/api-reference/endpoint/get.mdx
deleted file mode 100644
index ce95f65b..00000000
--- a/api-reference/endpoint/get.mdx
+++ /dev/null
@@ -1,101 +0,0 @@
----
-title: "Get User"
-api: "GET https://api.mintlify.com/api/user"
-description: "This endpoint gets or creates a new user."
----
-
-### Body
-
-
- This is the name of the user group.
-
-
-
- This is the ID you use to identify this user group in your database.
-
-
-
- This is a JSON mapping of schema id to either the data source that this user group should be
- associated with or id of the datasource you provided when creating it.
-
-
-
- This is a JSON object for properties assigned to this user group. These will be accessible through
- variables in the dashboards and SQL editor
-
-
-### Response
-
-
- Indicates whether the call was successful. 1 if successful, 0 if not.
-
-
-
- Indicates whether a new user group was created.
-
-
-
-
-The contents of the user group
-
-
-
-
- This is the internal ID for this user group. You don't need to record this information, since
- you will not need to use it.
-
-
-
- This is the user group token (userGroupToken or USER_GROUP_TOKEN) that will be used to identify
- which user group is viewing the dashboard. You should save this on your end to use when rendering
- an embedded dashboard.
-
-
-
- This is the name of the user group provided in the request body.
-
-
-
- This is the user_group_id provided in the request body.
-
-
-
- This is the properties object if it was provided in the request body
-
-
-
-
-
-
-
-
-```bash Example Request
-curl --location --request GET 'https://api.mintlify.com/api/user' \
---header 'Content-Type: application/json' \
---header 'Authorization: Token ' \
---data-raw '{
- "user_group_id": "example_1",
- "name": "Example 1",
- "mapping": {"40": "213", "134": "386"},
- "properties": {"filterValue": "value"}
-}'
-```
-
-
-
-
-
-```json Response
-{
- "success": 1,
- "new_user_group": true,
- "user_group": {
- "team_id": 3,
- "token": "",
- "name": "Example 1",
- "provided_id": "example_1"
- }
-}
-```
-
-
diff --git a/api-reference/endpoint/update.mdx b/api-reference/endpoint/update.mdx
deleted file mode 100644
index 43049876..00000000
--- a/api-reference/endpoint/update.mdx
+++ /dev/null
@@ -1,101 +0,0 @@
----
-title: "Update User"
-api: "PUT https://api.mintlify.com/api/user"
-description: "This endpoint updates an existing user."
----
-
-### Body
-
-
- This is the name of the user group.
-
-
-
- This is the ID you use to identify this user group in your database.
-
-
-
- This is a JSON mapping of schema id to either the data source that this user
- group should be associated with or id of the datasource you provided when
- creating it.
-
-
-
- This is a JSON object for properties assigned to this user group. These will
- be accessible through variables in the dashboards and SQL editor
-
-
-### Response
-
-
- Indicates whether the call was successful. 1 if successful, 0 if not.
-
-
-
-
-The contents of the user group
-
-
-
-
- Indicates whether a new user group was created.
-
-
-
- This is the user group token (userGroupToken or USER_GROUP_TOKEN) that will be
- used to identify which user group is viewing the dashboard. You should save
- this on your end to use when rendering an embedded dashboard.
-
-
-
- This is the name of the user group provided in the request body.
-
-
-
- This is the user_group_id provided in the request body.
-
-
-
- This is the properties object if it was provided in the request body
-
-
-
- This is the environment tag of the user group. Possible values are 'Customer'
- and 'Testing'
-
-
-
-
-
-
-
-
-```bash Example Request
-curl --location --request PUT 'https://api.mintlify.com/api/user' \
---header 'Content-Type: application/json' \
---header 'Authorization: Token ' \
---data-raw '{
- "user_group_id": "example_1",
- "name": "Example 1",
- "mapping": {"40": "213", "134": "386"},
- "properties": {"filterValue": "value"}
-}'
-```
-
-
-
-
-
-```json Response
-{
- "success": 1,
- "user_group": {
- "team_id": 113,
- "token": "",
- "name": "ok",
- "provided_id": "6"
- }
-}
-```
-
-
diff --git a/api-reference/overview.mdx b/api-reference/overview.mdx
new file mode 100644
index 00000000..9a0f3e4e
--- /dev/null
+++ b/api-reference/overview.mdx
@@ -0,0 +1,47 @@
+---
+title: "API Overview"
+description: "In this page you can learn about how to work with Novu's API"
+---
+
+
+ It's important to note that our API and backend SDK are intended for use
+ exclusively in server-side applications. **Attempting to use them in a
+ client-side application will result in Cross-Origin Resource Sharing (CORS)
+ errors.** This restriction ensures the security and integrity of our services.
+
+
+## Authentication
+
+Authentication for the Novu API involves the use of an API Key, which is a secure credential that is tied to your Novu account. This key should be included in the header of the request in the Authorization field as a string prefixed with 'ApiKey '.
+
+```curl
+--header 'Authorization: ApiKey '
+```
+
+For example, when using Novu in a Node.js application, the Novu package should be imported and initialized with the API key, as shown in this snippet:
+
+```javascript
+import { Novu } from "@novu/node";
+const novu = new Novu("");
+```
+
+Replace `` with your actual API Key, available in the API Key section of the Novu Dashboard.
+
+
+ It is advised not to hardcode your credentials in a file in production
+ environments. Use environment variables instead.
+
+
+## API Endpoints
+
+Novu provides a multitude of API endpoints that enable a variety of functionalities.
+the base URL for the Novu API is `https://api.novu.co/v1`.
+
+
+We offer two API options: the US API and the EU API. By default, our API documentation refers to the US API, which can be accessed at: https://api.novu.co/v1.
+
+If you require the EU version, you can access it here: https://eu.api.novu.co/v1.
+
+
+
+For instance, to get tenant information, the endpoint to use would include the tenant's identifier and look like this `https://api.novu.co/v1/tenants/{identifier}`.
diff --git a/api-reference/rate-limiting.mdx b/api-reference/rate-limiting.mdx
new file mode 100644
index 00000000..7dd8b2e7
--- /dev/null
+++ b/api-reference/rate-limiting.mdx
@@ -0,0 +1,40 @@
+---
+title: "API Rate Limiting"
+description: "In this page you can learn about how rate limiting works with Novu's API"
+---
+
+This feature is available from v0.22.0.
+
+Rate limiting is an essential functionality for establishing a robust and resilient system. It safeguards system resources from being misused by malicious actors or being monopolized by one client.
+
+A variable-cost token bucket rate limited algorithm has been added to provide the capability for different API controllers and methods to have a varying cost. It also lays a foundation for dynamic costing of resource consumption.
+
+## Limits
+
+The following limits apply to each category of the Novu system. Each category has an independent bucket of request tokens to consume from.
+Standard requests cost 1 request token and bulk requests cost 100 request tokens. Both standard and bulk requests consume from the same token pool.
+
+Each category has a different limit of requests per second (RPS), with the endpoints in each category shown below.
+
+| Category | Free | Business | Endpoints |
+| ------------- | ------ | -------- | -------------------------------------------------------------------- |
+| Events | 60 RPS | 600 RPS | Trigger |
+| Configuration | 20 RPS | 200 RPS | Subscribers, Topics, Tenants |
+| Global | 30 RPS | 300 RPS | All other endpoints consume request tokens from this category. |
+
+### HTTP response headers
+
+When integrating with Novu API, it’s important to consider the rate limiting HTTP headers included in the response. These headers help you manage your API usage and avoid hitting rate limits.
+
+```
+RateLimit-Remaining: 219
+RateLimit-Limit: 300
+RateLimit-Reset: 2
+RateLimit-Policy: 300;w=5;burst=330;comment="token bucket";category="trigger";cost="bulk";serviceLevel="free"
+```
+
+- `RateLimit-Remaining` - Indicates the remaining number of request tokens in the current window.
+- `RateLimit-Limit` - Indicates the total number of request tokens available in the current window.
+- `RateLimit-Reset` - Indicates the number of seconds until the current window resets and the request token limit is fully replenished.
+- `RateLimit-Policy` - Defines the details of the applied rate limiting policy.
+- `Retry-After` - Specifies the number of seconds to wait before making another request.
diff --git a/background.png b/background.png
new file mode 100644
index 00000000..8c4e9fc3
Binary files /dev/null and b/background.png differ
diff --git a/community/add-a-new-provider.mdx b/community/add-a-new-provider.mdx
new file mode 100644
index 00000000..fc7c616d
--- /dev/null
+++ b/community/add-a-new-provider.mdx
@@ -0,0 +1,295 @@
+---
+title: "Add a new provider"
+sidebarTitle: "Add a New Provider"
+description: "Steps to add a new provider to Novu"
+---
+
+Interested in expanding Novu's capabilities? By contributing to our growing ecosystem, you can enhance Novu's reach and impact.
+
+## How to add a new provider?
+
+Novu currently supports five channels `in_app`, `push`, `email`, `chat` and `sms`.
+
+For `in_app` we support only our own provider, so new providers cannot be added to this channel. For the other four channels, we support the integration of external providers. This guide will help in adding new providers for any of these 4 channels.
+
+In this guide, we are adding a new provider for the email channel, but all of the mentioned steps are similar for other channels as well.
+
+## Description
+
+Providers allow us to handle message delivery over multiple channels.
+
+We have multiple providers for each channel (SMS, Email, Push and Chat). To get started with adding a new provider let's look at setting up our repository.
+
+## Requirements
+
+- Node.js version v20.8.1
+- MongoDB
+- Redis
+- **(Optional)** pnpm - Needed if you want to install new packages
+- **(Optional)** localstack (required only in S3 related modules)
+
+Need help installing the requirements? [Read more here](/community/run-in-local-machine)
+
+
+ {" "}
+ We have used pnpm package manager in this guide. You can use npm as well.{" "}
+
+
+## Initialization
+
+Fork the novu repository and clone it in your local machine.
+
+```shell
+git clone https://github.com/<'YOUR_GITHUB_USER_NAME'>/novu.git
+```
+
+To set up the repository, run the initial setup command:
+
+```shell
+pnpm run setup:project
+```
+
+At the root of the project build the `node` package to get started.
+
+```shell
+cd packages/node && pnpm run build
+```
+
+## Generate provider
+
+After the project is initialized, a new provider can be generated using the below command.
+
+```shell
+pnpm run generate:provider
+```
+
+ Use the above command at the root of the project.
+
+Choose the provider type.
+
+```shell
+
+? What type
+❯ EMAIL
+ SMS
+ PUSH
+ CHAT
+```
+
+Use `up` and `down` arrow to switch `channel` type and press `enter` to select.
+
+For this example, we will be selecting `EMAIL` as our provider type. The name for our provider will be `example-provider`.
+
+```
+? Write the provider name`kebab-cased` (e.g. proton-mail, outlook365, yahoo-mail): example-provider
+```
+
+Make sure your selected name is not conflicting with our existing provider's name. Boilerplate files for this new `example-provider` is generated in your local machine project.
+
+> In above example, we have given our provider name as example-provider for simplicity. If provider you want to add have name as twilio, don't use twilio-provider as name, instead use twilio only. If one provider supports multiple channels like infobip supports both sms and email channels, use infobip-email or infobip-sms to differentiate these providers.
+
+Once our `example-provider` is generated we will need to begin working from within `packages/providers/src/lib/example-provider` directory. Make sure to write the test for this new provider.
+
+```ts packages/providers/src/lib/example-provider/example-provider.provider.ts
+import {
+ ChannelTypeEnum,
+ ISendMessageSuccessResponse,
+ IEmailOptions,
+ IEmailProvider,
+} from '@novu/stateless';
+
+export class ExampleProviderEmailProvider implements IEmailProvider {
+ id = 'example-provider'
+ channelType = ChannelTypeEnum.EMAIL as ChannelTypeEnum.EMAIL;
+
+ constructor(
+ private config: {
+ apiKey: string;
+ }
+ ) {}
+
+ async sendMessage(options: IEmailOptions): Promise {
+ return {
+ id: 'id_returned_by_provider',
+ date: 'current_time'
+ }
+}
+```
+
+### Template test case for `example-provider`.
+
+```ts packages/providers/src/lib/example-provider/example-provider.provider.sepc.ts
+import { ExampleProviderEmailProvider } from "./example-provider.provider";
+
+test("should trigger example-provider library correctly", async () => {});
+```
+
+
+ {" "}
+ Add the provider's SDK as a dependency in the provider's package.json file. Run
+ `pnpm run setup:project` to build all dependencies again. Use this new sdk method
+ to complete the provider's `sendMessage` function. Check the **[reference links
+ for adding new providers](#reference-for-adding-new-providers)** section for each
+ channel's provider example.{" "}
+
+
+### Add provider logos
+
+In order to present this new provider in the `integration store` we need logos in dark and light mode. Add dark color svg logo in `apps/web/public/static/images/providers/dark/sqaure` directory and light color svg logo in `apps/web/public/static/images/providers/light/sqaure` directory.
+
+Use the provider name as the file name. The sample name for our above-added provider is `example-provider.svg`.
+
+## Add config items to the list
+
+In order to build the UI integration store, we need to provide it list of all provider integrations. This part is made up of two parts:
+
+- Create credentials config
+- Add ProviderId Enum
+- Add provider configuration to the providers list
+
+### 1. Create credentials config
+
+Every provider requires some credentials to create an instance. Novu will add these credentials fields in the integration store provider's form so that users can use their credentials to connect to their preferred provider to use for that channel notification. For example, in the above added `example-provider`, we have only one credential `ApiKey`. We will need to add a config object for `example-provider` with all existing provider's configs like below.
+
+```tsx libs/shared/src/consts/providers/credentials/provider-credentials.ts
+export const exampleProviderConfig: IConfigCredentials[] = [
+ {
+ key: CredentialsKeyEnum.ApiKey,
+ displayName: "API Key",
+ description: "This is API key for example provider",
+ type: "text",
+ required: true,
+ },
+ ...mailConfigBase,
+];
+```
+
+1. Here the `key` is of type `CredentialsKeyEnum`.
+
+> If a new key is added, add this key at these 3 places:-
+>
+> - In `CredentialsKeyEnum` at file `libs/shared/src/consts/providers/provider.enum.ts`
+> - In `CredentialsDto` at file `apps/api/src/app/integrations/dtos/credentials.dto.ts`
+> - In `credentials` field of `integrationSchema` at file `libs/dal/src/repositories/integration/integration.schema.ts`
+
+2. `displayName` is a human-friendly easy to understand name which will be shown in the provider integration form for this credential.
+3. `description` is a field that can be used to share more information about the credential.
+4. `type` here means text field type. this can be a string for text, text for text-area, or a switch for the toggle.
+5. `required` is of boolean type.
+6. `mailConfigBase` is an object having default credentials required by any `email` provider. Make sure not to add duplicate providers which are already there in `mailConfigBase`. In the case of another channel provider, we will use that channel config base in place of `mailConfigBase`.
+
+> A credential can be made secret by adding in ./secure-credentials.ts file.
+
+### 2. Add ProviderId Enum
+
+Add this new provider id in the respective channel provider id enum in file `libs/shared/src/consts/providers/provider.enum.ts`. As our `example-provider` is of email type, add this in `EmailProviderIdEnum` with all existing providers like below
+
+```tsx libs/shared/src/consts/providers/provider.enum.ts
+export enum EmailProviderIdEnum {
+ ExampleProvider = "example-provider",
+}
+```
+
+### 3. Add provider to providers list
+
+Now we need to add the provider data to the list located at `libs/shared/src/consts/providers/channels/email.ts`. Note that the `id` is the provider's name, `displayName` is the provider's name in Pascal's case, `credentials` are the ones we created in the previous step, `logoFileName` should be as it was on the adding logo step (with the format type included).
+
+```tsx libs/shared/src/consts/providers/channels/email.ts
+{
+ id: 'example-provider',
+ displayName: 'Example Provider',
+ channel: ChannelTypeEnum.EMAIL,
+ credentials: exampleProviderConfig,
+ // Use valid documentation link
+ docReference: 'https://docs.example-provider.com/',
+ logoFileName: { light: 'example-provider.svg', dark: 'example-provider.svg' }
+}
+```
+
+## Add provider handler in the API
+
+### 1. Adding the provider dependency in the application-generic service
+
+In the previous step, we created a standalone provider package that will be published to NPM. However currently in our development environment, it is not yet published. In order to use it locally please go to the `package.json` located in `libs/application-generic/package.json` and add it manually to the dependencies list: `"@novu/": "^"`
+
+> Please note that the provider name and version can be found from the provider package.json you created earlier.
+
+After adding the dependency run `pnpm run setup:project` from the root of the mono repo. so, it can create the required symlinks for the newly created package.
+
+### 2. Create a provider handler
+
+In order to map internally the different providers’ credentials, we need to add a provider handler at the respective channel handlers located. For Email, it can be found at `libs/application-generic/src/factories/mail/handlers`. Other channel handlers can also be found here.
+
+Create a new file `example-provider.handler.ts` here with the following code
+
+```tsx libs/application-generic/src/factories/mail/handlers/example-provider.handler.ts
+import { ChannelTypeEnum } from "@novu/shared";
+import { ExampleProviderEmailProvider } from "@novu/example-provider";
+import { BaseHandler } from "./base.handler";
+
+export class ExampleProviderHandler extends BaseHandler {
+ constructor() {
+ super("example-provider", ChannelTypeEnum.EMAIL);
+ }
+
+ buildProvider(credentials, from: string) {
+ const config: { apiKey: string } = { apiKey: credentials.apiKey };
+
+ this.provider = new ExampleProviderEmailProvider(config);
+ }
+}
+```
+
+Add this line given below to export this handler
+
+```tsx libs/application-generic/src/factories/mail/handlers/index.ts
+export * from "./example-provider.handler";
+```
+
+### 3. Add handler to factory
+
+The last step is to initialize the handler in the factory located in `libs/application-generic/src/factories/mail/mail.factory.ts`
+
+```tsx libs/application-generic/src/factories/mail/mail.factory.ts
+import { ExampleProviderHandler } from "./handlers";
+
+export class MailFactory {
+ handlers: IMailHandler[] = [new ExampleProviderHandler()];
+}
+```
+
+### Final Steps
+
+Now, build the project again using this command
+
+```shell
+pnpm run setup:project
+```
+
+Run novu in your local machine. Read [here](/community/run-in-local-machine) to learn on how to run novu on a local machine and test this new provider.
+
+Run the below command in the root of the project to run the providers test
+
+```shell
+pnpm run test:providers
+```
+
+If everything is working fine without any error, commit your local branch changes, push this branch and create a new pull request to our main repo.
+
+Hurray 🎉! You have successfully added a new provider in Novu!
+
+
+ ️ In this guide, we have used only one credential `apiKey` for
+ our `example-provider`. This is for reference purposes only. A provider can
+ have more than one credential as per its `SDK` requirements. At each step, you
+ will need to add all credentials carefully. Check providers referenced below
+ for more information.{" "}
+
+
+### Reference for Adding New Providers
+
+- [SendGrid Email Provider](https://github.com/novuhq/novu/blob/next/providers/sendgrid/src/lib/sendgrid.provider.ts)
+- [Twilio SMS Provider](https://github.com/novuhq/novu/blob/next/providers/twilio/src/lib/twilio.provider.ts)
+- [FCM Push Provider](https://github.com/novuhq/novu/blob/next/providers/fcm/src/lib/fcm.provider.ts)
+- [Slack Chat Provider](https://github.com/novuhq/novu/blob/next/providers/slack/src/lib/slack.provider.ts)
+- [Resend - How to add an Email Provider to Novu](https://dev.to/novu/resend-how-to-add-an-email-api-provider-to-novu-49cd)
diff --git a/community/changelog.mdx b/community/changelog.mdx
new file mode 100644
index 00000000..95d9a7d6
--- /dev/null
+++ b/community/changelog.mdx
@@ -0,0 +1,36 @@
+---
+title: "Changelog"
+description: "See the most recent changes and learn about how to shape Novu's future"
+---
+
+
+ Learn about what's changed, new features, bug fixes, and see Novu's history
+ across versions.
+
+
+Using the changelog, you can:
+
+- Keep an eye on the latest updates,
+- Learn more about the newly added features and improvements, and
+- Stay informed about bug fixes and enhancement
+
+## Getting involved:
+
+Community is at the heart of everything we do at Novu. To get more involved with the community, you can:
+
+- **Fork and Contribute** to our open issues as well as suggest new ideas at our [github repository](https://github.com/novuhq/novu)
+- **Join our community** to ask questions, engage with other users and share ideas. Here's the [joining link.](https://discord.gg/novu?ref=docs-join-our-community)
+- **Participate in our office hours** to learn more and connect with our core team. Join us [here.](https://www.youtube.com/@novuhq/streams)
+
+
+ Remember, we're not building just another product, but a community of
+ passionate developers who shape its evolution. Our changelog isn't just a list
+ of updates but a reflection of our journey together. Your voice matters, and
+ your ideas and feedback are what fuel our progress and shape our future. So,
+ join us in this adventure, and – Let's build something amazing, one feature at
+ a time!
+
diff --git a/community/code-of-conduct.mdx b/community/code-of-conduct.mdx
new file mode 100644
index 00000000..00fcd75f
--- /dev/null
+++ b/community/code-of-conduct.mdx
@@ -0,0 +1,127 @@
+---
+title: "Novu Community Code of conduct"
+sidebarTitle: "Code of Conduct"
+description: "The set of rules and guidelines that govern interaction among community members"
+---
+
+As a community-driven company, Novu is committed to creating an inclusive and welcoming environment for all members, regardless of factors such as age, body size, disability, ethnicity, gender identity, experience level, education, socio-economic status, nationality, personal appearance, race, religion, or sexual orientation.
+
+However, diverse communities may face challenges, such as potential misunderstandings and miscommunications. To ensure respectful interactions, free from behaviour that may create an unsafe environment, we have established this Code of Conduct.
+
+These guidelines do not cover every possible scenario comprehensively but serve as a guiding light towards courteous interactions among community members, aligning with the overarching principle of avoiding unprofessional behaviour. This Code of Conduct applies to all events and participants, aiming to maintain a welcoming and healthy environment for our community.
+
+Traits of a Novu community member include:
+
+### Being considerate and using appropriate channels
+
+Contributions of every kind have far-ranging consequences. Just as your work depends on the work of others, decisions you make surrounding your contributions to the Novu community will affect your fellow community members. Use appropriate channels for what you’re about to say and refrain from tagging a role that sends out a lot of pings. You are strongly encouraged to take those consequences into account while making decisions.
+
+### Adhering to these standards
+
+It's crucial to keep in mind that our community members are from all kinds of backgrounds, so the members are expected to:
+
+- Demonstrate empathy and kindness toward other people
+- Be respectful of differing opinions, viewpoints, and experiences
+- Give and accept constructive feedback gracefully
+- Accept responsibility and apologise to those affected by mistakes, and learn from such experiences
+- Focus on what is best not just for us as individuals, but for the overall community!
+
+### Patience
+
+Our community thrives on the generosity of volunteered time. Questions, contributions, and support requests may embark on a time-travelling journey before finding their destination. Repeated "bumps" or persistent "reminders" don’t display patience and are looked down upon. Lastly, it is a bad practice to ask general questions to a specific person (in direct messages for example). Try to ask in public as much as you can, and patiently wait for the response
+
+### Inclusivity, kindness and respectfulness
+
+Please be courteous and respectful to fellow members. Avoid offensive comments related to age, body size, disability, ethnicity, gender identity, experience level, education, socio-economic status, nationality, personal appearance, race, religion, or sexual orientation.
+
+Strictly prohibited are sexualized imagery, violence, intimidation, stalking, disruptions, sharing personal information without explicit permission, unwanted physical contact, and unwelcome sexual attention.
+
+Use inclusive language respecting our community's diversity.
+
+Avoid assumptions about others' backgrounds. Maintain a positive and professional demeanour, refraining from threatening or inappropriate behaviour.
+
+**We have zero tolerance for discrimination**. Any form of discrimination, including harassment, will lead to immediate consequences, potentially expulsion.
+
+### Inquisitive
+
+**_The only stupid question is the one that does not get asked_**.
+
+We encourage our users to ask early and ask often. Rather than asking whether you can ask a question (the answer is always yes!), instead, simply ask your question. You are encouraged to provide as many specifics as possible.
+
+Code snippets in the form of images are bad practice. Instead, use text formatted as code (using backticks) on Discord or simply send a gist. Refrain from pasting multiple lines of code directly into the chat channels - instead use [gist.github.com](http://gist.github.com/) or another paste site to provide code snippets.
+
+### Helpful
+
+Novu welcomes users of all skill levels. We were all beginners once, and a supportive environment is essential for the community to thrive. While it can be repetitive to answer the same questions, members are expected to be courteous and helpful to everyone.
+
+Avoid sarcastic responses and prioritize useful information. Everyone should read the provided documentation. We're here to answer questions, offer guidance, and suggest workflows, but not to do your job for you.
+
+### Anti-harassment policy
+
+Harassment includes (but is not limited to) all of the following behaviors:
+
+- Offensive comments related to gender (including gender expression and identity), age, sexual orientation, disability, physical appearance, body size, race, and religion.
+- Derogatory terminology including words commonly known to be slurs
+- Posting sexualized images or imagery in public spaces
+- Deliberate intimidation
+- Stalking
+- Posting others’ personal information without explicit permission
+- Sustained disruption of talks or other events
+- Inappropriate physical contact
+- Unwelcome sexual attention
+
+Immediate compliance is expected from participants asked to cease harassing behaviour. Sponsors must adhere to the anti-harassment policy, refraining from using sexualized images or creating a sexualized environment at events. Volunteer organizers, including meetup staff, should also avoid sexualized attire.
+
+Continuing inappropriate behaviour after being asked to stop constitutes harassment, even if not explicitly mentioned in this policy. It is respectful to stop doing something when asked to do so, and all community members are expected to promptly comply with such requests.
+
+### Reporting policy violations
+
+Instances of abusive, harassing, or otherwise unacceptable behaviour may be reported to anyone with administrative power in the community (Admins or Moderators on Discord, members of the ‘DevRel’ role), or to the local organizers of an event or to [support@novu.co](mailto:support@novu.co). Meetup organizers are encouraged to prominently display points of contact for reporting unacceptable behaviour at local events.
+
+If a participant engages in harassing behaviour, the meetup organizers may take any action they deem appropriate. These actions may include but are not limited to warning the offender, expelling the offender from the event, and barring the offender from future community events.
+
+Organizers will be happy to help participants contact security or local law enforcement, provide escorts to an alternate location, or otherwise assist those experiencing harassment to feel safe during the meetup. We value the safety and well-being of our community members and want everyone to feel welcome at our events, both online and offline.
+
+We expect all participants, organizers, speakers, and attendees to follow these policies at all of our event venues and event-related social events.
+
+### Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
+
+1. **Correction**
+
+- `Community Impact`: Use of inappropriate language or other behaviour deemed unprofessional or unwelcome in the community.
+- `Consequence`: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behaviour was inappropriate. A public apology may be requested.
+
+2. **Warning**
+
+- `Community Impact`: A violation through a single incident or series of actions.
+- `Consequence`: A warning with consequences for continued behaviour. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
+
+3. **Temporary Ban**
+
+- `Community Impact`: A serious violation of community standards, including sustained inappropriate behaviour.
+- `Consequence`: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
+
+4. **Permanent Ban**
+
+- `Community Impact`: Demonstrating a pattern of violation of community standards, including sustained inappropriate behaviour, harassment of an individual, or aggression toward or disparagement of classes of individuals.
+- `Consequence`: A permanent ban from any sort of public interaction within the community.
+
+The Novu Community Code of Conduct is licensed under the Creative Commons Attribution-Share Alike 3.0 license.
+
+Our Code of Conduct was adapted from Codes of Conduct of other open-source projects, including:
+
+- **Contributor Covenant**
+- **Elastic**
+- **The Fedora Project**
+- **OpenStack**
+- **Ansible**
+- **Puppet Labs**
+- **Ubuntu**
+
+Community Impact Guidelines were inspired by Mozilla's code of conduct enforcement ladder.
+
+For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq.
+
+Translations are available at https://www.contributor-covenant.org/translations.
diff --git a/community/feature-flags.mdx b/community/feature-flags.mdx
new file mode 100644
index 00000000..08a70acb
--- /dev/null
+++ b/community/feature-flags.mdx
@@ -0,0 +1,29 @@
+---
+title: "Feature Flags"
+description: "Learn how to enable/disable specific features in Novu"
+---
+
+Feature flags allow to turn certain functionality on and off based on configuration. In this way, users can preview beta features in their deployments.
+
+To enable the specific feature, you need to pass an environment variable to all services:
+
+
+ Feature flag environment variable accepts boolean values: `true` or `false`.
+
+
+- `IS_MULTI_PROVIDER_CONFIGURATION_ENABLED` adds ability to connect multiple providers per channel and make them active. It also shows redesigned integrations store page.
+
+
+ {" "}
+
+
+- `IS_MULTI_TENANCY_ENABLED` adds ability to manage tenants from dashboard.
+
+
+ {" "}
+
+
+- `IS_TEMPLATE_STORE_ENABLED` enables template store which contains pre-made workflows for common use cases (like **Password Reset** workflow etc).
+
+ {" "}
+
diff --git a/community/get-involved.mdx b/community/get-involved.mdx
new file mode 100644
index 00000000..d849169b
--- /dev/null
+++ b/community/get-involved.mdx
@@ -0,0 +1,126 @@
+---
+title: 'Get involved with the Novu Community'
+sidebarTitle: 'Get Involved'
+description: 'Your guide for engaging with the Novu Community'
+---
+
+## Our Community
+
+At Novu, we're a vibrant, open-source community driven by passion, collaboration, and a shared love for pushing boundaries. We believe in the power of community-driven development, where every voice matters and every contribution counts. As such, contributions are at the heart of how we evolve and progress forward.
+
+## Join the conversation
+
+- Read our [code of conduct](/community/code-of-conduct)
+- Say “Hi!” in our [Discord Server](https://discord.gg/novu?ref=docs-community-introduction)
+- Explore our [open issues](https://github.com/novuhq/novu/issues)
+- Subscribe to our newsletter [to stay in the know](https://novu.co/novu-community-2-0/?utm_campaign=docs-comm2-get-involved)
+
+## Getting help
+
+Our fellow community members are always ready to help you get past a blocker. However, you can take a few things into consideration to help them help you:
+
+- Share as many details as you can share. It will help us in debugging the issue.
+- If there is any bug, please share steps to reproduce that bug.
+- If there is any issue in running Novu on a local machine, please share system details like operating system, RAM size, npm version and node version.
+- If there is any issue in our backend SDKs, please mention the SDK version and relevant details.
+- If looking for self-hosting support, please share `Novu Version` and remote server details. Our latest version is `0.24.0`. Please be patient with self-hosting help. We are a small team and we will try our best to help you.
+
+
+ Intercome is suitable if you're using Novu cloud or. Kindly use Discord or
+ Github for community self-hosting questions.
+
+
+## Contributing
+
+All community members are of one of the following four types:
+
+1. Open source users
+2. Active member
+3. Power member
+4. Novu Ambassador
+
+### Become an active community member
+
+- Everyone who joins our community becomes an open-source user automatically.
+- You can become an active community member by:
+ - Submitting >2 PRs
+ - Opening >2 issues
+ - Sending >5 messages on Github
+ - Commenting >3 times on Github
+- Active community members are one step closer to becoming Power community members and then an active moderator.
+
+### Become a power community member
+
+- All active community members are eligible to become a power community member and can become so by having at least one of the following:
+ - Submitting 3 PRs
+ - Opening 3 Issues
+ - Sending 10 Discord messages
+ - Commenting >10 times on Github
+- Power community members are just one step away from becoming a Novu ambassador.
+
+### Become a Novu Ambassador
+
+A Novu ambassador is a trusted power member of the community that can do the following:
+
+- Love exploring and sharing their knowledge of any technology with other developers.
+- Often write and speak about Novu.
+- Possess strong Novu expertise and often help and support Novu users within the community.
+- Socially influence developers with some knowledge of Novu.
+- Consistently contribute to Novu OSS product.
+
+We are launching the Novu Ambassador program soon with amazing perks. Stay tuned!
+
+## Activities
+
+Are you passionate about notifications like we are? There are many different—and easy—ways to get involved.
+
+### Help in onboarding and answering questions.
+
+- Welcome new users into the community
+- Answer questions in the [community support channel](https://discord.com/channels/895029566685462578/1019663407915483176).
+- Offer input and opinions about various solutions you have tried.
+
+### Improve our documentation
+
+- Check out our [GitHub repo](https://github.com/novuhq/docs)
+- No documentation is perfect, and neither is ours
+- Help us improve our docs by
+ - Updating outdated examples
+ - Correcting typos and language for clarity
+ - Find and fix broken links, etc.
+
+### Help with the SDKs
+
+- Link to [contributors guide](https://github.com/novuhq/novu/blob/next/CONTRIBUTING.md)
+- We have SDKs in various languages and frameworks, most written and maintained by community members.
+- You can either become a maintainer there or help the existing maintainers by bringing the SDK up to speed with the latest features present in the core product.
+ - We have backend SDKs in the following: [Node.js](https://github.com/novuhq/novu/tree/next/packages/node), [PHP](https://github.com/novuhq/novu-php), [.NET](https://github.com/novuhq/novu-dotnet), [Elixir](https://github.com/novuhq/novu-elixir), [Go](https://github.com/novuhq/go-novu), [Ruby](https://github.com/novuhq/novu-ruby), [Python](https://github.com/novuhq/novu-python) [Laravel](https://github.com/novuhq/novu-laravel), and [Kotlin](https://github.com/novuhq/novu-kotlin)
+ - And Inbox SDKs: [React](https://github.com/novuhq/novu/tree/next/packages/react), [JavaScript](https://github.com/novuhq/novu/tree/next/packages/js)
+
+### Create content
+
+- Content writers program: Apply to become a content writer with us and get paid to write about use-cases highlighting how you solved a problem using Novu
+- Write an article about what you built with Novu and share it with the community. We’ll give you a shout out!
+
+### PRs, issues, and bug reports
+
+- Check out our [contributors guide](https://github.com/novuhq/novu/blob/next/CONTRIBUTING.md)
+- Once you’ve gone through our [development process](/community/run-in-local-machine), you can contribute directly to open issues.
+- Open a new issue if a relevant one isn’t already open.
+- Also, you can create bug reports if you find a bug somewhere.
+- Or a feature request if you find something that should be a feature but isn't.
+
+
+ At Novu, we believe that no contribution is small, and the only wrong question
+ is the one that doesn’t get asked. So feel free to ask any question or raise
+ that Pull Request. You’re always welcome here! 🤗
+
+
+### Participate in office hours
+
+- We have frequent (replace with interval— twice weekly, weekly, etc.) and topic-specific office hours that offer the ability to engage in real-time with Novu staff and other community members
+- In these sessions, offer input, opinions, examples, etc. to contribute!
+
+We’re excited to have you on board and look forward to your valuable contributions!
+
+Together, we’ll shape the future of Novu. 🫂
diff --git a/community/machine-setup.mdx b/community/machine-setup.mdx
new file mode 100644
index 00000000..d7750a81
--- /dev/null
+++ b/community/machine-setup.mdx
@@ -0,0 +1,260 @@
+---
+title: "MacOS/Linux"
+description: "Learn how to configure your local machine to run Novu"
+---
+
+## Terminal upgrade (Optional)
+
+### Install ZSH
+
+ZSH is a shell on steroids, it has multiple plugins and functionality that can save a lot of time.
+
+To install zsh run in your command line:
+
+```shell
+sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
+```
+
+
+ If you previously haven’t installed git on the machine, a prompt to install
+ git will show up. After installation, run the `sh` command from above again.
+
+
+
+ Make sure you have a `~/.zshrc` exists, if not it might be an indicator that
+ the installation failed for some reason.
+
+
+### ZSH auto-complete plugin
+
+Add the autocomplete plugin for autocompleting previously typed commands in the shell. Follow the instructions [here](https://github.com/zsh-users/zsh-autosuggestions/blob/master/INSTALL.md#oh-my-zsh).
+
+### Powerlevel10k (optional)
+
+[Powerlevel10k](https://github.com/romkatv/powerlevel10k) is a theme for Zsh. It emphasizes [speed](https://github.com/romkatv/powerlevel10k#uncompromising-performance), [flexibility](https://github.com/romkatv/powerlevel10k#extremely-customizable), and [out-of-the-box experience](https://github.com/romkatv/powerlevel10k#configuration-wizard).
+
+1. Clone the repository:
+
+ `git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k`
+
+2. Set `ZSH_THEME="powerlevel10k/powerlevel10k"` in `~/.zshrc`.
+3. Restart your terminal and follow the onboarding wizard to configure it to your likings and preferences.
+
+## Command Line Tools, Homebrew & Casks
+
+Every developer utilizes *Command Line Tools*. A simple terminal command will allow you to download these. It will ask you to confirm after you have entered the command.
+
+```shell
+xcode-select --install
+```
+
+### Homebrew & Casks
+
+[Homebrew](https://brew.sh/), it’s the missing package manager for macOS, and quite powerful at that.
+
+Copy and paste the following code into your terminal to install Homebrew.
+
+```shell
+/bin/shell -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
+```
+
+It will ask you to enter 'Return' to confirm the installation.
+
+
+ After installation run, the `echo` and `eval` commands shown on screen to add
+ brew to your shell executables
+
+
+## Setting up git
+
+To set up git and create an access token for GitHub, suggested installing [GitHub Desktop](https://www.notion.so/Dev-Machine-Setup-98d274c80fa249b0b0be75b9a7a72acb?pvs=21) before.
+
+## Helpful settings
+
+Show hidden files in Finder: `cmd + shift + .` or in the terminal:
+
+```shell
+defaults write com.apple.finder AppleShowAllFiles YES
+```
+
+## Development Environment
+
+### Node & NVM
+
+For installing node we suggest installing it using [NVM](https://github.com/nvm-sh/nvm) (Node Version Manager) for easily managing multiple node versions.
+
+1. Install nvm on your machine.
+
+```shell
+curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | shell
+```
+
+
+ Make sure that you have installed zsh previously, so NVM can automatically
+ inject the executable to PATH in the .zshrc config file.
+
+
+2. After installing nvm and checking that it’s in the path by running `nvm --version` in your CLI. You can now install our node version:
+
+
+ After running the install script, if you get `nvm: command not found` or see
+ no feedback from your terminal after you type `command -v nvm`, simply **close
+ your current terminal, open a new terminal**, and try verifying again.
+
+
+3. Optional for M1 processors - Install Rosetta.
+
+Rosetta enables a Mac with Apple silicon to use apps built for a Mac with an Intel processor.
+
+```shell
+softwareupdate --install-rosetta
+```
+
+### PNPM
+
+For package management, we use [PNPM](https://pnpm.io/) instead of npm or yarn to speed up our install times and space efficiency. To install pnpm globally run:
+
+```shell
+npm install -g pnpm@8.9.0
+```
+
+### Docker
+
+Follow the installation guide on [docker website](https://docs.docker.com/desktop/mac/install/) to install docker on your own machine. For ease of use, we suggest installing **Docker Desktop**.
+
+### AWS CLI
+
+Follow the instructions to install the AWS CLI so you can use it to create env later on, based on existing S3 buckets:
+
+[Installing or updating the latest version of the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)
+
+You should get something like this:
+
+
+
+
+
+If needed, see:
+
+[Resolve "Unable to locate credentials" error in Amazon S3](https://aws.amazon.com/premiumsupport/knowledge-center/s3-locate-credentials-error/)
+
+### Run LocalStack
+
+This is needed only when you upload files (for example - an image) in the admin panel from your local environment.
+
+LocalStack emulates cloud service that runs in a single container on your computer. So you can run your AWS applications on your local machine without connecting to the remote cloud provider.
+
+1. Build a container and run it:
+
+```shell
+docker run --rm -it -p 4566:4566 -p 4571:4571 localstack/localstack
+```
+
+2. Create local S3 buckets needed for the app:
+
+ In **local** environment:
+
+ ```shell
+ aws --endpoint-url=http://localhost:4566 s3 mb s3://novu-local
+ ```
+
+ In **test** environment:
+
+ ```shell
+ aws --endpoint-url=http://localhost:4566 s3 mb s3://novu-test
+ ```
+
+3. Update cors:
+
+ ```json
+ aws --endpoint-url=http://localhost:4566 s3api put-bucket-cors --bucket novu-local --cors-configuration file://cors.json
+ ```
+
+ ```json cors.json
+ {
+ "CORSRules": [
+ {
+ "AllowedOrigins": ["*"],
+ "AllowedHeaders": ["*"],
+ "AllowedMethods": ["GET", "PUT", "POST", "DELETE"],
+ "MaxAgeSeconds": 3000,
+ "ExposeHeaders": ["x-amz-server-side-encryption"]
+ }
+ ]
+ }
+ ```
+
+### MongoDB
+
+After installing brew you can easily install the MongoDB community edition.
+
+1. Tap the [MongoDB Homebrew Tap](https://github.com/mongodb/homebrew-brew) to download the official Homebrew formula for MongoDB and the Database Tools, by running the following command in your macOS Terminal:
+
+```shell
+brew tap mongodb/brew
+```
+
+2. To install MongoDB, run the following command in your macOS Terminal application
+
+ 1. You might need to update the Command Line Tools to be compatible with Xcode - see
+
+ [Sign in with your Apple ID](https://developer.apple.com/download/all/?q=Xcode)
+
+```shell
+brew install mongodb-community@5.0
+```
+
+3. Run the service in the background and start automatically after restart.
+
+```shell
+brew services start mongodb/brew/mongodb-community@5.0
+```
+
+### Redis
+
+After installing homebrew run the redis installation
+
+```shell
+brew install redis
+```
+
+After installation run the service to start on system start as well.
+
+```shell
+brew services restart redis
+```
+
+### Local dev domain (Optional)
+
+This will allow you to use [local.novu.co](http://local.novu.co) instead of [localhost](http://localhost) when accessing the service on your machine.
+
+Open the `/etc/hosts` file:
+
+```shell
+sudo nano /etc/hosts
+```
+
+And add the [local.novu.co](http://local.novu.co) domain
+
+```shell
+127.0.0.1 local.novu.co
+```
+
+### Code Editor
+
+Feel free to install your favorite code editor. We supply licenses for [WebStorm](https://www.notion.so/WebStorm-7570ab1037bf494d88d0f9b544ae3b79?pvs=21), or you can [Visual Studio Code](https://code.visualstudio.com/download) if you prefer it.
+
+### Productivity tools
+
+- GitHub Desktop (Optional)
+
+GitHub Desktop simplifies the development workflow and interaction with Git. [Download](https://desktop.github.com/) the client from their website.
+
+- Clean your Dock bar (Optional)
+
+For better productivity and wellness, we suggest removing the not-used apps from LaunchBar. Don’t worry if you will remove most apps from there. You can always use the spotlight to open the apps you need. Clean workspace === Happy life :)
+Right Click on the app icon > Options > Remove from Dock
+
+
+
+
diff --git a/community/monorepo-structure.mdx b/community/monorepo-structure.mdx
new file mode 100644
index 00000000..3e1d4e77
--- /dev/null
+++ b/community/monorepo-structure.mdx
@@ -0,0 +1,132 @@
+---
+title: 'Monorepo Structure'
+description: "Learn the components of Novu's monorepo"
+---
+
+In this guide, we will explore the Novu mono-repo structure and high-level structure of the different libraries and services we use.
+
+## Setting up the monorepo
+
+Novu uses [PNPM](https://pnpm.js.org/) as its package manager, and [NX](https://nx.dev/) as its build CLI tool. PNPM reduces the installation time and generates symlinks for all the internal packages we use.
+
+To initialize the monorepo, run the following command from the root of the project:
+
+```bash
+npm run setup:project
+```
+
+This will:
+
+- run `pnpm install`, which will download all the needed dependencies and create symlinks for packages.
+- copy the `.env.example` file to the `.env` file for the API service.
+- execute the `npm run build` command to build all the dependency trees locally.
+
+For additional information on running Novu locally, visit the [run locally](/community/run-in-local-machine) guide.
+
+## Apps
+
+The `apps` folder contains high-level applications and APIs. The app's outputs usually contain deployable units that a user can interact with either as an API or as a web/cli application.
+
+
+
+ The API package is our main service for handling backend logic. It handles anything from authentication, authorization, workflow management, triggering events, etc... This is where the Novu business logic is handled.
+
+
+ This is the WebSocket NestJs server which connects to the widget and provides real-time updates about new notifications to the widget consumer.
+
+
+ This is the Novu admin panel which is used to visually communicate with the API. You can configure workflows, manage content, enable or disable notifications, visually track the notification activity feed, etc...
+
+ The `WEB` project is a create-react-app built, well, with React. 😄
+
+
+
+ This is the client of our embeddable Inbox widget. It is consumed mainly with the embed script in an Iframe. We can access it on port 4500 to interact with it directly.
+
+
+ This is our service for managing and handling workers.
+
+
+
+## Libs
+
+
+
+ The `DAL` is our Data-Access-Layer. This is our connection to the DB service and wraps MongoDB and mongoose. When another service or API needs to consume the DB, it does not do that directly but uses the DAL as an interface. Importing `mongoose` directly outside the `dal` is not allowed.
+
+
+ This is a utility library that contains testing helpers. The testing helpers can generate test sessions and other functionality for e2e and unit-tests between our services.
+
+
+ The shared library contains reusable code and typescript interfaces between client and server packages. Code in the shared library should not contain any sensitive content because it can be accessed and downloaded by the web or other clients.
+
+
+ This is the connector between our client's web app and the widget project. It’s a small shim script that generates an iframe and attaches it to a client-specified div to host the notification widget.
+
+ If you are familiar with the Google Analytics embedded snippet or intercom-like embeddings, it uses the same mechanics.
+
+
+
+
+## Packages (on npm)
+
+
+
+ A Standalone Node.js wrapper around the Novu API. Exists to provide
+ type-safe and easier access to the different API endpoints Novu exposes
+ (Triggers, subscribers, etc…).
+
+
+ A Nest.js wrapper around the `@novu/node` package was created by the
+ community to easily interact with the core library from a nest project. Also
+ released on NPM as a package.
+
+
+ This is the library that powers the Novu Inbox. It is a React component that
+ can be embedded in any React application.
+
+
+
+## Providers
+
+These are the API wrappers created by the community to wrap communication providers in the following channels:
+
+Novu provides a single API to manage providers across multiple channels with a simple-to-use interface.
+
+### 💌 Email
+
+- [Sendgrid](https://github.com/novuhq/novu/tree/main/providers/sendgrid)
+- [Netcore](https://github.com/novuhq/novu/tree/main/providers/netcore)
+- [Mailgun](https://github.com/novuhq/novu/tree/main/providers/mailgun)
+- [SES](https://github.com/novuhq/novu/tree/main/providers/ses)
+- [Postmark](https://github.com/novuhq/novu/tree/main/providers/postmark)
+- [Custom SMTP](https://github.com/novuhq/novu/tree/main/providers/nodemailer)
+- [Mailjet](https://github.com/novuhq/novu/tree/main/providers/mailjet)
+- [Mandrill](https://github.com/novuhq/novu/tree/main/providers/mandrill)
+- [Brevo](https://github.com/novuhq/novu/tree/main/providers/sendinblue)
+
+### 📞 SMS
+
+- [Twilio](https://github.com/novuhq/novu/tree/main/providers/twilio)
+- [Plivo](https://github.com/novuhq/novu/tree/main/providers/plivo)
+- [SNS](https://github.com/novuhq/novu/tree/main/providers/sns)
+- [Nexmo - Vonage](https://github.com/novuhq/novu/tree/main/providers/nexmo)
+- [Sms77](https://github.com/novuhq/novu/tree/main/providers/sms77)
+- [Telnyx](https://github.com/novuhq/novu/tree/main/providers/telnyx)
+- [Termii](https://github.com/novuhq/novu/tree/main/providers/termii)
+- [Gupshup](https://github.com/novuhq/novu/tree/main/providers/gupshup)
+
+### 📱 Push
+
+- [FCM](https://github.com/novuhq/novu/tree/main/providers/fcm)
+- [Expo](https://github.com/novuhq/novu/tree/main/providers/expo)
+- [SNS](https://github.com/novuhq/novu/tree/main/providers/sns)
+
+### 👇 Chat
+
+- [Slack](https://github.com/novuhq/novu/tree/main/providers/slack)
+- [Discord](https://github.com/novuhq/novu/tree/main/providers/discord)
+
+### 📱 In-App
+
+- [Novu](/integrations/providers/in-app/overview)
diff --git a/community/overview.mdx b/community/overview.mdx
new file mode 100644
index 00000000..3d6e6faa
--- /dev/null
+++ b/community/overview.mdx
@@ -0,0 +1,80 @@
+---
+title: "Community Overview"
+sidebarTitle: 'Community Overview'
+description: "Get started and get involved with the Novu Project"
+---
+
+Welcome to the Novu community! As the leading open-source notification infrastructure, Novu enables developers and product teams to manage notifications across multiple channels seamlessly. Whether you're building in-app notifications, email alerts, or SMS updates, Novu provides the tools to simplify integration and enhance your communication strategy.
+
+Our mission is to create a platform where every developer can easily implement notifications, with a strong, growing community to support you in every step.
+
+## Community details
+
+The Novu community is built on collaboration. Whether you’re contributing code, helping with documentation, or supporting others in Discord, we welcome everyone. We have dedicated teams across various areas:
+
+- **Developers:** Working on the core project and building new features.
+- **Contributors:** Writing documentation, reporting bugs, and suggesting improvements.
+- **Maintainers:** Ensuring the health of the project by reviewing and merging contributions.
+- **Ambassadors**: The most involved contributors and community members nominated by the Community and approved by Novu.
+
+You can find our official community communication channels on Discord. Whether you're a seasoned developer or just getting started, there’s a place for you here.
+
+### Getting involved
+
+There are many ways to get involved:
+
+- Contribute code, raise issues, or request features on [GitHub](https://github.com/novuhq/novu).
+- Join discussions in the community on [Discord](https://discord.gg/novu).
+- Attend Novu events and participate in hackathons to connect with other community members.
+
+[Learn how to get involved with your new favorite open source project](/community/get-involved)
+
+## Novu Cloud, or Novu Project?
+
+The Novu Project is our fully open source, community backed project. It is a complete notifications infrastructure platform that offers all the core components you need to implement notifications.
+
+Novu Cloud is our commercial service offering. It is a superset of the Novu Project that includes additional features and capabilities businesses require to succeed with notifications at scale.
+
+[See the full list of differences between Novu Cloud and Novu.](/community/project-differences)
+
+## Using Novu
+
+There are two ways to get started with Novu.
+
+### 1. Create a free account in our hosted Novu Cloud service
+
+**Who it’s for:** Most businesses, smaller organizations and teams, businesses that do not want to architect and host their own infrastructure, and businesses that do not want to worry about upgrades and system maintenance.
+
+**Who it’s not for:** Organizations that want to self-host their own instance of Novu, or plan on making customizations to their Novu installation.
+
+[Learn more about Novu Cloud’s features, capabilities, and pricing.](https://novu.co/pricing)
+
+### 2. Self-host
+
+Self-hosting Novu is a great way to rapidly test new custom providers, or to test your contributions.
+
+**Use the Community Docker instances**
+
+- **Who it’s for:** Individuals and organizations that want to experiment with more advanced notifications infrastructure architectures, businesses that have a strict requirement to run notifications on premises or inside their own network boundary.
+- **Who it’s not for:** Non-technical users or teams that are not familiar with implementation and management of container infrastructure. Organizations that require rapid scale-up out-of-the-box, and that don’t have the skills and knowledge required to implement and adjust deployments.
+
+[Learn how to deploy Novu with Docker](/community/self-hosting-novu/deploy-with-docker)
+
+**Build it yourself**
+
+- **Who it’s for:** Highly technical users that are deeply familiar with the required infrastructure components and technologies required to run it either locally or in your own cloud. Developers that want to experiment. Builders that are actively contributing to the Novu Project, and are testing new functionality.
+- **Who it’s not for:** Nearly everyone else. ;)
+
+[Learn how to run Novu on your local machine](/community/run-in-local-machine)
+
+## Get help
+
+For assistance with a Paid or Trial Novu Cloud account? Please use the in-app chat widget which is available in the lower right hand corner of your app dashboard. :smile:
+
+Should you encounter an issue with either your free Novu Cloud account, or your Novu Project instance, the community is here to help. Our primary channel for community support is our Discord community.
+
+[Learn how to get help with your Novu Project or Free-tier Novu Cloud account](/community/get-involved#getting-help)
+
+### Events
+
+Staying in tune with our upcoming community events, workshops, and hackathons is easy. Keep an eye out for new announcements in the [announcement](https://discord.com/channels/895029566685462578/1040040454906986578) channel in our [Discord community.](https://discord.gg/novu) That’s the best way to stay updated about new events. You can also subscribe to our [youtube channel](https://www.youtube.com/@novuhq) and follow us on [dev.to](https://dev.to/novu).
diff --git a/community/project-differences.mdx b/community/project-differences.mdx
new file mode 100644
index 00000000..fe13fa6f
--- /dev/null
+++ b/community/project-differences.mdx
@@ -0,0 +1,56 @@
+---
+title: "Novu Project and Novu Cloud"
+sidebarTitle: 'Novu Project and Novu Cloud'
+description: "Understand the difference between Novu Project and Novu Cloud"
+---
+## Better together
+Novu Project is where the community comes together with our development teams to improve notifications for everyone. Novu Cloud is our commercial offering, and where we extend the capabilities of the Novu Project and make them accessible and consumable by all teams and businesses. Most simply, the Novu Project is how you test and experiment with notifications infrastructure, and Novu Cloud is how you press the easy button for all things notifications.
+
+### Key differences
+Our goal is always provide a functional, powerful, and complete notifications infrastructure platform, whether you opt to self host or use our cloud offering. However, certain features we build into Novu Cloud are not available in the Novu Project. When this happens, it's for several reasons:
+- We rely on a paid third-party service provider
+- The feature is complex and requires significant investment (read: money!) time to build and maintain
+- The feature is primarily targeted at the largest and most sophisticated of use cases, and supporting these in self-hosted environments is challenging
+
+### Current matrix
+Below you will find a table that outlines the best available Novu Cloud Tier, compared to the available Novu Project capabilities.
+
+|CAPABILITY|NOVU CLOUD|NOVU PROJECT|
+|--- | --- | --- |
+|Channels supported: Email, In-app, SMS, Chat, Push | ✅ | ✅ |
+|Notification Subscribers | Unlimited | Infratructure dependent |
+|GUI-based workflows | ✅ | ✅ |
+|Code-based Framework workflows | ✅ | ✅ |
+|Subscriber management| Q1 2025 | ❌ |
+|Multi-org/multi-tenancy | Q1 2025 | ❌ |
+|**FRAMEWORK**|
+|Max workflows | Unlimited | Infratructure dependent |
+|Provider integrations | Unlimited | Unlimited |
+|Activity feed retention | Unlimited | Infratructure dependent |
+|Digests | ✅ | Infratructure dependent |
+|Workflow step controls | ✅ | ✅ |
+|Translations| ✅ | ✅ |
+|Block-based email editor | Nov 2024 | Nov 2024 |
+|**INBOX**|
+|Inbox component | ✅ | ✅ |
+|User preferences component | ✅ | ✅ |
+|Bell component | ✅ | ✅ |
+|Notifications component | ✅ | ✅ |
+|Inbox content component | ✅ | ✅ |
+|Remove Novu branding | ✅ | ✅ |
+|**ACCOUNT ADMINISTRATION AND SECURITY**|
+|Max team members | Unlimited | Unlimited |
+|Role-Based Access Control (RBAC)| Q2 2025 | ❌ |
+|Standard SAML authentication (Google, Github) | ✅ | ❌ |
+|Custom SAML SSO, OIDC enterprise providers | ✅ | ❌ |
+|Built-In authentication | ✅ | ✅ |
+|Multi-Factor Authentication (MFA) | ✅ | ❌ |
+|**COMPLIANCE**|
+|GDPR | ✅ | Installation dependent |
+|SOC 2 / ISO 27001 | ✅ | ❌ |
+|HIPAA BAA| ✅ | ❌ |
+|Custom security reviews | ✅ | ❌ |
+|Data Processing Agreements | Custom | ❌ |
+
+### Unmatched flexbility
+One huge advantage of working with Novu? If you opt for a paid Novu Cloud account, you can always fall back to the free tier, or migrate your notifications to a self-hosted Novu Project instance. Say goodbye to notifications risk.
\ No newline at end of file
diff --git a/community/roadmap.mdx b/community/roadmap.mdx
new file mode 100644
index 00000000..a49b89f0
--- /dev/null
+++ b/community/roadmap.mdx
@@ -0,0 +1,25 @@
+---
+title: "Roadmap"
+description: "Learn about our roadmap"
+---
+
+
+ Learn about ongoing developments, upcoming plans, and items in our backlog..
+
+
+Your involvement in shaping the future of our notification infrastructure solution is highly encouraged.
+
+## Get involved
+
+**Submit Ideas:** If you have a new feature idea or an enhancement suggestion, don't hesitate to submit it through our [roadmap page](https://roadmap.novu.co/roadmap). Your creativity fuels our innovation!
+
+**Upvote and Comment:** Review the existing feature requests and upvote the ones that resonate with you. Feel free to provide additional insights or use-cases through comments.
+
+**Contribute:** If you're a developer, you can actively contribute to the development of the features in progress or even the backlog ones. [Join our community of contributors](https://discord.gg/novu?ref=docs-contribute) and help us bring these enhancements to life.
+
+
+ Remember, this roadmap is a living document that evolves based on your input
+ and the direction the community decides to take. Your voice matters, and we're
+ excited to work together in building a robust notification infrastructure
+ solution that meets everyone's needs.
+
diff --git a/community/run-in-local-machine.mdx b/community/run-in-local-machine.mdx
new file mode 100644
index 00000000..d4a98010
--- /dev/null
+++ b/community/run-in-local-machine.mdx
@@ -0,0 +1,304 @@
+---
+title: "Run Novu in local machine"
+description: "Prerequisites and steps to run Novu in local machine. Learn how to set up Novu on your local environment for testing and development."
+---
+
+[](https://gitpod.io/#https://github.com/novuhq/novu)
+
+### Requirements
+
+- Node.js version v20.8.1
+- MongoDB
+- Redis
+- **(Optional)** pnpm - Needed if you want to install new packages
+- **(Optional)** localstack (required only in S3 related modules)
+
+Need help installing the requirements? Read more [here](/community/machine-setup).
+
+
+ We recommend having at least 8GB of RAM to run Novu on a local machine as Novu
+ has multiple services running together with external services like redis,
+ mongodb etc.
+
+
+
+
+### Setup the project
+
+After installing the required services on your machine, you can clone and set up
+your forked version of the project:
+
+1. Clone the repository
+
+
+
+ ```shell git clone https://github.com/novuhq/novu.git ```
+
+
+ ```shell git clone https://github.com/{YOUR_GITHUB_USER_NAME}/novu.git ```
+
+
+
+2. Install all dependencies
+
+```shell
+cd novu && npm run setup:project
+```
+
+3. Run the project
+
+```shell
+npm run start
+```
+
+The `npm run start` will start the Jarvis CLI tool which allows you to run the whole project with ease. If you only want to run parts of the platform, you can use the following run commands from the root project:
+
+- **start:dev** - Synonym to `npm run start`
+- **start:web** - Only starts the web management platform
+- **start:ws** - Only starts the WebSocket service for notification center updates
+- **start:widget** - Starts the widget wrapper project that hosts the notification center inside an iframe
+- **start:api** - Runs the API in watch mode
+- **start:worker** - Runs the worker application in watch mode
+- **start:dal** - Runs the Data Access Layer package in watch mode
+- **start:shared** - Starts the watch mode for the shared client and API library
+- **start:node** - Runs the `@novu/node` package in watch mode
+- **start:notification-center** - Runs and builds the React package for the Novu notification center
+
+
+
+
+
+### Set up your environment variables
+
+If you have used Jarvis CLI tool from the previous step you don't need to setup the env variables as Jarvis will do that on the first run if setup wasn't done before.
+
+The command `npm run setup:project` creates default environment variables that are required to run Novu in a development environment. However, if you want to test certain parts of Novu or run it in production mode, you need to change some of them. These are all the available environment variables:
+
+
+
+ - `NODE_ENV` (default: local)The environment of the app. Possible values are: dev, test, production, ci, local
+ - `S3_LOCAL_STACK`The AWS endpoint for the S3 Bucket required for storing various media
+ - `S3_BUCKET_NAME`The name of the S3 Bucket
+ - `S3_REGION`The AWS region of the S3 Bucket
+ - `PORT`The port on which the API backend should listen on
+ - `FRONT_BASE_URL`The base url on which your frontend is accessible for the user. (e.g. dashboard.novu.co)
+ - `DISABLE_USER_REGISTRATION` (default: false)If users should not be able to create new accounts. Possible values are: true, false
+ - `REDIS_HOST`The domain / IP of your redis instance
+ - `REDIS_PORT`The port of your redis instance
+ - `REDIS_PASSWORD`Optional password of your redis instance
+ - `REDIS_DB_INDEX`The Redis database index
+ - `REDIS_CACHE_SERVICE_HOST`The domain / IP of your redis instance for caching
+ - `REDIS_CACHE_SERVICE_PORT`The port of your redis instance for caching
+ - `REDIS_CACHE_DB_INDEX`The Redis cache database index
+ - `REDIS_CACHE_TTL`The Redis cache ttl
+ - `REDIS_CACHE_PASSWORD`The Redis cache password
+ - `REDIS_CACHE_CONNECTION_TIMEOUT`The Redis cache connection timeout
+ - `REDIS_CACHE_KEEP_ALIVE`The Redis cache TCP keep alive on the socket timeout
+ - `REDIS_CACHE_FAMILY`The Redis cache IP stack version
+ - `REDIS_CACHE_KEY_PREFIX`The Redis cache prefix prepend to all keys
+ - `REDIS_CACHE_SERVICE_TLS`The Redis cache TLS connection support
+ - `IN_MEMORY_CLUSTER_MODE_ENABLED`The flag that enables the cluster mode. It might be Redis or ElastiCache cluster, depending on the env variables set for either service.
+ - `ELASTICACHE_CLUSTER_SERVICE_HOST`ElastiCache cluster host
+ - `ELASTICACHE_CLUSTER_SERVICE_PORT`ElastiCache cluster port
+ - `REDIS_CLUSTER_SERVICE_HOST`Redis cluster host
+ - `REDIS_CLUSTER_SERVICE_PORTS`Redis cluster ports
+ - `REDIS_CLUSTER_DB_INDEX`Redis cluster database index
+ - `REDIS_CLUSTER_TTL`Redis cluster ttl
+ - `REDIS_CLUSTER_PASSWORD`Redis cluster password
+ - `REDIS_CLUSTER_CONNECTION_TIMEOUT`Redis cluster connection timeout
+ - `REDIS_CLUSTER_KEEP_ALIVE`Redis cluster TCP keep alive on the socket timeout
+ - `REDIS_CLUSTER_FAMILY`Redis cluster IP stack version
+ - `REDIS_CLUSTER_KEY_PREFIX`Redis cluster prefix prepend to all keys
+ - `JWT_SECRET`The secret keybase which is used to encrypt / verify the tokens issued for authentication
+ - `SENDGRID_API_KEY`The api key of the Sendgrid account used to send various emails
+ - `MONGO_URL`The URL of your MongoDB instance
+ - `MONGO_MAX_POOL_SIZE`The max pool size of the MongoDB connection
+ - `NOVU_SECRET_KEY`The api key of dashboard.novu.co used to send various emails
+ - `SENTRY_DSN`The DSN of sentry.io used to report errors happening in production
+
+
+{" "}
+
+ - `NODE_ENV` (default: local)The environment of the app. Possible values are:
+ dev, test, production, ci, local - `PORT`The port on which the Worker app
+ should listen on - `STORE_ENCRYPTION_KEY`The encryption key used to
+ encrypt/decrypt provider credentials - `MAX_NOVU_INTEGRATION_MAIL_REQUESTS`The
+ number of free emails that can be sent with the Novu email provider -
+ `NOVU_EMAIL_INTEGRATION_API_KEY`The Novu email provider Sentry API key -
+ `STORAGE_SERVICE`The storage service name: AWS, GCS, or AZURE -
+ `S3_LOCAL_STACK`The LocalStack service URL - `S3_BUCKET_NAME`The name of the
+ S3 Bucket - `S3_REGION`The AWS region of the S3 Bucket - `GCS_BUCKET_NAME`The
+ name of the GCS Bucket - `AZURE_ACCOUNT_NAME`The name of the Azure account -
+ `AZURE_ACCOUNT_KEY`The Azure account key - `AZURE_HOST_NAME`The Azure host
+ name - `AZURE_CONTAINER_NAME`The Azure container name - `AWS_ACCESS_KEY_ID`The
+ AWS access key - `AWS_SECRET_ACCESS_KEY`The AWS secret access key -
+ `REDIS_HOST`The domain / IP of your redis instance - `REDIS_PORT`The port of
+ your redis instance - `REDIS_PASSWORD`Optional password of your redis instance
+ - `REDIS_DB_INDEX`The Redis database index - `REDIS_CACHE_SERVICE_HOST`The
+ domain / IP of your redis instance for caching - `REDIS_CACHE_SERVICE_PORT`The
+ port of your redis instance for caching - `REDIS_DB_INDEX`The Redis cache
+ database index - `REDIS_CACHE_TTL`The Redis cache ttl -
+ `REDIS_CACHE_PASSWORD`The Redis cache password -
+ `REDIS_CACHE_CONNECTION_TIMEOUT`The Redis cache connection timeout -
+ `REDIS_CACHE_KEEP_ALIVE`The Redis cache TCP keep alive on the socket timeout -
+ `REDIS_CACHE_FAMILY`The Redis cache IP stack version -
+ `REDIS_CACHE_KEY_PREFIX`The Redis cache prefix prepend to all keys -
+ `REDIS_CACHE_SERVICE_TLS`The Redis cache TLS connection support -
+ `IN_MEMORY_CLUSTER_MODE_ENABLED`The flag that enables the cluster mode. It
+ might be Redis or ElastiCache cluster, depending on the env variables set for
+ either service. - `ELASTICACHE_CLUSTER_SERVICE_HOST`ElastiCache cluster host -
+ `ELASTICACHE_CLUSTER_SERVICE_PORT`ElastiCache cluster port -
+ `REDIS_CLUSTER_SERVICE_HOST`Redis cluster host -
+ `REDIS_CLUSTER_SERVICE_PORTS`Redis cluster ports -
+ `REDIS_CLUSTER_DB_INDEX`Redis cluster database index -
+ `REDIS_CLUSTER_TTL`Redis cluster ttl - `REDIS_CLUSTER_PASSWORD`Redis cluster
+ password - `REDIS_CLUSTER_CONNECTION_TIMEOUT`Redis cluster connection timeout
+ - `REDIS_CLUSTER_KEEP_ALIVE`Redis cluster TCP keep alive on the socket timeout
+ - `REDIS_CLUSTER_FAMILY`Redis cluster IP stack version -
+ `REDIS_CLUSTER_KEY_PREFIX`Redis cluster prefix prepend to all keys -
+ `MONGO_URL`The URL of your MongoDB instance - `MONGO_MAX_POOL_SIZE`The max
+ pool size of the MongoDB connection - `NEW_RELIC_APP_NAME`The New Relic app
+ name - `NEW_RELIC_LICENSE_KEY`The New Relic license key - `SEGMENT_TOKEN`The
+ Segment Analytics token
+
+
+{" "}
+
+ - `REACT_APP_ENVIRONMENT` The environment of the app. Possible values are:
+ dev, test, production, ci, local - `REACT_APP_API_URL` The base url on which
+ your API backend would be accessible - `REACT_APP_WS_URL` The base url on
+ which your WebSocket service would be accessible -
+ `SKIP_PREFLIGHT_CHECK` (default: true)Solves a problem with React App
+ dependency tree.
+
+ When configuring different than default values for the API and WebSocket
+ URLs, in order for the Web app to apply the changes done to
+ the `./env` file, it is needed to run the script `pnpm envsetup`. This will
+ generate a file called `env-config.js` that will be copied inside of
+ the `public` folder of the application. Its purpose is to inject in
+ the `window._env_`object the chosen environment variables that manage the
+ URLs the Web client will call to access to the API backend and the WebSocket
+ service.
+
+
+
+
+ - `NODE_ENV` (default: local)The environment of the app. Possible values are: dev, test, production, ci, local
+ - `SENTRY_DSN`The DSN of sentry.io used to report errors happening in production
+ - `REDIS_HOST`The domain / IP of your redis instance
+ - `REDIS_PORT`The port of your redis instance
+ - `REDIS_DB_INDEX`The database index of your redis instance
+ - `REDIS_PASSWORD`Optional password of your redis instance
+ - `JWT_SECRET`The secret keybase which is used to encrypt / verify the tokens issued for authentication
+ - `MONGO_URL`The URL of your MongoDB instance
+ - `MONGO_MAX_POOL_SIZE`The max pool size of the MongoDB connection
+ - `PORT`The port on which the WebSocket service should listen on
+
+
+
+### Running tests
+
+After making changes, you can run the tests for the respective package using the appropriate CLI commands:
+
+### API
+
+To run the API tests, run the following command:
+
+```shell
+npm run start:worker:test
+npm run start:e2e:api
+```
+
+The tests create a new instance of Novu and a test db and run the tests against it. The test db is removed after all tests have finished running.
+
+### Web
+
+To run the front end tests for the web project using cypress you need to install localstack. The cypress tests perform E2E tests. To be able to perform E2E tests, you need to run the API service in the appropriate test environment.
+
+Run the services in test env with the following commands:
+
+```shell
+npm run start:web
+npm run start:api:test
+npm run start:worker:test
+npm run start:ws:test
+```
+
+Run the cypress test suite with the following command:
+
+```shell
+cd apps/web && npm run cypress:run
+```
+
+To open the cypress management window to debug tests, run the following commands:
+
+```shell
+cd apps/web && npm run cypress:open
+```
+
+### Different ports used by the services
+
+- **3000** - API
+- **3002** - WebSocket Service
+- **3003** - Webhook Service
+- **3004** - Worker Service
+- **4200** - Web Management UI
+- **4701** - Iframe embed for notification center
+- **4500** - Widget Service
+
+### Testing providers
+
+To run tests against the providers’ folder, you can use the `npm run test:providers` command.
+
+### Local environment setup script (beta)
+
+As an option in our script runner `Jarvis` we have made available an option to run [this script](https://github.com/novuhq/novu/blob/2f2abdcaaad8a7735e0a2d488607c3276c8975fd/scripts/dev-environment-setup.sh) that will automatically try to install all the dependencies needed to be able to run Novu locally, as the previous step of installing the project dependencies through `pnpm install`. When executing it inside `Jarvis`, you will need to have previously installed by yourself `git` and `node`, as we mentioned earlier on this page.
+
+The script can be run on its own without any previous dependency installed, as it is prepared to execute the following tasks:
+
+- Check the running OS in the local machine (currently only MacOSx and [GNU Linux](https://en.wikipedia.org/wiki/GNU/Linux_naming_controversy)supported)
+- Install of OS dependencies (currently only MacOSx supported) -- MacOSx: It will execute the following tasks --- Will try to install or update [XCode](https://developer.apple.com/xcode/) (skippable step; though XCode installs `[git](https://git-scm.com/)` that is a required dependency for later) --- Will install [Rosetta](https://support.apple.com/en-gb/HT211861) for Apple CPUs --- Will set up some opinionated OS settings
+- Will check if `[git](https://git-scm.com/)` is installed and if not will abort the operation
+- Will make [ZSH](https://en.wikipedia.org/wiki/Z_shell) the default shell to be able to execute the next task
+- Will (opinionatedly) install [Oh My Zsh!](https://ohmyz.sh/) (skippable task)
+- Will (opinionatedly) install the [Homebrew](https://brew.sh/) package manager and will set up your local environment to execute it besides adding some casks
+- Will (opinionatedly) install [NVM](https://github.com/nvm-sh/nvm) as a Node.js version manager
+- Will install the required [Node.js](https://nodejs.org/en/) version to be able to [run Novu](https://github.com/novuhq/novu/blob/2f2abdcaaad8a7735e0a2d488607c3276c8975fd/package.json#L180)
+- Will install [PNPM](https://pnpm.io/) as a package manager, required dependency for some of the tasks inside Novu's scripts
+- Will install [Docker](https://www.docker.com/) as containerized application development tool
+- Will install required databases [MongoDB](https://www.mongodb.com/) (Community version) and [Redis](https://redis.io/) through Homebrew
+- Will install the [AWS CLI](https://aws.amazon.com/cli/) tool (not required to run Novu; it is a core maintainer used tool)
+- Will create a local development domain `local.novu.co` in your local machine
+- Will clone the Novu repository in your local machine (skippable step) to a selected folder `$HOME/Dev`
+
+
+ This script has only been thoroughly tested in MacOSx. Little testing has been
+ run in GNU Linux.
+
+
+
+ This script is not bullet-proof and some of the tasks have intertwined
+ dependencies with each other. We have tried to make it as idempotent as
+ possible but some loose knots will probably show because of conflicts between
+ versions of the different dependencies. Please report to us any problem found
+ and we will try to fix or assist though we do not have the resources to make
+ it idempotent in every potential system and potential combinations
+
diff --git a/community/self-hosting-novu/deploy-with-docker.mdx b/community/self-hosting-novu/deploy-with-docker.mdx
new file mode 100644
index 00000000..f2495063
--- /dev/null
+++ b/community/self-hosting-novu/deploy-with-docker.mdx
@@ -0,0 +1,259 @@
+---
+title: "Deploy with Docker"
+description: "Learn how to deploy Novu with Docker"
+icon: "docker"
+---
+
+Docker compose is the easiest way to get started with self-hosted Novu.
+
+## Before you begin
+
+You need the following installed in your system:
+
+- [Docker](https://docs.docker.com/engine/install/) and [docker-compose](https://docs.docker.com/compose/install/)
+- [Git](https://git-scm.com/downloads)
+
+
+
+
+ In above loom video `local/deployment` directory is used. Now it has been
+ moved to `docker/community`. Follow below mentioned steps.
+
+
+## Quick Start
+
+### Get the code
+
+Clone the Novu repo and enter the docker directory:
+
+```bash
+# Get the code
+git clone --depth 1 https://github.com/novuhq/novu
+
+# Go to the docker community folder
+cd novu/docker/community
+
+# Copy the example env file
+cp .env.example .env
+```
+
+### Configure Environment
+
+#### Local Development
+For local development, you can use the default configuration. Start Novu with:
+
+```bash
+docker-compose -f docker-compose.yml up
+```
+
+Now visit [http://localhost:4200](http://localhost:4200/) to start using Novu.
+
+#### VPS Deployment
+When deploying to a VPS, update your `.env` file with your server's information:
+
+```bash
+# Replace with your VPS IP address
+HOST_NAME=http://
+```
+
+Start Novu on your VPS:
+
+```bash
+docker-compose -f docker-compose.yml up
+```
+
+Access your dashboard at [http://vps-ip-address:4200](http://vps-ip-address:4200/).
+
+## Securing your setup
+
+While we provide example secrets for getting started, you should NEVER deploy your Novu setup using the defaults provided.
+
+Update the `.env` file with your own secrets.
+
+### Required Variables:
+
+- `JWT_SECRET`: Used by the API to generate JWT keys.
+- `STORE_ENCRYPTION_KEY`: Used to encrypt/decrypt the provider credentials. It must be 32 characters long.
+- `HOST_NAME`: Host name of your installation:
+ - For local development: `http://localhost`
+ - For VPS deployment: Your server's IP address (e.g., `http://`) or domain name
+- `REDIS_CACHE_SERVICE_HOST` and `REDIS_HOST` can have same value for small deployments. For larger deployments, it is recommended to use separate Redis instances for caching and queue management.
+
+## Configuration
+
+To keep the setup simple, we made some choices that may not be optimal for production:
+
+- the database is in the same machine as the servers
+- the storage uses localstack instead of S3
+
+We strongly recommend that you decouple your database before deploying.
+
+### Setting Up Novu Studio and Bridge
+
+#### Setting Up Novu Studio
+Novu Studio is a development environment that allows you to test and manage your notifications. The setup varies based on your deployment:
+
+##### For Local Development
+```bash
+npx novu@latest dev
+```
+
+##### For VPS Deployment
+```bash
+# Start Novu Studio with your VPS dashboard URL and bridge application URL
+npx novu@latest dev -d http://:4200 -o http://:4000
+```
+
+#### Setting Up the Novu Bridge
+The Novu Bridge is used to connect your application with Novu. Here's how to set it up:
+
+```bash
+# Initialize the bridge
+npx novu@latest init \
+ --secret-key= \
+ --api-url=http://:3000
+
+# Start the bridge
+npm run dev
+```
+
+### Synchronizing Workflows
+
+For VPS deployment:
+```bash
+npx novu@latest sync \
+ --bridge-url http://:3000/api/novu \
+ --api-url http://:3000 \
+ --secret-key
+```
+
+### VPS Security Considerations
+
+When deploying to a VPS, consider these additional security measures:
+
+1. Use a firewall to restrict access to only necessary ports
+2. Set up SSL/TLS certificates for HTTPS access
+3. Regularly update your Docker images and host system
+4. Use strong, unique secrets in your `.env` file
+5. Consider using a reverse proxy like Nginx for additional security layers
+
+### Triggering events with custom installation
+
+When self-hosting Novu, in order to trigger an event you must first create a new `Novu` object and configure it with the proper `backendUrl`.
+
+```tsx
+import { Novu } from "@novu/node";
+
+const config = {
+ backendUrl: "",
+};
+
+const novu = new Novu("", config);
+
+await novu.trigger("", {
+ to: {
+ subscriberId: "",
+ },
+ payload: {},
+});
+```
+
+### Pointing IFrame embed to custom installation
+
+When using the IFrame embed to attach the notification center rather than the React component, you need to specify the `backendUrl` and the `socketUrl` when initializing the iframe.
+
+```html
+
+```
+
+### Using React Component with custom installation
+
+See [Use your own backend and socket URL](https://v0.x-docs.novu.co/notification-center/client/react/get-started#use-your-own-backend-and-socket-url).
+
+### Caching
+
+We are introducing the first stage of caching in our system to improve performance and efficiency. Caching is turned off by default, but can easily be activated by setting the following environment variables:
+
+- REDIS_CACHE_SERVICE_HOST
+- REDIS_CACHE_SERVICE_PORT
+
+Currently, we are caching data in the most heavily loaded areas of the system: the widget requests such as feed and unseen count, as well as common DAL requests during the execution of trigger event flow. These are the most heavily used areas of our system, and we hope that by implementing caching in these areas, we can improve performance in the near future.
+
+### Reverse-Proxy / Load Balancers
+
+To implement a reverse-proxy or load balancer in front of Novu, you need to set the GLOBAL_CONTEXT_PATH for the base path of the application.
+This is the path that the application will be served from after the domain.
+For example: - company.com/novu
+This is used to set the base path for the application, and is used to set the base path for the API, web, and websocket connections.
+
+The following environment variables are used to set the context path for each public service that Novu provides:
+API_CONTEXT_PATH
+WIDGET_CONTEXT_PATH
+WS_CONTEXT_PATH
+WEB_CONTEXT_PATH
+
+These allow you to set the context path for each service independently or dependently of the GLOBAL_CONTEXT_PATH.
+
+For example, if I was using a reverse proxy to serve Novu from company.com/novu,
+I would set the GLOBAL_CONTEXT_PATH to novu,
+and then set the API_CONTEXT_PATH to api,
+the WIDGET_CONTEXT_PATH to widget,
+the WS_CONTEXT_PATH to ws,
+and the WEB_CONTEXT_PATH to web.
+
+This would produce the following urls: - API: company.com/novu/api - WIDGET: company.com/novu/widget - WS: company.com/novu/ws - WEB: company.com/novu/web
+
+However the Service context path can be used entirely independently of the GLOBAL_CONTEXT_PATH.
+
+For example, if I wanted to expose the api as novu-api, I would set the API_CONTEXT_PATH to novu-api without setting the GLOBAL_CONTEXT_PATH.
+This would producte the following url: - API: company.com/novu-api
+
+
+ {" "}
+ These env variables should be present on all services novu provides due to tight
+ coupling.
+
+
+## FAQs
+
+### Local Tunnel and Self-Hosted Deployments
+
+Novu uses a local tunnel to connect to Local Studio during development. Local tunnel support is not included as part of the Novu Docker images. This is typically unnecessary for many users, depending on how their environment is structured.
+
+#### When is Local Tunnel Not Required?
+If the customer's application and the self-hosted Novu deployment are within the same network, there is no need for a local tunnel. In this case, the application can communicate directly with Novu through the internal network.
+
+#### When is Local Tunnel Required?
+If the application and Novu deployment reside on different networks, you can still interact with your self-hosted Novu instance using the Novu CLI. The CLI allows you to specify the Dashboard URL and Bridge Endpoint Origin to enable communication across networks via the Novu Cloud Local Tunnel.
+
+For example, you can use the following command:
+
+```bash
+npx novu@latest dev -d http://my-self-hosted-novu-domain.com:my-port -o http://my-app-running-novu-domain.com
+```
+
+- `-d`: The URL of your self-hosted Novu Dashboard.
+- `-o`: The origin of your application that will be communicating with Novu.
+
+For more details on the available options, refer to the CLI help by running:
+
+```bash
+npx novu@latest dev --help
+```
diff --git a/community/self-hosting-novu/overview.mdx b/community/self-hosting-novu/overview.mdx
new file mode 100644
index 00000000..f4aa3bb5
--- /dev/null
+++ b/community/self-hosting-novu/overview.mdx
@@ -0,0 +1,43 @@
+---
+title: 'Overview'
+description: 'Self-hosting Novu provides full control and flexibility over your notification infrastructure, with specific system requirements for optimal performance across VMs, Redis, MongoDB, and S3 storage.'
+icon: 'code'
+---
+
+When self-hosting Novu, you take full control over your communication infrastructure by deploying it on your own servers. While this setup allows for customization and greater flexibility, it is important to note that some features exclusive to Novu’s cloud-managed solution will not be available in a self-hosted environment.
+
+## System requirements overview
+
+### Hosting Novu services on separate VMs
+For optimal performance, we recommend hosting Novu's core services across multiple virtual machines (VMs).
+
+- **Novu services:**
+ - 3 VMs per service
+ - Each VM: 2 vCPUs and 4GB of RAM
+- **Redis:**
+ - 2 Redis clusters (one dedicated to queues with Append-Only Log (AOL) enabled)
+ - Minimum: 8GB RAM per cluster
+- **MongoDB:**
+ - 1 MongoDB cluster (M20 or higher on MongoDB Atlas)
+- **Storage:**
+ - 10GB of S3 storage
+
+### Hosting all Novu services on a single VM
+If resources are limited or simplicity is a priority, Novu services can be hosted on a single VM.
+
+- **All services:** 36 vCPUs and 64GB of RAM
+- **Storage:** 10GB of S3 storage
+
+### Redis requirements
+- **Redis Clusters:** 2 (one for queues with AOL enabled)
+- **Memory:** 8GB RAM per cluster
+- **AOL:** Active Append-Only Log (AOL) for data persistence and to prevent job loss during outages.
+
+### MongoDB requirements
+- **MongoDB cluster:** M20 or higher (recommended) on MongoDB Atlas.
+
+### Storage requirements
+- **S3 storage:** Minimum 10GB for file storage.
+
+The above specifications are general recommendations. Adjust them based on your system load, usage patterns, and scale of operations.
+Self-hosting Novu does not support GitHub login. To access your account, please use the email and password associated with your Novu account.
\ No newline at end of file
diff --git a/community/self-hosting-novu/telemetry.mdx b/community/self-hosting-novu/telemetry.mdx
new file mode 100644
index 00000000..42c2fd58
--- /dev/null
+++ b/community/self-hosting-novu/telemetry.mdx
@@ -0,0 +1,61 @@
+---
+title: "Telemetry"
+description: ""
+icon: 'chart-column'
+---
+Telemetry in Novu encompasses the collection of data regarding user interactions with the platform. This data enables the Novu team to identify usage patterns, troubleshoot issues, and make informed decisions about new features and improvements.
+
+## Data collected by Novu
+Novu does not capture any data from your APIs, databases, or third-party tools. All information collected from self-hosted instances is completely anonymized to protect user privacy.
+
+### Keep Alive Beacon
+The Novu server sends a keep-alive ping every hour to confirm its operational status without errors. This data is collected regardless of whether telemetry is enabled or disabled.
+```json
+{
+ "freeMemory": 115703808,
+ "hostname": "somemachine",
+ "instanceId": "ba54fb29-6422-4a83-a0e4-951d767efa73",
+ "ipAddress": "192.168.1.4",
+ "platform": "darwin",
+ "release": "23.6.0",
+ "timestamp": "2024-10-04T11:54:14",
+ "totalMemory": 8589934592
+}
+```
+
+### Usage Statistics
+When telemetry is enabled, the server collects anonymous information regarding users, organizations, events, notifications, and more. This data provides insights into platform usage and helps us improve the Novu Project for all users.
+```json
+{
+ "eventCount": 1,
+ "integrationCount": [
+ {
+ "count": 1,
+ "providerId": "example"
+ },
+ {
+ "count": 1,
+ "providerId": "novu"
+ }
+ ],
+ "orgCount": 1,
+ "subscriberCount": 1,
+ "timestamp": "2024-10-04T11:54:30",
+ "topicCount": 3,
+ "totalSteps": 10,
+ "userCount": 3,
+ "workflowCount": 6
+}
+```
+## Disable telemetry
+Sharing telemetry data is optional. You can disable telemetry via the Admin Settings or by modifying the relevant environment variable.
+
+### Environment variable
+To disable telemetry using the `NOVU_TELEMETRY` environment variable, follow these steps:
+
+1. Go to the directory where the .env file is located.
+2. Open the file in an editor and search for `NOVU_TELEMETRY`.
+3. Change the value of `NOVU_TELEMETRY` to `false`.
+4. Restart the Docker container.
+
+Once the container restarts, Novu will be running with telemetry disabled.
diff --git a/community/windows-machine-setup.mdx b/community/windows-machine-setup.mdx
new file mode 100644
index 00000000..21c7bb0e
--- /dev/null
+++ b/community/windows-machine-setup.mdx
@@ -0,0 +1,121 @@
+---
+title: "Windows"
+description: "Learn how to configure your local Windows machine to run Novu"
+---
+
+## Setting up git
+
+To set up git and create an access token for GitHub, suggested installing [GitHub Desktop](https://www.notion.so/Dev-Machine-Setup-98d274c80fa249b0b0be75b9a7a72acb?pvs=21) before.
+
+## Development Environment
+
+### Node & NVM
+
+To easily manage multiple node versions you can use [NVM for Windows](https://github.com/coreybutler/nvm-windows) a (Node Version Manager alternative for Windows).
+
+Download the .exe installation file for NVM for Windows [here](https://github.com/coreybutler/nvm-windows/releases).
+
+After installing NVM for Windows, check that the installation is successfull and added to path by running `nvm -v` on a terminal.
+
+
+ After installing NVM, if you get `nvm: command not found` or see no feedback
+ from your terminal after you type `nvm -v`, simply **close your current
+ terminal, open a new terminal**, and try verifying again.
+
+
+Once nvm is installed and working you can install any version of Node using the syntax `nvm install vX.Y.Z` and to switch to a node version,
+run `nvm use vX.Y.Z`. Novu requires node v20.8.1 or higher:
+
+```shell
+nvm install v18.17.0
+nvm use v18.17.0
+```
+
+### PNPM
+
+For package management, we use [PNPM](https://pnpm.io/) instead of npm or yarn to speed up our install times and space efficiency. To install pnpm globally run:
+
+```shell
+npm install -g pnpm
+```
+
+### MongoDB
+
+for detailed instructions on installing MongoDB community edition on a Windows machine please refere to the MongoDB doc [here](https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-windows).
+
+After installation, whether you have installed as a Windows service or just the binaries, ensure that the MongoDB instance is started before you proceed.
+
+### Redis
+
+To install Redis for local development you'll first need to enable WSL2 (Windows Subsystem for Linux). detailed instructions for installing
+WSL can be found [here](https://learn.microsoft.com/en-us/windows/wsl/install)
+
+If you have WSL installed and enabled, you can easily install Redis using the native procedure for the Linux distribution that you have installed.
+for Ubuntu or Debian which is the default distro installed by WSL, use the following commands
+
+1. First install `lsb-release`, `curl` and `gpg` if you do not already have them:
+
+```shell
+sudo apt install lsb-release curl gpg
+```
+
+2. Add the repository to the apt index, update it, and then install:
+
+```shell
+curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
+
+echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list
+
+sudo apt-get update
+sudo apt-get install redis
+```
+
+3. Lastly, start the Redis server like so:
+
+```shell
+sudo service redis-server start
+```
+
+### Docker
+
+Docker is not a requirement to run Novu on your local machine, but if you would prefer to use Docker, Follow the installation guide on [docker website](https://docs.docker.com/desktop/install/windows-install/) to install **Docker Desktop** on your Windows machine.
+
+### AWS CLI
+
+Follow the instructions to install the AWS CLI so you can use it to create env later on, based on existing S3 buckets:
+
+[Installing or updating the latest version of the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)
+
+You should get something like this:
+
+
+
+If needed, see:
+
+[Resolve "Unable to locate credentials" error in Amazon S3](https://aws.amazon.com/premiumsupport/knowledge-center/s3-locate-credentials-error/)
+
+### Local dev domain (Optional)
+
+This will allow you to use [local.novu.co](http://local.novu.co) instead of [localhost](http://localhost) when accessing the service on your machine.
+
+Navigate to `C:\Windows\System32\drivers\etc` in the file explorer and open the `hosts` file with a text editor (like Notepad):
+
+
+ Note that this file can only be edited when running as an Administrator.
+
+
+And add the [local.novu.co](http://local.novu.co) domain and save.
+
+```shell
+127.0.0.1 local.novu.co
+```
+
+### Code Editor
+
+Feel free to install your favorite code editor. We supply licenses for [WebStorm](https://www.notion.so/WebStorm-7570ab1037bf494d88d0f9b544ae3b79?pvs=21), or you can [Visual Studio Code](https://code.visualstudio.com/download) if you prefer it.
+
+### Productivity tools
+
+- GitHub Desktop (Optional)
+
+GitHub Desktop simplifies the development workflow and interaction with Git. [Download](https://desktop.github.com/) the client from their website.
diff --git a/concepts/endpoint.mdx b/concepts/endpoint.mdx
new file mode 100644
index 00000000..996cd8e9
--- /dev/null
+++ b/concepts/endpoint.mdx
@@ -0,0 +1,68 @@
+---
+title: "Framework Endpoint"
+description: "Learn how to customize and extend any aspect of the Novu platform using the Framework SDK powered bridge endpoint."
+---
+
+import { SvelteIcon } from "/snippets/icons/svelte.mdx";
+import { NestjsIcon } from "/snippets/icons/nestjs.mdx";
+import { ExpressjsIcon } from "/snippets/icons/expressjs.mdx";
+import { LambdaIcon } from "/snippets/icons/lambda.mdx";
+import { NuxtIcon } from "/snippets/icons/nuxt.mdx";
+import { NextjsIcon } from "/snippets/icons/nextjs.mdx";
+import { RemixIcon } from "/snippets/icons/remix.mdx";
+import { H3Icon } from "/snippets/icons/h3.mdx";
+import { FrameworkTerminal } from "/snippets/framework-terminal.mdx";
+
+Novu was built for endless extensibility. Although not required for majority of your notification usecases, you can customize and extend any aspect of the Novu platform using the Framework SDK powered bridge endpoint.
+
+## Overview
+
+The framework endpoint is a Restful API path that runs on your server and environment and can be used for multiple usecases including:
+
+- Automating workflow creation using GitOps
+- Adding custom components to your emails and other channels
+- Hydrating resources including subscribers, topics and tenants directly from your database
+- Executing custom logic before, during and after a workflow is executed
+- Connect to 3rd-party services such as OpenAI, Stripe, Salesforce and more.
+- Implement custom delivery providers with internal systems
+
+## How it works
+
+
+
+
+
+### Build phase
+
+When [building new workflows](/workflow/overview), you will be using the [Novu Framework SDK](/framework/typescript/overview) to define the workflows and their steps directly in your IDE. The local workflows can be previewed and tested using our [Local Studio](/framework/studio) companion app.
+
+### Sync phase
+
+A sync is needed once a notification workflow code is created or updated. We offer multiple sync methods including CI/CD integrations and a custom CLI tool.
+
+During a sync, the workflows defined using the Novu Framework SDK will be pushed to our cloud service and will be persisted in our database.
+Once synced, you will be able to view the workflows in the workflows Page on the Dashboard. All the controls created using JSON Schema or Zod will be transformed into UI elements that can be modified without changing the source code. Read more about controls [here](/framework/controls).
+
+### Execution phase
+
+Once a workflow is synced, the workflow engine will make API requests to the bridge endpoint to execute individual workflow steps.
+After the fan-out is complete, the bridge endpoint will be triggered with the relevant execution context and will respond with the compiled content and metadata needed to deliver the notification.
+
+## Get Started
+
+To get started with the framework endpoint, visit the Getting Started page relevant to your tech-stack:
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+ The Framework SDK is currently only supported in Typescript for JS based run-times.
+
diff --git a/concepts/environments.mdx b/concepts/environments.mdx
new file mode 100644
index 00000000..8cc2fda7
--- /dev/null
+++ b/concepts/environments.mdx
@@ -0,0 +1,61 @@
+---
+title: "Environments"
+description: "Understanding and managing environments in Novu"
+---
+
+Novu uses environments to separate your development and production workflows. When you create an account, you'll get two default environments: `Development` and `Production`.
+
+## Development Environment
+
+Use the development environment to test new notification workflows, validate changes before deploying to production, and experiment with different configurations.
+
+## Production Environment
+
+The production environment is your live environment where notifications are sent to real users.
+
+## Custom Environments
+
+Custom environments are available in Business and Enterprise plans
+
+Create custom environments to match your development workflow:
+
+1. Go to the Environments page in your dashboard
+2. Create environments like `Staging` or `QA`
+3. Assign unique colors to easily distinguish between environments
+4. Sync changes between environments just like with development and production
+
+The sync process works the same way as between development and production environments.
+
+## What's Unique to Each Environment?
+
+Each environment maintains its own separate:
+- Subscriber list
+- Notification workflows
+- Message history
+- Activity feed
+- Integration settings
+- Notification feeds
+- Brand assets and settings
+
+## Environment Credentials
+
+Each environment has two unique identifiers:
+
+1. **Application Identifier**
+ - Public ID for client-side apps
+ - Unique per environment
+ - Safe to expose in frontend code
+
+2. **API Secret Key**
+ - Used for backend API authentication
+ - Keep this secure and never expose publicly
+ - Different for each environment
+
+**Best Practice**: Configure these credentials in your application based on the active environment, similar to how you manage other service credentials.
+
+## Promoting Changes to Production
+
+You can move changes from development to production in two ways:
+1. Using the Dashboard's sync interface
+2. Using the Sync API in your CI/CD pipeline
+
diff --git a/concepts/integrations.mdx b/concepts/integrations.mdx
new file mode 100644
index 00000000..ab1f67bc
--- /dev/null
+++ b/concepts/integrations.mdx
@@ -0,0 +1,39 @@
+---
+title: "Integrations"
+description: "Learn about what Novu Integrations are"
+---
+
+## Integrations
+
+Integrations are what allow Novu to handle message delivery over multiple channels. Of the five notification channels that Novu currently supports, each channel except the ‘in-app’ channel has multiple providers.
+
+### Integraton Store
+
+You can configure your own integrations by visiting the [Integration Store](https://dashboard-v2.novu.co/integrations).
+When connecting the integration you will be asked to provide the `credentials` and other optional configurations for the integration. All secret keys are encrypted before persisted to the database.
+
+### Available Integrations
+
+You can find the list of available integrations for each channel:
+
+
+
+ Configure email providers and settings
+
+
+ Set up SMS messaging capabilities
+
+
+ Enable push notification delivery
+
+
+ Integrate with chat platforms
+
+
+ Manage in-app notification center
+
+
+
+### Missing an integration?
+
+If you don't see the integration you need, you can create a custom integration using the [Custom Step](/workflow/custom). Or contribute to the [Novu Integrations Repository](/community/add-a-new-provider).
diff --git a/concepts/notifications.mdx b/concepts/notifications.mdx
new file mode 100644
index 00000000..6fe063aa
--- /dev/null
+++ b/concepts/notifications.mdx
@@ -0,0 +1,77 @@
+---
+title: "Notifications"
+description: "Learn about what Novu Notifications are, their structure, and functionality"
+---
+
+Notifications in Novu are the primary data entities that facilitate communication between your application and its users. These entities encapsulate critical details about the subscriber, the message, and the delivery process, enabling streamlined and reliable interactions across various channels.
+
+## Anatomy of a notification
+
+A notification in Novu is designed to provide complete visibility and control over its lifecycle.
+
+```json Sample Notification Object
+{
+ "data": {
+ "_id": "",
+ "_environmentId": "",
+ "_organizationId": "",
+ "transactionId": "",
+ "createdAt": "",
+ "channels": "in_app",
+ "subscriber": {
+ "firstName": "",
+ "_id": "",
+ "lastName": "",
+ "email": "",
+ "phone": ""
+ },
+ "template": {
+ "_id": "",
+ "name": "",
+ "triggers": [
+ ""
+ ]
+ },
+ "jobs": [
+ ""
+ ]
+ }
+}
+```
+
+### Key Components
+
+1. **Identification**
+ - `_id`: Unique identifier for the notification.
+ - `_environmentId`: Links the notification to a specific environment (e.g., staging, production).
+ - `_organizationId`: Identifies the organization that owns the notification.
+
+2. **Transaction and timing**
+ - `transactionId`: Tracks individual notification transactions for auditing and debugging.
+ - `createdAt`: Timestamp indicating when the notification was created.
+
+3. **Channel and delivery**
+ - `channels`: Specifies the delivery channel, such as in-app notifications, email, SMS, or push notifications.
+
+4. **Subscriber details**
+ - `subscriber`: Contains user-specific data:
+ - `firstName`, `lastName`, `email`, `phone`: Information about the recipient.
+ - `_id`: A unique identifier for the subscriber.
+
+5. **Notification workflow**
+ - `template`: Links the notification to a predefined message format:
+ - `_id`: Identifier for the workflow.
+ - `name`: Workflow name.
+ - `triggers`: Events or actions that initiate the notification (e.g., user sign-up, order confirmation).
+
+6. **Jobs**
+ - `jobs`: Tracks tasks associated with processing and delivering the notification.
+
+---
+
+### Notification APIs
+
+
+ Query notifications and update messages individually or in batches via the Novu API.
+
+
diff --git a/concepts/payload.mdx b/concepts/payload.mdx
new file mode 100644
index 00000000..6039892f
--- /dev/null
+++ b/concepts/payload.mdx
@@ -0,0 +1,57 @@
+---
+title: "Payload"
+---
+
+Workflow payload is the data passed during the `novu.trigger` method. This is useful for ensuring that the payload is correctly formatted and that the data is valid.
+
+## Payload Schema
+
+Payload schema is defining the payload passed during the `novu.trigger` method. This is useful for ensuring that the payload is correctly formatted and that the data is valid.
+
+```tsx
+workflow(
+ "comment-on-post",
+ async ({ step, payload }) => {
+ await step.email("send-email", async () => {
+ return {
+ subject: `You have a new comment from: ${payload.author_name}.`,
+ body: render(),
+ };
+ });
+ },
+ {
+ payloadSchema: {
+ // Always `object`
+ type: "object",
+ // Specify the properties to validate. Supports deep nesting.
+ properties: {
+ post_id: { type: "number" },
+ author_name: { type: "string" },
+ comment: { type: "string", maxLength: 200 },
+ },
+ // Specify the array of which properties are required.
+ required: ["post_id", "comment"],
+ // Used to enforce full type strictness, with no rogue properties.
+ additionalProperties: false,
+ // The `as const` is important to let Typescript know that this
+ // type won't change, enabling strong typing on `inputs` via type
+ // inference of the provided JSON Schema.
+ } as const,
+ }
+);
+```
+
+## Passing Payload
+
+Here is an example of the validated payload during trigger:
+
+```tsx
+novu.trigger("comment-on-post", {
+ to: "subscriber_id",
+ payload: {
+ post_id: 1234,
+ author_name: "John Doe",
+ comment: "Looks good!",
+ },
+});
+```
diff --git a/concepts/preferences.mdx b/concepts/preferences.mdx
new file mode 100644
index 00000000..bff1d107
--- /dev/null
+++ b/concepts/preferences.mdx
@@ -0,0 +1,77 @@
+---
+title: "Preferences"
+---
+
+Novu provides a way to store subscriber preferences. This allows subscribers, your users, to specify and manage their preferences and customize their notifications experience.
+
+**Levels of preferences:**
+
+- Workflow channel preferences
+- Subscriber channel preferences per workflow
+- Subscriber global preferences
+
+## Workflow channel preferences
+
+When creating a new workflow, you can specify default preferences for your subscribers via code or in the Dashboard. These preferences will be used during notification delivery unless the subscriber overrides via the preferences page in ``.
+
+
+
+
+
+## Subscriber channel preferences per workflow
+
+`` displays the available preferences per workflow, allowing subscribers to modify them for each channel. Critical workflows will be excluded from the list.
+
+
+
+
+
+
+ Inbox displays only channels used by the current workflow.
+
+
+## Subscriber global preferences
+
+Subscribers can set global channel preferences, which override individual settings. For instance, if there are 10 workflows, and a subscriber wants to disable SMS notifications for all of them, they can do so with via global preferences.
+
+
+
+
+
+## Critical workflows
+
+In some cases, you don't want the subscriber to be able to unsubscribe from mandatory notifications such as Account Verification, Password Reset, etc...
+
+In those cases you can mark a workflow as `critical` in the Dashboard. Critical workflow are not displayed in the subscriber preferences page.
+
+## API
+
+
+
+
+
+
diff --git a/concepts/subscribers.mdx b/concepts/subscribers.mdx
new file mode 100644
index 00000000..6108d2dd
--- /dev/null
+++ b/concepts/subscribers.mdx
@@ -0,0 +1,179 @@
+---
+title: "Subscribers"
+---
+
+In Novu, we call the entities designed to receive notifications as `Subscribers`. Each subscriber is unique and identified by a unique subscriberId.
+
+
+ We recommend using the internal unique id your application uses for a specific
+ user as the `subscriberId`.
+
+
+Each subscriber has the following data points:
+
+- **User Data** - Data stored in the subscriber object that you can easily access in your notification templates. This contains basic info such as first name, last name, avatar, locale, email, and phone. This data is fixed and structured.
+- **Custom Data** - Apart from the above fixed structured user data, any unstructured custom data such as user's address, nationality, height, etc can also be stored in the `data` field using key-value pairs.
+- **Channel Specific Credentials** - `deviceTokens` required to send push notifications and `webhookUrl` for chat channel providers can also be stored.
+
+## Subscriber attributes
+
+| Field | Type | Required | Example |
+| :----------- | :----- | :------- | :------------------------------------ |
+| subscriberId | string | true | b0bea066-f5fe-11ed-b67e-0242ac120002 |
+| firstName | string | false | John |
+| lastName | string | false | Doe |
+| email | string | false | john.doe@domain.org. |
+| phone | string | false | +13603963366 |
+| locale | string | false | en |
+| avatar | string | false | https://example.com/images/avatar.jpg |
+| data | object | false | `{"key": "value"}` |
+
+## Creating a subscriber
+
+We support creating new subscriber using two ways, `Ahead of Trigger` means adding subscribers before triggering notification or `Just-in-time` means sending complete subscriber data in `to` field while triggering.
+
+### Just-in-time
+
+A non-existing subscriber can be added by sending subscriber data in `to` field of the trigger method. If any subscriber with provided `subscriberId` does not exists, a new subscriber will be created. In this case, subscriber will be created first and then the trigger will be executed synchronously.
+
+
+
+ ```jsx
+ import { Novu } from '@novu/node';
+
+ const novu = new Novu('');
+
+ await novu.trigger('', {
+ to: {
+ subscriberId: '111',
+ email: 'john.doe@domain.com',
+ firstName: 'John',
+ lastName: 'Doe',
+ phone: '+13603963366',
+ },
+ payload: {
+ customVariable: 'variableValue',
+ organization: {
+ logo: 'https://organization.com/logo.png',
+ },
+ },
+ });
+ ```
+
+
+ ```php
+ use Novu\SDK\Novu;
+
+ $novu = new Novu('');
+
+ $novu->triggerEvent([
+ 'name' => '',
+ 'to' => [
+ 'subscriberId' => '111',
+ 'email' => 'john.doe@domain.com',
+ 'firstName' => 'John',
+ 'lastName' => 'Doe',
+ 'phone' => '+13603963366'
+ ]
+ 'payload' => [
+ 'customVariable' => 'variableValue',
+ 'organization' => [
+ 'logo' => 'https://organization.com/logo.png',
+ ]
+ ],
+ ]);
+ ```
+
+
+
+
+When triggering the workflow, you must specify all required `to` fields per the channels in the workflow. For example, if an email step is included in the workflow, then the `to.email` field must be specified. Similarly if an SMS step is included, the `to.phone` field must be specified.
+
+If you are defining workflows with code, you can use the `subscriberId` to hydrate subscriber data directly from your database or other sources during workflow execution. This is useful when you don't want to store all the subscriber data in Novu.
+
+For example, you may have a welcome onboarding email that you trigger immediately after a user signs up, with the email only sent 1 hour after signup:
+
+```typescript
+import { workflow } from '@novu/framework';
+import { userDb } from '../db/userDb';
+
+export const welcomeEmail = workflow('welcome-email', async ({ step, subscriber }) => {
+ await step.delay('delay', () => ({ amount: 1, unit: 'hours' }));
+
+ await step.email('send-email', async (controls) => {
+ // Fetch user data from your database in real-time to
+ // ensure up-to-date information
+ const user = await userDb.findById(subscriber.subscriberId);
+
+ return {
+ subject: `Welcome, ${user.firstName}`,
+ body: 'Welcome to our platform!',
+ };
+ });
+});
+```
+
+Then, when you trigger the workflow, you can pass the `subscriberId` and the workflow will fetch the user data from your database in real-time:
+
+```typescript
+await welcomeEmail.trigger({
+ to: {
+ subscriberId: '123',
+ email: 'john.doe@domain.com',
+ },
+});
+```
+
+### Migration (Optional)
+
+Create the subscriber and then trigger the notification to this subscriber. Here `subscriberId` is the required field and other fields are optional.
+
+
+
+```jsx
+import { Novu } from '@novu/node';
+
+const novu = new Novu('');
+
+await novu.subscribers.identify('111', {
+ email: 'john.doe@domain.com',
+ firstName: 'John',
+ lastName: 'Doe',
+ phone: '+13603963366',
+ avatar: 'https://example.com/images/avatar.jpg',
+ locale: 'en-US',
+ data: { customKey1: 'customVal1', customKey2: 'customVal2' },
+});
+
+```
+
+
+
+```php
+use Novu\SDK\Novu;
+
+$novu = new Novu('');
+
+$novu->createSubscriber([
+ 'subscriberId' => '111',
+ 'email' => 'john.doe@domain.com',
+ 'firstName' => 'John',
+ 'lastName' => 'Doe',
+ 'phone' => '+13603963366',
+ 'avatar' => 'https://example.com/images/avatar.jpg',
+ 'locale' => 'en',
+ 'data' => [
+ 'customKey1' => 'customVal1',
+ 'customKey2' => 'customVal2'
+ ]
+]);
+```
+
+
+
+
+Novu will create a subscriber if one does not exist and will update the existing subscriber based on the `identify` payload. You can call this function during registration or signup to make sure the subscriber data is up-to-date, if you wish to save additional attributes with a subscriber, you can pass additional custom data in the **data** field as key-value pairs.
+
+## Bulk Subscriber Creation
+
+You can create subscribers in bulk _(up to 500 at once)_ via the SDKs or [API](/api-reference/subscribers/bulk-create-subscribers).
diff --git a/concepts/tenants.mdx b/concepts/tenants.mdx
new file mode 100644
index 00000000..ed38be7f
--- /dev/null
+++ b/concepts/tenants.mdx
@@ -0,0 +1,37 @@
+---
+title: "Multi-tenancy"
+description: "Learn about how to implement multi-tenant notifications in Novu"
+---
+
+
+This features is currently under development, and planned to be released in early Q1 2025.
+
+
+Multi-tenancy is a common use case for a lot of companies that have multiple organizations that use their applications. In some cases, there is a need to separate the behavior of the notification system depending on the individual tenants.
+
+Some of the common multi-tenancy use cases are:
+- Group subscribers by the tenant
+- Use a different delivery provider for each tenant
+- Adjust the content of the notification depending on the tenant
+- Specify different subscriber preference defaults
+
+## Tenant Management
+
+Tenants can be created and modified via the API or Dashboard. Each tenant can have multiple fields representing the tenant's data:
+
+- Identifier - The identifier is a unique value, and can be used later when pointing to this tenant during trigger calls.
+- Name - A human-readable name of the tenant.
+- Data - A custom data object that can store information about the tenant. This data can be later accessed inside workflows.
+
+## Multi-tenancy vs topics
+
+While topics can be mapped to any nested entity in your application such as teams, projects, issues, and etc...
+and used for broadcasing messages to different subsets of your audience, tenants are some times refered to as "organizations", "workspaces", "companies" or "accounts" and are usually directly tied to your customer entities which used to customize the notification behavior and defaults for each customer.
+
+## Using Tenant data
+
+When creating a tenant, you can specify a data object that will be available to you in your workflows. This can be useful to store information about the tenant such as the name, logo, etc...
+
+```
+Welcome to the {{ tenant.name }} workspace!
+```
diff --git a/concepts/topics.mdx b/concepts/topics.mdx
new file mode 100644
index 00000000..03979351
--- /dev/null
+++ b/concepts/topics.mdx
@@ -0,0 +1,189 @@
+---
+title: "Topics"
+---
+
+Novu offers a simple API providing an easy interface for triggering notifications to multiple subscribers at once. It is called Topics and allows the users to manage their bulk notifications without having to do complex loop implementations. A topic is identified by a custom key that is provided by the user. This custom key will be the identifier for the topic.
+
+
+ The topic key should be unique and can't be changed once chosen. Novu also
+ safe guards for key uniqueness behind the scenes.
+
+
+Users can also assign a name to a topic. This name doesn't need to be unique and it can be changed using the API. So far, it is for descriptive goals.
+
+A topic would get assigned different subscribers that will receive a notification every time a notification is sent to the topic.
+
+## Common usecases
+
+- Send to all users who have commented on a post.
+- Send to all users who subscribed to task updates.
+- Send an update to all tenant members
+- Other use cases that require sending a notification to a group of subscribers around a specific structure
+
+## Create a topic
+
+In order to be able to send a notification to a topic, first the user needs to create one. It can be done like this:
+
+```typescript
+import { Novu } from "@novu/node";
+
+const novu = new Novu("");
+
+const result = await novu.topics.create({
+ key: "unique-topic-identifier",
+ name: "descriptive-topic-name",
+});
+```
+
+If successful this will return the internal Id generated by Novu and the topic key given by the user.
+
+That topic key can be used to retrieve the whole Topic entity:
+
+```typescript
+import { Novu } from "@novu/node";
+
+const novu = new Novu("");
+
+const key = "unique-topic-identifier";
+
+const result = await novu.topics.get(key);
+```
+
+## Add subscribers to a topic
+
+Adding subscribers to the topic is the main usecase of topic. A topic is like a group of subscribers. A notification can be sent to all subscribers of a topic at once
+
+
+ Before adding subscribers to a topic, it's crucial to ensure that they are
+ correctly identified in the Novu system. This identification is done using the
+ [identify method](/concepts/subscribers#creating-a-subscriber) or API. When a
+ subscriber is added to a topic, Novu uses the identification information to
+ pick up the email or phone number associated with the subscriber.
+
+
+```typescript
+import { Novu } from '@novu/node';
+
+const novu = new Novu("");
+
+const topicKey = 'posts:comment:12345';
+
+const response = await novu.topics.addSubscribers(topicKey, {
+ subscribers: ['subscriber-id-1', 'subscriber-id-2', ...],
+});
+```
+
+This call will return a response in the shape of:
+
+```typescript
+{
+ succeeded: ['subscriber-id-1', 'subscriber-id-2', ...],
+ failed: {
+ notFound: ['subscriber-id-1', 'subscriber-id-2', ...],
+ },
+}
+```
+
+The field `succeeded` will return the array of subscriber ids that have been correctly assigned to the topic. The field `failed` will show up only if there has been any subscriber that couldn't been assigned to the topic. This probably will happen if there is any typo in the given subscriberId or if the given subscriberId doesn't belong to the environment and the organization where the topic has been created.
+
+One topic can have maximum 500K subscribers
+
+### Create topic on the fly
+
+To facilitate flows we allow to create a topic on the fly when adding subscribers.
+
+If the topic key used does not exist for the selected environment and organization, Novu will create a new topic with name `Autogenerated-` and the subscribers will be assigned to this newly created topic.
+After that, that topic created on the fly can be managed in the same ways as the ones created in the normal way.
+
+## Trigger workflow to a topic
+
+A workflow can be triggered to a single subscriber and an array of subscribers. While doing so, one need to send subscriberIds in `to` field. In case of trigger, we have already stored subscriberIds in a topic, so a topic key can be used to trigger a workflow. The workflow will be triggered to all subscribers in the topic.
+
+For billing purposes, when a workflow is triggered to a topic, it will create one workflow event per topic member.
+
+```typescript
+const topicKey = "posts:comment:12345";
+
+await novu.trigger("", {
+ to: [{ type: "Topic", topicKey: topicKey }],
+ payload: {},
+});
+```
+
+## Exclude actor from topic trigger event
+
+To exclude the actor responsible for the action of a triggered topic event, you must add the subscriberId of that actor when triggering that event.
+
+```typescript
+const topicKey = "posts:comment:12345";
+
+await novu.trigger("", {
+ to: [{ type: "Topic", topicKey: topicKey }],
+ payload: {},
+ actor: { subscriberId: "" },
+});
+```
+
+## API
+
+
+
+
+
+
+
+
+
+
+
+
+## Frequently Asked Questions
+
+
+
+ No, adding subscribers into topic does not change subscriber's individual
+ [subscriber preference](/concepts/preferences). Topic is mainly used to
+ group subscribers and fan out workflow triggered notifications to all
+ subscribers at once.
+
+
diff --git a/concepts/trigger.mdx b/concepts/trigger.mdx
new file mode 100644
index 00000000..46520967
--- /dev/null
+++ b/concepts/trigger.mdx
@@ -0,0 +1,52 @@
+---
+title: "Trigger"
+description: "Managing Trigger Events from Request to Processing"
+---
+
+## Trigger Request
+
+A trigger request is the initial step in handling a trigger event. It contains crucial details such as the template identifier, a list of subscribers who will receive the notification, the payload of the notification, and any overrides that need to be applied.
+
+## Trigger Endpoint
+
+Upon sending the request to the `/event/trigger` endpoint, a series of essential steps are initiated:
+
+1. **Subscriber Mapping and Validation:** The first step involves mapping and validating the subscribers for the specified event. This ensures that notifications are sent to the correct recipients.
+
+2. **Workflow Validation:** Following subscriber validation, the workflow associated with the event is validated. This validation process considers factors such as the active status to determine if it meets the necessary criteria for processing.
+
+3. **Attachment Upload:** Once the validation process is successfully completed, any attachments associated with the event are uploaded to the designated storage service.
+
+4. **Event Queuing:** The trigger event, now enriched with mapped subscribers and attachment links, is appended to the **trigger event queue**. This queuing mechanism optimizes response times, ensuring efficient event processing.
+
+## Trigger Event Processing
+
+When an event is picked up by the **trigger queue worker**, the processing phase begins. Here's what happens:
+
+- **Subscriber Upsert:** The worker validates the subscribers associated with the event and either creates or updates the subscriber with the information passed in the `to` object.
+- **Notification Entity Creation:** For each subscriber listed in the trigger event, a corresponding notification entity is created. This entity contains essential data related to the organization, template, subscriber, and event payload.
+- **Job Creation:** Based on the notification template's defined steps, jobs are generated. These jobs are responsible for carrying out specific tasks related to the event notification. Additionally, the notification entity is updated with a "channels" field generated from these steps, indicating the communication channels through which notifications will be sent.
+
+## Jobs
+
+Jobs play a crucial role in the trigger event lifecycle. They are created based on the steps outlined in the workflow.
+
+### Job Statuses
+
+| Status | Description |
+| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **PENDING** | This status is assigned to a job before it is added to the worker queue. It indicates that the job is waiting to be processed. |
+| **QUEUED** | After the initial validation and just before adding a job to the worker queue, it is set to `QUEUED`. This status signifies that the job is ready for processing but is awaiting its turn in the queue. |
+| **RUNNING** | When a job is picked up by a worker from the queue, its status is changed to `RUNNING`. This indicates that the job is currently being processed by a worker. |
+| **COMPLETED** | Once a job has been successfully executed and processed, its status is changed to `COMPLETED`. This signifies that the job has been successfully completed. |
+| **FAILED** | If a job encounters an issue during processing or execution, its status is changed to `FAILED`. This indicates that the job has not been successfully completed, and there may have been errors or problems during processing. |
+| **DELAYED** | The `DELAYED` status is applied to specific types of jobs, such as `digest` or `delay` jobs, to indicate that they are delayed and not immediately processed. For `digest` jobs, it means that the digesting process is running or scheduled for a later time. For `delay` jobs, it signifies that the job is set to be executed at a specified delay time. |
+| **CANCELED** | When a job is canceled for any reason, its status is set to `CANCELED`. This indicates that the job will not be processed further and is effectively removed from the processing queue. |
+| **MERGED** | The `MERGED` status is assigned to events that are part of a `digest`. It indicates that an event will be merged into the digesting event. In a digesting process, there is typically a primary or initial event that serves as the digesting event, and subsequent events are merged into it. Instead of having a separate `COMPLETED` status for these merged events, they are marked as `MERGED` to indicate their specific role in the digesting process. |
+| **SKIPPED** | The `SKIPPED` status is used in the context of backoff versions of digesting. In this scenario, the first event's digesting is skipped, and the second event takes on the digesting role. The `SKIPPED` status is applied to the first event's digesting, indicating that it was intentionally skipped in the digesting process. Subsequent events may be merged into the second event's digesting process, as explained with the `MERGED` status. The `SKIPPED` status helps differentiate the skipped event from others in the digesting sequence. |
+
+**Example:**
+
+
+ {" "}
+
diff --git a/concepts/workflows.mdx b/concepts/workflows.mdx
new file mode 100644
index 00000000..de18d8e6
--- /dev/null
+++ b/concepts/workflows.mdx
@@ -0,0 +1,147 @@
+---
+title: "Workflows"
+description: "Learn what workflows are and how they work in Novu"
+---
+
+A workflow holds the entire flow of steps (nodes) that are sent to the subscriber. This is where all the different channels are tied together under a single entity.
+
+## Structure
+
+A workflow acts as the blueprint for the notifications that will be sent. This is where all the different channels, filters, rules and actions are tied together under a single entity.
+
+The **Workflow Editor** simplifies building automated, personalized workflows without coding while also allowing developers to refine code-based workflows.
+
+## How it works
+
+Workflow Editor builds on the principle of steps. Instead of defining a single, complex piece of business logic, workflows contain multiple individual steps.
+
+In case of an error, a failed step is retried individually without needing to re-run any previous steps. Instead of the entire business logic, each step can take up your serverless function execution duration, and many more benefits.
+
+
+
+Here is how a workflow looks like in the UI Workflow Editor:
+
+
+
+## Workflow building blocks
+
+Every Novu workflow is built using these four key elements:
+
+- **Workflow identifier**: A unique ID used to link triggers to the correct workflow
+- **Trigger**: The event or action that starts the workflow
+- **Channel steps**: These send notifications through your configured channels
+- **Action steps**: Functions that manage the workflow’s logic and control its flow
+
+### Workflow identifier
+
+Give your Workflow a clear, descriptive name that your team can easily identify.
+Workflow identifiers should be unique to your application and descriptive of the workflow's purpose.
+
+For example:
+
+- Abandoned cart recovery
+- Password reset flow
+- New user welcome series
+- Re-engagement campaign
+
+### Trigger
+
+A trigger starts every workflow. It’s an action or event that tells the workflow to begin. To do this, you call the Novu API with a unique workflow trigger identifier (`workflow_id`), which links the trigger to the correct workflow.
+
+When the trigger happens, it sends the data the workflow needs to notify subscribers. [Learn more about subscribers](/concepts/subscribers).
+
+Triggers are the only way to start workflows and send notifications, making sure everything runs as expected.
+
+**Example of a trigger**
+Here's an example of an API call to trigger an event:
+
+```bash
+curl -X POST https://api.novu.co/v1/events/trigger \
+ -H "Authorization: ApiKey " \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "",
+ "to": {
+ "subscriberId": "",
+ },
+ "payload": {}
+ }'
+```
+
+### Channel step
+
+A **Channel step** in Novu represents a configured provider to send notifications to your recipients.
+
+The Workflow Editor is designed for **Omni-Channel Communication**, delivering a unified experience across diverse messaging channels:
+
+- **In-app notifications**
+- **Email**
+- **SMS**
+- **Push**
+ - **Mobile push**
+ - **Web push**
+- **Chat**
+ - **Enterprise messaging platforms** (e.g., Slack, Microsoft Teams)
+ - **Consumer messaging apps** (e.g., WhatsApp, Telegram, Discord)
+
+Most providers within Novu use credentials that you supply to deliver notifications on your behalf. These credentials and other settings are what make a configured channel.
+
+### Action steps
+
+**Action steps** are purpose built functions that help manage the workflow flow. **Action Steps** can be executed before and after **Channel steps**.
+For example, you can use a **Delay** to pause a workflow for a certain duration before sending a notification.
+
+There are three types of action steps supported by Novu:
+
+- **[Delay](/workflow/delay)**
+- **[Digest](/workflow/digest)**
+- **[Custom](/workflow/custom)**
+
+## Example use cases
+
+
+
+ Notifications for orders, shipping, or subscription statuses
+
+
+ Welcome emails, follow-up emails, and more
+
+
+ Re-engagement campaigns, follow-up emails, and more
+
+
+ Notifications for product updates, new features, and more
+
+
+ Notifications for abandoned carts and follow-up emails
+
+
+ Notifications for password resets and follow-up emails
+
+
+ Notifications for administrative actions like account deletions and more
+
+
+ Notifications for system errors and alerts
+
+
+
+## Execution of workflow steps
+
+When a workflow is triggered, its steps are executed in a predetermined sequence. This ensures a controlled, orderly flow and helps maintain the integrity of the notification process.
+Here's how it works:
+
+
+
+ Each step is executed one at a time, in the exact order they are defined. This guarantees that dependencies and prerequisites for later steps are met before moving forward.
+
+
+ Sequential execution creates a reliable process, ensuring that outputs from earlier steps seamlessly influence subsequent steps.
+
+
+ Messages are sent in a structured manner, preventing overlap or confusion and ensuring each step receives the attention it requires.
+
+
+
+By following this approach, workflows remain consistent, predictable, and effective.
+Read more about building workflows [here](/workflow/overview).
\ No newline at end of file
diff --git a/content-creation-design/brand.mdx b/content-creation-design/brand.mdx
new file mode 100644
index 00000000..f84fd399
--- /dev/null
+++ b/content-creation-design/brand.mdx
@@ -0,0 +1,11 @@
+---
+title: "Brand"
+description: "Customize and manage your notification brand settings including logos, colors, and email templates to maintain consistent brand identity."
+icon: "sparkle"
+---
+
+Through Novu's web dashboard, you have the power to effortlessly tailor your brand settings, streamlining the process of creating notifications. Customize key elements such as your logo, which will be seamlessly integrated into your email communications.
+
+For those utilizing Novu's In-App notification center, the ability to configure the primary font and its corresponding color awaits you. This empowers you to craft an experience for your users that resonates with your unique brand identity.
+
+
diff --git a/content-creation-design/handlebars-helpers.mdx b/content-creation-design/handlebars-helpers.mdx
new file mode 100644
index 00000000..7de6e528
--- /dev/null
+++ b/content-creation-design/handlebars-helpers.mdx
@@ -0,0 +1,239 @@
+---
+title: "Handlebars & Helpers"
+description: "Learn how to use Handlebars templating and built-in helper functions to create dynamic, personalized notification content in Novu."
+icon: "brackets-curly"
+---
+
+# Handlebars
+
+The Novu notification content editor relies on Handlebars.js to establish the logic and control flow of our workflows.
+For a deeper comprehension of Handlebars.js, you can refer to its [documentation](https://handlebarsjs.com/guide/#what-is-handlebars).
+
+This resource offers comprehensive insights into the utilization of Handlebars.js in crafting dynamic and adaptable notification content within the Novu platform.
+
+| Helper | Description | Example | Output |
+| --------------- | ------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------- |
+| if | Conditionally renders a block. Falsy values won't be rendered. | `{{#if author}}
{{firstName}} {{lastName}}
{{/if}}` | Renders `
Yehuda Katz
` if `author` is truthy. |
+| includeZero | `includeZero=true` treats the conditional as not empty, handling 0 differently. | `{{#if 0 includeZero=true}}
Does render
{{/if}}` | Renders `
Does render
` for 0. |
+| Sub-Expressions | Create custom logic helpers and use them in sub-expressions. | `{{#if (isdefined value1)}}true{{else}}false{{/if}}` | Renders `true` or `false` based on custom helper. |
+| unless | Inverse of if helper. Renders a block if the expression is falsy. | `{{#unless license}}
WARNING
{{/unless}}` | Renders warning if `license` is falsy. |
+| each | Iterate over a list. Use this to reference the iterated element. | `{{#each people}}
{{this}}
{{/each}}` | Renders list items. |
+| with | Change context for template parts. | `{{#with person}}{{firstname}} {{lastname}}{{/with}}` | Renders `firstname` and `lastname` from the context. |
+| Nested each | Access iteration variables in nested each blocks. | `{{#each array}}{{@index}}: {{this}}{{/each}}` | Renders index and item in each iteration. |
+| Nested with | Use with block parameters for clear code. | See above example with nested with and complex template. | Renders nested context values. |
+| | You can use else section to handle empty values. | `{{#with city}}...{{else}}No city found{{/with}}` | Handles empty context case. |
+
+# Novu-specific helpers
+
+| Handlebar Expression | Description |
+| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `{{dateFormat date 'EEEE, MMMM Do yyyy'}}` | You can format the date using the `dateFormat` function. Here, `date: '2020-01-01'` has been formatted into `Wednesday, January 1st 2020`. |
+| `{{lowercase key}}` | This helps you use the `lowercase` handlebar helper function and turns the value of the specified key to its lowercase value. So, for `message: 'hEllo WORLD'`, if we write `{{lowercase message}}`, we'll end up with `hello world`. |
+| `{{uppercase key}}` | This helps you use the `uppercase` handlebar helper function and turns the value of the specified key to its uppercase value. So, for `message: 'hello woRld'`, if we write `{{uppercase message}}`, we'll end up with `HELLO WORLD`. |
+| `{{titlecase key}}` | This helps you use the `titlecase` handlebar helper function and turns the value of the specified key to its titlecase value. So, for `message: 'hEllo wOrLD'`, if we write `{{titlecase message}}`, we'll end up with `Hello World`. |
+
+### groupBy
+
+`groupBy` helper is used to group the items in an array based on a property.
+
+
+
+```html
+{{#each (groupBy names "name")}}
+
+```
+
+
+
+### numberFormat
+
+`numberFormat` helper is used to format numbers with specified decimal length, thousands separator, and decimal separator.
+
+
+
+```html
+
{{numberFormat number decimalSep="," decimalLength="2" thousandsSep="|"}}
+```
+
+
+
+### pluralize
+
+The `pluralize` helper allows you to easily handle pluralization in your templates by providing different forms of a word depending on a variable value.
+
+### Basic Usage
+
+In its simplest form, the `pluralize` helper takes three arguments:
+
+```
+{{pluralize count singular plural}}
+```
+
+- `count`: The numeric value that determines whether the singular or plural form should be used.
+- `singular`: The singular form of the word.
+- `plural`: The plural form of the word.
+
+#### Example
+
+```
+{{pluralize dog_count "dog" "dogs"}}
+```
+
+In this example, if `dog_count` is equal to 1, it will render "1 dog," and if it's greater than 1, it will render "dogs."
+
+## Use Cases & Examples
+
+### Handling Event Count Display with Excluded Individuals
+
+In some cases, you may want to display the total count of certain events but exclude specific individuals who are already mentioned. For example, when using a phrase like "John and `{{step.total_count}}` people liked your photo," you may notice that the count is off by one because "John" is already mentioned.
+
+To account for such scenarios, you can utilize the following approach:
+
+```
+{{#each step.events}}
+ {{#if @first}}
+ {{this.name}}
+ {{else}}
+ {{#if @last}}
+ and {{@index}}
+ {{/if}}
+ {{/if}}
+{{/each}}
+followed you!
+```
+
+In this code, we iterate through the `step.events` array and conditionally display each name. If it's the first name in the list, it will be shown without "and." For subsequent names, we add "and" before the last name. This ensures that the count is not affected by names that are explicitly mentioned in the message.
+
+By using this code, you can achieve a more accurate representation of the event count in your messages, even when some users are individually mentioned in the message text.
+
+### Using array length as count
+
+Array length be used as total number of item. For example, if paylod have fruits property of type array then its length can be used as total number of fruits.
+
+
+
+```html
+Total number of fruits is {{fruits.length}}.
+```
+
+
+```json
+{
+ "fruits": [
+ {
+ "name": "Pine apple",
+ "price": "10$"
+ },
+ {
+ "name": "Apple",
+ "age": "8$"
+ },
+ {
+ "name": "Orange",
+ "age": "5$"
+ }
+ ]
+}
+```
+
+
+```html
+Total number of fruits is 3.
+```
+
+
+
+
+ {" "}
+ In a future release, we plan to replace handlebar helpers with an alternative solution.
+ Please note that custom handlebars in the editor itself are not currently supported.
+ If we are missing any handlebar helper that is blocking you to write template for
+ use case, feel free to reach out to us in [discord](https://discord.gg/novu?ref=docs-handlebar-helper)
+
diff --git a/content-creation-design/layouts.mdx b/content-creation-design/layouts.mdx
new file mode 100644
index 00000000..67c0ccbc
--- /dev/null
+++ b/content-creation-design/layouts.mdx
@@ -0,0 +1,178 @@
+---
+title: "Layouts"
+description: "Explore how to create and manage layouts in Novu"
+icon: "table-layout"
+---
+
+### Email Layouts
+
+Novu allows the creation of layouts - a specific HTML design or structure to wrap content of email notifications. Layouts can be manipulated and assigned to new or existing workflows within the Novu platform, allowing users to create, manage, and assign these layouts to workflows, so they can be reused to structure the appearance of notifications sent through the platform.
+
+By default, Novu will create a default layout and assign it as the organization's default layout. At any time, you can choose any of your layouts as the default for your organization. All new email templates will be assigned the default layout unless assigned a different one through the email editor.
+
+#### Manage Layouts
+
+You can find and manage your organization's layouts in `Brand -> Layouts`
+
+
+
+#### Create and Edit Layouts
+
+You can create new or edit existing layouts through the layout editor.
+
+- You can create any html code to structure your layout.
+- Set a layout as default for your organization. Notice there could only be one default layout at a time.
+- Manage layout variables - set default values or required variables.
+
+
+
+ Layout content must include `{{{body}}}`, to indicate where the email editor content will be injected inside the layout.
+
+#### Assign layout to workflow
+
+To assign, choose a layout through the email editor. You can preview your layout combined with your email content through the `Preview` tab.
+
+
+
+#### Override layout on trigger
+
+To override your assigned layout during a trigger event, use the `layoutIdentifier` property.
+
+The layout specified will be used for all emails in the context of that trigger event.
+
+```ts
+import { Novu } from "@novu/node";
+
+const novu = new Novu("");
+
+novu.trigger("workflow-identifier", {
+ to: {
+ subscriberId: "",
+ },
+ payload: {
+ attachments: [
+ {
+ file: fs.readFileSync(__dirname + "/data/test.jpeg"),
+ name: "test.jpeg",
+ mime: "image/jpg",
+ },
+ ],
+ },
+ overrides: {
+ layoutIdentifier: "your-layout-identifier",
+ },
+});
+```
+
+
+ {" "}
+ The override layout on trigger functionality is only available on v0.19.0.
+
+
+#### Using SDK
+
+Novu SDK supports all layout functionalities:
+
+#### Create a new layout
+
+A new layout can be created with name, description, content, variables and an `isDefault` flag. Here, content param is html content with custom variables.
+
+```tsx
+const name: string = "layout-name";
+const description: string =
+ "The description of the layout that will help other users to understand the goal it was created"; // Optional.
+const content: string = "";
+const variables: ITemplateVariable[] = []; // Optional. Will handle the placeholder variables inserted in the email layout (content).
+const isDefault: boolean = true; // Optional. All layouts are created as non default if not stated otherwise.
+
+const { _id: layoutId } = await novu.layouts.create({
+ name,
+ description,
+ content,
+ variables,
+ isDefault,
+});
+```
+
+```tsx
+interface ITemplateVariable {
+ type: TemplateVariableTypeEnum;
+ name: string;
+ required?: boolean;
+ defaultValue?: string | boolean;
+}
+
+enum TemplateVariableTypeEnum {
+ STRING = "String",
+ ARRAY = "Array",
+ BOOLEAN = "Boolean",
+}
+```
+
+#### Update existing layout
+
+When updating a layout, all properties are optional and the SDK will only update the ones passed.
+
+```tsx
+const layoutId: LayoutId = ""; // The unique identifier of the layout.
+
+const updatedLayout = await novu.layouts.update(layoutId, {
+ name,
+ description,
+ content,
+ variables,
+ isDefault,
+});
+```
+
+#### Set default layout
+
+When executing this, the existing default layout in the environment will be automatically set as non default and the chosen layout will be set as default. This action is non reversible.
+
+```tsx
+const layoutId: LayoutId = ""; // The unique identifier of the layout to be set as default.
+
+await novu.layouts.setDefault(layoutId);
+```
+
+#### Delete an existing layout
+
+Layouts can also be deleted. The condition to be able to delete a layout is that it is not a default layout and is not assigned in any existing workflow.
+
+```tsx
+const layoutId: LayoutId = ""; // The unique identifier of the layout to be deleted.
+
+await novu.layouts.delete(layoutId);
+```
+
+#### Get a layout
+
+Find a layout by layoutId
+
+```tsx
+const layoutId: LayoutId = ""; // The unique identifier of the layout to be found.
+
+const layout = await novu.layouts.get(layoutId);
+```
+
+#### List all layouts
+
+List paginated layouts
+
+```tsx
+const page: number = 0; // Pagination value of the page for the results. First page will be page = 0.
+const pageSize: number = 100; // Optional. Pagination value of the amount of layouts per page to return.
+const sortBy: string = "createdAt"; // Optional. Property to order the list of the layouts. So far only `createdAt` is supported.
+const orderBy: OrderDirectionEnum = -1; // Optional. Direction of the sorting by the property chosen in `sortBy`. Ascendent order is represented by 1 and descendent order is represented by -1.
+
+const layouts = await novu.layouts.list({
+ page,
+ pageSize,
+ sortBy,
+ orderBy,
+});
+```
+
+## API Reference Links
+
+- [Layout Methods](/api-reference/layouts/layout-creation/)
diff --git a/content-creation-design/notification-content-creation.mdx b/content-creation-design/notification-content-creation.mdx
new file mode 100644
index 00000000..8d7132e5
--- /dev/null
+++ b/content-creation-design/notification-content-creation.mdx
@@ -0,0 +1,206 @@
+---
+title: "Content Creation"
+description: "Explore how to create notification content"
+icon: "pen"
+---
+
+To demonstrate the extensive range of possibilities, we've crafted a subscriber profile enriched with various attributes to highlight the flexibility of our system.
+
+```typescript
+const subscriber = {
+ subscriberId: "6427e97d0136cef86a315c46",
+ firstName: "John",
+ lastName: "Doe",
+ email: "john.doe@gmail.com",
+ phone: "+98712345677",
+ avatar:
+ "https://sm.ign.com/ign_nordic/cover/a/avatar-gen/avatar-generations_prsz.jpg",
+ locale: "en-US",
+ data: {
+ plan: "basic",
+ zipcode: 14925,
+ validVehicleLicense: true,
+ },
+};
+```
+
+Let's go over examples for each communication channel:
+
+## Email
+
+There are multiple ways to craft your email notification content for maximum impact.
+
+
+ Please remember that you have the option to preview the anticipated output of
+ your input using the editor. Simply switch from the editing mode to the
+ preview mode to see the results.
+
+
+### Regular Editor
+
+Example: Send a personalized welcome email to a user by leveraging their properties.
+
+**Input:**
+
+```html
+Hello {{subscriber.firstName}} {{subscriber.lastName}}! We are excited to
+welcome you to our {{subscriber.data.plan}} plan. Look forward to receiving
+daily updates from us on your phone number: {{subscriber.phone}}. {{#if
+subscriber.data.validVehicleLicense}} Why not consider renting a car to visit
+us? {{else}} We're delighted to offer you a train ticket to come see us. {{/if}}
+Should you require any assistance, don't hesitate to reach out. Best regards,
+Your friends at Novu.
+```
+
+
+
+**Output:**
+
+```html
+Hello John Doe! We are excited to welcome you to our basic plan. Look forward to
+receiving daily updates from us on your phone number: +98712345677. Why not
+consider renting a car to visit us? Should you require any assistance, don't
+hesitate to reach out. Best regards, Your friends at Novu.
+```
+
+
+
+### Custom Code Editor
+
+**Input:**
+
+
+ ```HTML
+
+
+
+
+ Welcome to Novu!
+
+
+
+
We are thrilled to have you on board with our {{subscriber.data.plan}} plan.
+
Get ready to receive daily updates on your phone: {{subscriber.phone}}.
+
{{#if subscriber.data.validVehicleLicense}}
+ We would like to offer you renting a car to visit us.
+ {{else}}
+ We would like to buy you a train ticket to visit us.
+ {{/if}}
+
If you need any assistance or have questions, feel free to reach out.
+
+
+ ```
+
+
+
+**Output:**
+
+
+
+
+ Email Subject, Sender Name and Preheader field accepts system, payload
+ variables and handlebar helpers
+
+[Explore how to manage Email layouts.](/content-creation-design/layouts){" "}
+
+## In-App
+
+The In-App notification content editor closely resembles the Email notification editor, although with some discernible differences.
+
+
+
+You can:
+
+- Transform notifications into clickable links, redirecting to your specified URLs
+- Enjoy unlimited character freedom
+- Utilize all variables to craft dynamic and personalized messages
+- Infuse your brand's essence with customizable elements
+- Drive engagement with action buttons
+- Embed avatar images
+- Preview the expected output
+
+## SMS
+
+When crafting your SMS notification, you can tap into all the available variables and even add dynamic ones.
+
+
+
+## Chat
+
+We make it effortless to send notifications to chat providers like Slack, Microsoft Teams, and Discord. Meet your users in the tools they use daily.
+You have the ability to utilize all variables to craft dynamic and personalized messages.
+
+
+
+## Push
+
+Push channel has two fields `Push Message Title` and `Push Message Content` where user can write hard coded text content or use system, dynamic variables or handlebar helpers to customize notification content.
+
+
diff --git a/content-creation-design/translations.mdx b/content-creation-design/translations.mdx
new file mode 100644
index 00000000..bb9e9e62
--- /dev/null
+++ b/content-creation-design/translations.mdx
@@ -0,0 +1,696 @@
+---
+title: "Translations"
+description: "Translate notification content to different languages using i18n."
+icon: "language"
+---
+
+
+ The feature is only available for **Novu Cloud** users with **Business** or
+ **Enterprise** plans.
+
+
+## Introduction
+
+The Translation Management feature allows you to seamlessly adapt your workflows to different languages and automatically apply them based on your customers' locales. It enhances engagement, personalizes the user experience, and allows you to expand your market reach.
+
+To understand how Translation Management works in Novu we need to review its main pillars:
+
+- Translation Groups
+- Translation Files
+- Localized Message Content
+- Subscribers locale
+
+## Translation Groups
+
+Translation Group allows you to efficiently manage translations for diverse languages with shared business significance. For example, the Branding translation group can hold the content related to your company identity, while the Marketing translation group can exclusively house content related to your marketing campaigns. You aren't obligated to divide your translation content, you can manage it all within a single translation group.
+
+To create the Translation Group navigate to the `Translations` page from the main navigation and click on the “Add group” button.
+
+
+
+Before creating your first Translation Group you'll be prompted to specify the default locale for organization. This locale serves as a fallback option for localized messages in cases where the recipient hasn't defined a specific locale.
+
+
+
+After selecting the default language, you can provide the essential details: the group name, group identifier, and select your target languages.
+
+The group identifier is an important value that you will later use in the editor to localize your messages. This is a similar concept that we use across the app to identify the main parts.
+
+
+
+After the group is created, you will land on the group details page on which you can see the list with the empty translations for the languages you choose.
+
+
+
+## Translations
+
+Translations are JSON files that contain the content of your messages in a specific language. For example, if you created a Marketing translation group with two target languages English and German, then your translation JSON files might look like this:
+
+**English (en.json):**
+
+```json
+{
+ "welcome_message": "Welcome to our Marketing Campaign!",
+ "learn_more": "Learn More",
+ "special_offer": "Special Offer",
+ "contact_us": "Contact Us"
+}
+```
+
+**German (de.json):**
+
+```json
+{
+ "welcome_message": "Willkommen bei unserer Marketingkampagne!",
+ "learn_more": "Mehr erfahren",
+ "special_offer": "Sonderangebot",
+ "contact_us": "Kontaktieren Sie uns"
+}
+```
+
+To upload your translations click on the “Upload files” button located at the top of the Translation Group details page.
+
+
+
+Select the translation files and click “Open” button.
+
+
+
+With each uploaded translation file, you must indicate the language it references. Additionally, you can verify the correctness of the uploaded file by previewing its contents.
+
+
+
+Click on “Upload files” button. Congratulations you just uploaded the translation files and created the translation group! 🎉
+
+### Localized Message Content
+
+To localize your message content, you need to use the translation variables in the editor. The variables allow you to reference the values from the translation files. It consists of the `i18n` [handlebar](https://docs.novu.co/content-creation-design/handlebars-helpers) helper and path to the translated value.
+
+Let’s take a look at the example: `{{i18n "marketing.welcome_message"}}`.
+
+- `i18n` is the handlebar helper that tells Novu that you would like to use the Translations
+- `"marketing.welcome_message"` is the path that is composed of the Translation Group identifier `marketing` and translation JSON file key `welcome_message`
+
+
+
+
+ i18n variables can be used in `subject` and `preheader` fields of the email
+ step.
+
+## Subscribers Locale
+
+The subscribers locale defines in which language the message should be delivered to that recipient. When the locale is not specified it will fallback to the default locale set for the organization. The locale format includes ISO language plus the region, for example `de_DE` language German and region Germany.
+
+To update the locale you can use Novu API or any of our SDKs, more information can be found [here](https://docs.novu.co/subscribers/subscribers).
+
+
+| Language Name | Locale |
+|-------------------------------------------------- |--------- |
+| English (United States) | en_US |
+| Spanish (Spain) | es_ES |
+| German (Germany) | de_DE |
+| French (France) | fr_FR |
+| Chinese Simplified | zh_CN |
+| Portuguese (Portugal) | pt_PT |
+| Russian (Russia) | ru_RU |
+| Chinese Traditional | zh_TW |
+| Persian (Afghanistan) | fa_AF |
+| Turkmen (Afghanistan) | tk_AF |
+| Albanian (Albania) | sq_AL |
+| Greek (Albania) | el_AL |
+| Arabic (Algeria) | ar_DZ |
+| English (American Samoa) | en_AS |
+| Samoan (American Samoa) | sm_AS |
+| Tongan (American Samoa) | to_AS |
+| Catalan (Andorra) | ca_AD |
+| Portuguese (Angola) | pt_AO |
+| Spanish (Argentina) | es_AR |
+| English (Argentina) | en_AR |
+| Italian (Argentina) | it_AR |
+| German (Argentina) | de_AR |
+| French (Argentina) | fr_AR |
+| Guarani (Argentina) | gn_AR |
+| Armenian (Armenia) | hy_AM |
+| Dutch (Aruba) | nl_AW |
+| Spanish (Aruba) | es_AW |
+| English (Aruba) | en_AW |
+| English (Australia) | en_AU |
+| German (Austria) | de_AT |
+| Croatian (Austria) | hr_AT |
+| Hungarian (Austria) | hu_AT |
+| Slovenian (Austria) | sl_AT |
+| Azerbaijani (Azerbaijan) | az_AZ |
+| Russian (Azerbaijan) | ru_AZ |
+| Armenian (Azerbaijan) | hy_AZ |
+| Arabic (Bahrain) | ar_BH |
+| English (Bahrain) | en_BH |
+| Persian (Bahrain) | fa_BH |
+| Urdu (Bahrain) | ur_BH |
+| Bengali (Bangladesh) | bn_BD |
+| English (Bangladesh) | en_BD |
+| English (Barbados) | en_BB |
+| Belarusian (Belarus) | be_BY |
+| Russian (Belarus) | ru_BY |
+| Dutch (Belgium) | nl_BE |
+| French (Belgium) | fr_BE |
+| German (Belgium) | de_BE |
+| English (Belize) | en_BZ |
+| Spanish (Belize) | es_BZ |
+| French (Benin) | fr_BJ |
+| English (Bermuda) | en_BM |
+| Portuguese (Bermuda) | pt_BM |
+| Dzongkha (Bhutan) | dz_BT |
+| Spanish (Bolivia) | es_BO |
+| Quechua (Bolivia) | qu_BO |
+| Aymara (Bolivia) | ay_BO |
+| Dutch (Caribbean Netherlands) | nl_BQ |
+| English (Caribbean Netherlands) | en_BQ |
+| Serbian (Bosnia and Herzegovina) | sr_BA |
+| English (Botswana) | en_BW |
+| Portuguese (Brazil) | pt_BR |
+| Spanish (Brazil) | es_BR |
+| English (Brazil) | en_BR |
+| French (Brazil) | fr_BR |
+| Malay (Brunei) | ms_BN |
+| Bulgarian (Bulgaria) | bg_BG |
+| French (Burkina Faso) | fr_BF |
+| French (Burundi) | fr_BI |
+| Rundi (Burundi) | rn_BI |
+| French (Cambodia) | fr_KH |
+| English (Cambodia) | en_KH |
+| French (Cameroon) | fr_CM |
+| English (Canada) | en_CA |
+| French (Canada) | fr_CA |
+| Inuktitut (Canada) | iu_CA |
+| French (Central African Republic) | fr_CF |
+| Sango (Central African Republic) | sg_CF |
+| Lingala (Central African Republic) | ln_CF |
+| Kongo (Central African Republic) | kg_CF |
+| French (Chad) | fr_TD |
+| Spanish (Chile) | es_CL |
+| Yue Chinese (Cantonese) (China) | yue_CN |
+| Uyghur, Uighur (China) | ug_CN |
+| Zhuang, Chuang (China) | za_CN |
+| Chinese Traditional (Hong Kong) | zh_HK |
+| Yue Chinese (Cantonese) (Hong Kong) | yue_HK |
+| English (Hong Kong) | en_HK |
+| Portuguese (Macau) | pt_MO |
+| English (Christmas Island) | en_CX |
+| English (Cocos (Keeling) Islands) | en_CC |
+| Spanish (Colombia) | es_CO |
+| Arabic (Comoros) | ar_KM |
+| French (Comoros) | fr_KM |
+| French (Congo - Brazzaville) | fr_CG |
+| Kongo (Congo - Brazzaville) | kg_CG |
+| Lingala (Congo - Brazzaville) | ln_CG |
+| Spanish (Costa Rica) | es_CR |
+| English (Costa Rica) | en_CR |
+| Croatian (Croatia) | hr_HR |
+| Serbian (Croatia) | sr_HR |
+| Dutch (Curaçao) | nl_CW |
+| Greek (Cyprus) | el_CY |
+| English (Cyprus) | en_CY |
+| Czech (Czechia) | cs_CZ |
+| Slovak (Czechia) | sk_CZ |
+| French (Côte d’Ivoire) | fr_CI |
+| French (Congo - Kinshasa) | fr_CD |
+| Lingala (Congo - Kinshasa) | ln_CD |
+| Kongo (Congo - Kinshasa) | kg_CD |
+| Swahili (Congo - Kinshasa) | sw_CD |
+| Danish (Denmark) | da_DK |
+| English (Denmark) | en_DK |
+| French (Djibouti) | fr_DJ |
+| Arabic (Djibouti) | ar_DJ |
+| Somali (Djibouti) | so_DJ |
+| Afar (Djibouti) | aa_DJ |
+| Spanish (Dominican Republic) | es_DO |
+| Spanish (Ecuador) | es_EC |
+| Arabic (Egypt) | ar_EG |
+| English (Egypt) | en_EG |
+| French (Egypt) | fr_EG |
+| Spanish (El Salvador) | es_SV |
+| Spanish (Equatorial Guinea) | es_GQ |
+| French (Equatorial Guinea) | fr_GQ |
+| Arabic (Eritrea) | ar_ER |
+| Tigrinya (Eritrea) | ti_ER |
+| Estonian (Estonia) | et_EE |
+| Russian (Estonia) | ru_EE |
+| Amharic (Ethiopia) | am_ET |
+| Oromo (Ethiopia) | om_ET |
+| Tigrinya (Ethiopia) | ti_ET |
+| Somali (Ethiopia) | so_ET |
+| Fijian (Fiji) | fj_FJ |
+| Finnish (Finland) | fi_FI |
+| Swedish (Finland) | sv_FI |
+| Breton (France) | br_FR |
+| Corsican (France) | co_FR |
+| Catalan (France) | ca_FR |
+| Basque (France) | eu_FR |
+| Occitan (France) | oc_FR |
+| French (French Guiana) | fr_GF |
+| Tahitian (French Polynesia) | ty_PF |
+| French (French Southern Territories) | fr_TF |
+| French (Gabon) | fr_GA |
+| Wolof (Gambia) | wo_GM |
+| Fulah (Gambia) | ff_GM |
+| Russian (Georgia) | ru_GE |
+| Armenian (Georgia) | hy_GE |
+| Azerbaijani (Georgia) | az_GE |
+| Akan (Ghana) | ak_GH |
+| Ewe (Ghana) | ee_GH |
+| Twi (Ghana) | tw_GH |
+| Spanish (Gibraltar) | es_GI |
+| Italian (Gibraltar) | it_GI |
+| Portuguese (Gibraltar) | pt_GI |
+| Greek (Greece) | el_GR |
+| English (Greece) | en_GR |
+| French (Greece) | fr_GR |
+| Kalaallisut (Greenland) | kl_GL |
+| English (Greenland) | en_GL |
+| French (Guadeloupe) | fr_GP |
+| English (Guam) | en_GU |
+| Spanish (Guatemala) | es_GT |
+| English (Guernsey) | en_GG |
+| French (Guinea) | fr_GN |
+| Portuguese (Guinea - Bissau) | pt_GW |
+| English (Guyana) | en_GY |
+| Italian (Vatican City) | it_VA |
+| French (Vatican City) | fr_VA |
+| Spanish (Honduras) | es_HN |
+| Hungarian (Hungary) | hu_HU |
+| Icelandic (Iceland) | is_IS |
+| English (Iceland) | en_IS |
+| German (Iceland) | de_IS |
+| Danish (Iceland) | da_IS |
+| Swedish (Iceland) | sv_IS |
+| Norwegian (Iceland) | no_IS |
+| English (India) | en_IN |
+| Hindi (India) | hi_IN |
+| Bengali (India) | bn_IN |
+| Telugu (India) | te_IN |
+| Marathi (India) | mr_IN |
+| Tamil (India) | ta_IN |
+| Urdu (India) | ur_IN |
+| Kannada (India) | kn_IN |
+| Malayalam (India) | ml_IN |
+| Odia (India) | or_IN |
+| Punjabi (India) | pa_IN |
+| Assamese (India) | as_IN |
+| Bihari (India) | bh_IN |
+| Kashmiri (India) | ks_IN |
+| Nepali (India) | ne_IN |
+| Sindhi (India) | sd_IN |
+| Konkani (India) | kok_IN |
+| Sanskrit (India) | sa_IN |
+| French (India) | fr_IN |
+| Indonesian (Indonesia) | id_ID |
+| English (Indonesia) | en_ID |
+| Dutch (Indonesia) | nl_ID |
+| Javanese (Indonesia) | jv_ID |
+| Persian (Iran) | fa_IR |
+| Arabic (Iraq) | ar_IQ |
+| Armenian (Iraq) | hy_IQ |
+| English (Ireland) | en_IE |
+| Irish (Ireland) | ga_IE |
+| English (Isle of Man) | en_IM |
+| Manx (Isle of Man) | gv_IM |
+| Hebrew (Israel) | he_IL |
+| English (Israel) | en_IL |
+| Italian (Italy) | it_IT |
+| Catalan (Italy) | ca_IT |
+| Corsican (Italy) | co_IT |
+| Slovenian (Italy) | sl_IT |
+| English (Jamaica) | en_JM |
+| Japanese (Japan) | ja_JP |
+| English (Jersey) | en_JE |
+| French (Jersey) | fr_JE |
+| Arabic (Jordan) | ar_JO |
+| English (Jordan) | en_JO |
+| Kazakh (Kazakhstan) | kk_KZ |
+| Russian (Kazakhstan) | ru_KZ |
+| Swahili (Kenya) | sw_KE |
+| Arabic (Kuwait) | ar_KW |
+| English (Kuwait) | en_KW |
+| Kyrgyz (Kyrgyzstan) | ky_KG |
+| Uzbek (Kyrgyzstan) | uz_KG |
+| Russian (Kyrgyzstan) | ru_KG |
+| Lao (Laos) | lo_LA |
+| French (Laos) | fr_LA |
+| English (Laos) | en_LA |
+| Latvian (Latvia) | lv_LV |
+| Russian (Latvia) | ru_LV |
+| Lithuanian (Latvia) | lt_LV |
+| Arabic (Lebanon) | ar_LB |
+| English (Lebanon) | en_LB |
+| Armenian (Lebanon) | hy_LB |
+| Southern Sotho (Lesotho) | st_LS |
+| Zulu (Lesotho) | zu_LS |
+| Xhosa (Lesotho) | xh_LS |
+| Arabic (Libya) | ar_LY |
+| Italian (Libya) | it_LY |
+| English (Libya) | en_LY |
+| German (Liechtenstein) | de_LI |
+| Lithuanian (Lithuania) | lt_LT |
+| Russian (Lithuania) | ru_LT |
+| Polish (Lithuania) | pl_LT |
+| Luxembourgish (Luxembourg) | lb_LU |
+| German (Luxembourg) | de_LU |
+| French (Luxembourg) | fr_LU |
+| French (Madagascar) | fr_MG |
+| Malagasy (Madagascar) | mg_MG |
+| Chichewa (Malawi) | ny_MW |
+| Sena (Malawi) | swk_MW |
+| Malay (Malaysia) | ms_MY |
+| English (Malaysia) | en_MY |
+| Tamil (Malaysia) | ta_MY |
+| Telugu (Malaysia) | te_MY |
+| Malayalam (Malaysia) | ml_MY |
+| Punjabi (Malaysia) | pa_MY |
+| Thai (Malaysia) | th_MY |
+| Divehi (Maldives) | dv_MV |
+| English (Maldives) | en_MV |
+| French (Mali) | fr_ML |
+| Bambara (Mali) | bm_ML |
+| English (Malta) | en_MT |
+| Marshallese (Marshall Islands) | mh_MH |
+| English (Marshall Islands) | en_MH |
+| French (Martinique) | fr_MQ |
+| French (Mauritania) | fr_MR |
+| Wolof (Mauritania) | wo_MR |
+| English (Mauritius) | en_MU |
+| French (Mauritius) | fr_MU |
+| French (Mayotte) | fr_YT |
+| Spanish (Mexico) | es_MX |
+| French (Monaco) | fr_MC |
+| English (Monaco) | en_MC |
+| Italian (Monaco) | it_MC |
+| Russian (Mongolia) | ru_MN |
+| Serbian (Montenegro) | sr_ME |
+| Hungarian (Montenegro) | hu_ME |
+| Albanian (Montenegro) | sq_ME |
+| Croatian (Montenegro) | hr_ME |
+| Arabic (Morocco) | ar_MA |
+| French (Morocco) | fr_MA |
+| Portuguese (Mozambique) | pt_MZ |
+| Burmese (Myanmar) | my_MM |
+| English (Namibia) | en_NA |
+| Afrikaans (Namibia) | af_NA |
+| German (Namibia) | de_NA |
+| Herero (Namibia) | hz_NA |
+| Nama (Namibia) | naq_NA |
+| Nauru (Nauru) | na_NR |
+| Nepali (Nepal) | ne_NP |
+| English (Nepal) | en_NP |
+| Dutch (Netherlands) | nl_NL |
+| Frisian | fy_NL |
+| English (New Zealand) | en_NZ |
+| Spanish (Nicaragua) | es_NI |
+| English (Nicaragua) | en_NI |
+| French (Niger) | fr_NE |
+| Hausa (Niger) | ha_NE |
+| Kanuri (Niger) | kr_NE |
+| Zarma (Niger) | dje_NE |
+| English (Nigeria) | en_NG |
+| Hausa (Nigeria) | ha_NG |
+| Yoruba (Nigeria) | yo_NG |
+| Igbo (Nigeria) | ig_NG |
+| Fulah (Nigeria) | ff_NG |
+| Filipino (Northern Mariana Islands) | fil_MP |
+| Tagalog (Northern Mariana Islands) | tl_MP |
+| English (Northern Mariana Islands) | en_MP |
+| Norwegian (Norway) | no_NO |
+| Norwegian Bokmål (Norway) | nb_NO |
+| Norwegian Nynorsk (Norway) | nn_NO |
+| Northern Sami (Norway) | se_NO |
+| Finnish (Norway) | fi_NO |
+| Arabic (Oman) | ar_OM |
+| English (Oman) | en_OM |
+| Urdu (Oman) | ur_OM |
+| Urdu (Pakistan) | ur_PK |
+| English (Pakistan) | en_PK |
+| Punjabi (Pakistan) | pa_PK |
+| Sindhi (Pakistan) | sd_PK |
+| Japanese (Palau) | ja_PW |
+| Filipino (Palau) | fil_PW |
+| Spanish (Panama) | es_PA |
+| English (Panama) | en_PA |
+| Hiri Motu (Papua New Guinea) | ho_PG |
+| Spanish (Paraguay) | es_PY |
+| Guarani (Paraguay) | gn_PY |
+| Spanish (Peru) | es_PE |
+| Quechua (Peru) | qu_PE |
+| Aymara (Peru) | ay_PE |
+| Tagalog (Philippines) | tl_PH |
+| English (Philippines) | en_PH |
+| Filipino (Philippines) | fil_PH |
+| Cebuano (Philippines) | ceb_PH |
+| Tausug (Philippines) | tsg_PH |
+| Polish (Poland) | pl_PL |
+| Spanish (Puerto Rico) | es_PR |
+| Arabic (Qatar) | ar_QA |
+| Spanish (Qatar) | es_QA |
+| Korean (Korea) | ko_KR |
+| English (South Korea) | en_KR |
+| Romanian (Moldova) | ro_MD |
+| Russian (Moldova) | ru_MD |
+| Turkish (Moldova) | tr_MD |
+| Romanian (Romania) | ro_RO |
+| Hungarian (Romania) | hu_RO |
+| Tatar (Russia) | tt_RU |
+| Komi (Russia) | kv_RU |
+| Chechen (Russia) | ce_RU |
+| Bashkir (Russia) | ba_RU |
+| Avaric (Russia) | av_RU |
+| Kinyarwanda (Rwanda) | rw_RW |
+| French (Rwanda) | fr_RW |
+| Swahili (Rwanda) | sw_RW |
+| French (Réunion) | fr_RE |
+| French (St. Barthélemy) | fr_BL |
+| French (St. Martin) | fr_MF |
+| French (St. Vincent & Grenadines) | fr_VC |
+| Samoan (Samoa) | sm_WS |
+| Portuguese (São Tomé and Príncipe) | pt_ST |
+| Arabic (Saudi Arabia) | ar_SA |
+| French (Senegal) | fr_SN |
+| Wolof (Senegal) | wo_SN |
+| Serbian (Serbia) | sr_RS |
+| Hungarian (Serbia) | hu_RS |
+| English (Singapore) | en_SG |
+| Malay (Singapore) | ms_SG |
+| Tamil (Singapore) | ta_SG |
+| Chinese Simplified (Singapore) | zh_SG |
+| Dutch (Sint Maarten) | nl_SX |
+| English (Sint Maarten) | en_SX |
+| Slovak (Slovakia) | sk_SK |
+| Hungarian (Slovakia) | hu_SK |
+| Slovenian (Slovenia) | sl_SI |
+| Somali (Somalia) | so_SO |
+| Italian (Somalia) | it_SO |
+| Zulu (South Africa) | zu_ZA |
+| Xhosa (South Africa) | xh_ZA |
+| Afrikaans (South Africa) | af_ZA |
+| Pedi (South Africa) | nso_ZA |
+| English (South Africa) | en_ZA |
+| Tswana (South Africa) | tn_ZA |
+| Southern Sotho (South Africa) | st_ZA |
+| Tsonga (South Africa) | ts_ZA |
+| Swati (South Africa) | ss_ZA |
+| Venda (South Africa) | ve_ZA |
+| South Ndebele (South Africa) | nr_ZA |
+| English (South Georgia & South Sandwich Islands) | en_GS |
+| English (South Sudan) | en_SS |
+| Catalan (Spain) | ca_ES |
+| Galician (Spain) | gl_ES |
+| Basque (Spain) | eu_ES |
+| Occitan (Spain) | oc_ES |
+| Sinhala (Sri Lanka) | si_LK |
+| Tamil (Sri Lanka) | ta_LK |
+| English (Sri Lanka) | en_LK |
+| Arabic (Sudan) | ar_SD |
+| English (Sudan) | en_SD |
+| English (Suriname) | en_SR |
+| Javanese (Suriname) | jv_SR |
+| Norwegian (Svalbard & Jan Mayen) | no_SJ |
+| Russian (Svalbard & Jan Mayen) | ru_SJ |
+| Swedish (Sweden) | sv_SE |
+| Northern Sami (Sweden) | se_SE |
+| German (Switzerland) | de_CH |
+| French (Switzerland) | fr_CH |
+| Italian (Switzerland) | it_CH |
+| Romansh (Switzerland) | rm_CH |
+| Arabic (Syria) | ar_SY |
+| Armenian (Syria) | hy_SY |
+| French (Syria) | fr_SY |
+| English (Syria) | en_SY |
+| Tajik (Tajikistan) | tg_TJ |
+| Russian (Tajikistan) | ru_TJ |
+| Thai (Thailand) | th_TH |
+| English (Thailand) | en_TH |
+| Albanian (North Macedonia) | sq_MK |
+| Turkish (North Macedonia) | tr_MK |
+| Serbian (North Macedonia) | sr_MK |
+| Indonesian (Timor-Leste) | id_TL |
+| English (Timor-Leste) | en_TL |
+| French (Togo) | fr_TG |
+| Ewe (Togo) | ee_TG |
+| Hausa (Togo) | ha_TG |
+| Tongan (Tonga) | to_TO |
+| English (Trinidad and Tobago) | en_TT |
+| French (Trinidad & Tobago) | fr_TT |
+| Spanish (Trinidad & Tobago) | es_TT |
+| Arabic (Tunisia) | ar_TN |
+| French (Tunisia) | fr_TN |
+| Turkish (Turkey) | tr_TR |
+| Azerbaijani (Turkey) | az_TR |
+| Avaric (Turkey) | av_TR |
+| Turkmen (Turkmenistan) | tk_TM |
+| Russian (Turkmenistan) | ru_TM |
+| Uzbek (Turkmenistan) | uz_TM |
+| English (Tuvalu) | en_TV |
+| Samoan (Tuvalu) | sm_TV |
+| Ganda (Uganda) | lg_UG |
+| Swahili (Uganda) | sw_UG |
+| Arabic (Uganda) | ar_UG |
+| Ukrainian (Ukraine) | uk_UA |
+| Russian (Ukraine) | ru_UA |
+| Polish (Ukraine) | pl_UA |
+| Hungarian (Ukraine) | hu_UA |
+| Arabic (United Arab Emirates) | ar_AE |
+| Persian (United Arab Emirates) | fa_AE |
+| English (United Arab Emirates) | en_AE |
+| Hindi (United Arab Emirates) | hi_AE |
+| Urdu (United Arab Emirates) | ur_AE |
+| English (United Kingdom) | en_GB |
+| Welsh (United Kingdom) | cy_GB |
+| Scottish Gaelic (UK) | gd_GB |
+| Swahili (Tanzania) | sw_TZ |
+| English (Tanzania) | en_TZ |
+| Arabic (Tanzania) | ar_TZ |
+| English (U.S. Minor Outlying Islands) | en_UM |
+| English (U.S. Virgin Islands) | en_VI |
+| Spanish (United States) | es_US |
+| Hawaiian (US) | haw_US |
+| French (US) | fr_US |
+| Spanish (Uruguay) | es_UY |
+| Uzbek (Uzbekistan) | uz_UZ |
+| Russian (Uzbekistan) | ru_UZ |
+| Tajik (Uzbekistan) | tg_UZ |
+| Bislama (Vanuatu) | bi_VU |
+| Spanish (Venezuela) | es_VE |
+| Vietnamese (Vietnam) | vi_VN |
+| English (Vietnam) | en_VN |
+| French (Vietnam) | fr_VN |
+| Arabic (Western Sahara) | ar_EH |
+| Arabic (Yemen) | ar_YE |
+| Bemba (Zambia) | bem_ZM |
+| Chichewa (Zambia) | ny_ZM |
+| English (Zimbabwe) | en_ZW |
+| Shona (Zimbabwe) | sn_ZW |
+| South Ndebele (Zimbabwe) | nr_ZW |
+| North Ndebele (Zimbabwe) | nd_ZW |
+
+
+## Editor Examples
+
+### Variables
+
+It is possible to include variables in your translation strings, they should be wrapped in double curly brackets. Their value could be set as a string or as a variable - either a system variable or a variable sent through the payload.
+
+```json
+{
+ "withVariables": "Welcome {{name}} to {{organization}}"
+}
+```
+
+In the editor:
+
+```handlebars
+{{i18n "welcome.withVariables" name=subscriber.firstName organization="Novu"}}
+```
+
+### Context
+
+By providing a context you can differ translations. Eg. useful to provide gender specific translations.
+
+For example:
+
+```json
+{
+ "friend": "A friend",
+ "friend_male": "A boyfriend",
+ "friend_female": "A girlfriend"
+}
+```
+
+In the editor:
+
+```handlebars
+{{i18n "welcome.friend" context="male"}}
+```
+
+Will result in the message `A boyfriend`. Not adding any context will output `A friend`.
+
+### Plurals
+
+To distinguish between plural translation options.
+
+```json
+{
+ "keyWithCount": "{{count}} item",
+ "keyWithCount_plural": "{{count}} items"
+}
+```
+
+In the editor:
+
+```handlebars
+{{i18n "welcome.keyWithCount" count=7}}
+```
+
+Will result in `7 items`
+
+### Date formats
+
+To control date formats, do as follows:
+
+```json
+{
+ "date": "Current date: {{date, dd/MM/yyyy}}",
+ "try_date": "Current date: {{date, MM/dd/yyyy}}",
+ "intlDateTime": "On the {{date, HH:yyyy}}"
+}
+```
+
+In the editor:
+
+Date can be a string or a variable you send in the payload as `{ date: new Date() }`
+
+```handlebars
+{{i18n "welcome.date" date="12-06-2023"}}
+{{i18n "welcome.try_date" date=date}}
+{{i18n "welcome.intlDateTime" date=date}}
+```
diff --git a/content-creation-design/variables.mdx b/content-creation-design/variables.mdx
new file mode 100644
index 00000000..6f4251b4
--- /dev/null
+++ b/content-creation-design/variables.mdx
@@ -0,0 +1,104 @@
+---
+title: "Workflow Editor"
+description: "Learn how Novu's Workflow Editor empowers developers to create dynamic, personalized notification workflows using versatile system and data payload variables."
+icon: "code"
+---
+
+Novu's content creation tools allow developers to utilize a wealth of variables when designing notification workflows.
+
+As developers construct workflows using Novu, certain status facets—results of batch functions and other steps in the workflow—are auto-generated and can be utilized to control the content displayed in notification templates.
+
+These variables offer dynamic and automated properties that hold subscriber information, such as first and last name, email, phone, and avatar.
+
+For example, developers can insert a subscriber's first name into a workflow by using `{{ subscriber.firstName }}`.
+
+In addition to the subscriber's details, developers can use data payload variables that encapsulate dynamic data meant to be injected into the content of a workflow.
+
+For instance, in the code example the variables username and resetLink are data payload variables that would be adjusted based on the workflow's purpose.
+
+**The utilization of these variables allows Novu to deliver personalized notifications to the receiver, thus improving user experience and accomplishing better communication outcomes.**
+
+## System variables
+
+### Subscriber variables
+
+| Variable | Type | Description |
+| -------------- | -------- | ----------------------------------------------------------------------------- |
+| `subscriber` | `Object` | Represents someone who is intended to receive a notification. |
+| `firstName` | `String` | The first name of the subscriber. |
+| `lastName` | `String` | The last name of the subscriber. |
+| `email` | `String` | The subscriber's email address. |
+| `phone` | `String` | The phone number associated with the subscriber. |
+| `avatar` | `String` | A URL or reference to the subscriber’s avatar or profile picture. |
+| `locale` | `String` | The preferred locale or language of the subscriber. |
+| `subscriberId` | `String` | Unique identifier for the subscriber. |
+| `data` | `Object` | An additional object that can hold custom subscriber data as key-value pairs. |
+
+### Actor variables
+
+| Variable | Type | Description |
+| -------------- | -------- | ------------------------------------------------------------------------ |
+| `actor` | `Object` | A subscriber who initiates actions triggering events. |
+| `firstName` | `String` | The first name of the actor. |
+| `lastName` | `String` | The last name of the actor. |
+| `email` | `String` | The actor's email address. |
+| `phone` | `String` | The phone number associated with the actor. |
+| `avatar` | `String` | A URL or reference to the actor’s avatar or profile picture. |
+| `locale` | `String` | The preferred locale or language of the actor. |
+| `subscriberId` | `String` | Unique identifier for the actor. |
+| `data` | `Object` | An additional object that can hold custom actor data as key-value pairs. |
+
+### Step variables
+
+| Variable | Type | Description |
+| ------------- | --------- | --------------------------------------------------------------------------- |
+| `step` | `Object` | A component in the execution of a workflow. |
+| `digest` | `Boolean` | Indicates whether digest mode is active or not. |
+| `events` | `Array` | An aggregated collection of events (that are stored when digest is active). |
+| `total_count` | `Number` | Represents the total number of events in the digest. |
+
+### Brand variables
+
+| Variable | Type | Description |
+| ---------- | -------- | --------------------------------------------------------------------------- |
+| `branding` | `Object` | Enables customization of the notification's visual identity. |
+| `logo` | `String` | Insert the brand logo. |
+| `color` | `String` | Set the primary color of the notification based on the brand configuration. |
+
+### Tenant variables
+
+| Variable | Type | Description |
+| -------- | -------- | ------------------------------------------------------------------------- |
+| `tenant` | `Object` | Group of users or an organization. |
+| `name` | `String` | Insert the brand logo. |
+| `data` | `Object` | An additional object that can hold custom tenant data as key-value pairs. |
+
+## Data payload variables
+
+Data payload variables encompass the dynamic data intended to be injected into the content of a workflow. These variables are integral components of the payload object, forming part of the parameters for the trigger function.
+
+Here's a more illustrative example of how data payload variables can be employed:
+
+```jsx
+curl -L -X POST 'https://api.novu.co/v1/events/trigger' \
+-H 'Authorization: ApiKey ' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "name": "password-reset",
+ "to": {
+ "subscriberId": "6427e97d0136cef86a315c46",
+ "email": "john.doe@acme.com"
+ },
+ "payload": {
+ "username": "Jhontheman1969",
+ "resetLink": "http://sample.info/password.reset",
+ "securityEmail": "john.doe.backup@acme.com",
+ }
+}'
+```
+
+Within this code snippet, the variables `username` and `resetLink` are representative of data payload variables.
+
+Importantly, it's worth noting that the payload itself can encompass any serializable JSON object.
+
+Avoid use of system reserved variables as normal variables. For example don't use `email` as variable name because `{{subscriber.email}}` is a system reserved variable.
diff --git a/development.mdx b/development.mdx
deleted file mode 100644
index 87830089..00000000
--- a/development.mdx
+++ /dev/null
@@ -1,98 +0,0 @@
----
-title: 'Development'
-description: 'Learn how to preview changes locally'
----
-
-
- **Prerequisite** You should have installed Node.js (version 18.10.0 or
- higher).
-
-
-Step 1. Install Mintlify on your OS:
-
-
-
-```bash npm
-npm i -g mintlify
-```
-
-```bash yarn
-yarn global add mintlify
-```
-
-
-
-Step 2. Go to the docs are located (where you can find `mint.json`) and run the following command:
-
-```bash
-mintlify dev
-```
-
-The documentation website is now available at `http://localhost:3000`.
-
-### Custom Ports
-
-Mintlify uses port 3000 by default. You can use the `--port` flag to customize the port Mintlify runs on. For example, use this command to run in port 3333:
-
-```bash
-mintlify dev --port 3333
-```
-
-You will see an error like this if you try to run Mintlify in a port that's already taken:
-
-```md
-Error: listen EADDRINUSE: address already in use :::3000
-```
-
-## Mintlify Versions
-
-Each CLI is linked to a specific version of Mintlify. Please update the CLI if your local website looks different than production.
-
-
-
-```bash npm
-npm i -g mintlify@latest
-```
-
-```bash yarn
-yarn global upgrade mintlify
-```
-
-
-
-## Deployment
-
-
- Unlimited editors available under the [Startup
- Plan](https://mintlify.com/pricing)
-
-
-You should see the following if the deploy successfully went through:
-
-
-
-
-
-## Troubleshooting
-
-Here's how to solve some common problems when working with the CLI.
-
-
-
- Update to Node v18. Run `mintlify install` and try again.
-
-
-Go to the `C:/Users/Username/.mintlify/` directory and remove the `mint`
-folder. Then Open the Git Bash in this location and run `git clone
-https://github.com/mintlify/mint.git`.
-
-Repeat step 3.
-
-
-
- Try navigating to the root of your device and delete the ~/.mintlify folder.
- Then run `mintlify dev` again.
-
-
-
-Curious about what changed in a CLI version? [Check out the CLI changelog.](/changelog/command-line)
diff --git a/docs.json b/docs.json
new file mode 100644
index 00000000..d3e435d3
--- /dev/null
+++ b/docs.json
@@ -0,0 +1,698 @@
+{
+ "$schema": "https://mintlify.com/docs.json",
+ "theme": "mint",
+ "name": "Novu",
+ "colors": {
+ "primary": "#03a9ca",
+ "light": "#03a9ca",
+ "dark": "#03a9ca"
+ },
+ "favicon": "/favicon.png",
+ "navigation": {
+ "versions": [
+ {
+ "version": "v2.x",
+ "tabs": [
+ {
+ "tab": "Platform",
+ "groups": [
+ {
+ "group": "Get started",
+ "pages": [
+ "getting-started/how-novu-works",
+ "getting-started/quickstart"
+ ]
+ },
+ {
+ "group": "Novu for...",
+ "pages": [
+ "getting-started/novu-for/developers",
+ "getting-started/novu-for/product"
+ ]
+ },
+ {
+ "group": "Concepts",
+ "pages": [
+ "concepts/workflows",
+ "concepts/notifications",
+ "concepts/trigger",
+ "concepts/subscribers",
+ "concepts/endpoint",
+ "concepts/environments",
+ "concepts/preferences",
+ "concepts/topics",
+ "concepts/tenants",
+ "concepts/integrations"
+ ]
+ },
+ {
+ "group": "Build workflows",
+ "pages": [
+ "workflow/overview",
+ "workflow/how-to/build-a-workflow",
+ "workflow/template-editor",
+ "workflow/digest",
+ "workflow/delay",
+ "workflow/tags",
+ "workflow/step-conditions",
+ "workflow/channel-steps"
+ ]
+ },
+ {
+ "group": "Channels",
+ "pages": [
+ {
+ "group": "In-App",
+ "icon": "bell",
+ "pages": [
+ "integrations/providers/in-app/overview",
+ "integrations/providers/in-app/adding-in-app"
+ ]
+ },
+ {
+ "group": "Email",
+ "icon": "envelope",
+ "pages": [
+ "integrations/providers/email/overview",
+ "integrations/providers/email/adding-email",
+ "integrations/providers/email/writing-email-template",
+ {
+ "group": "Providers",
+ "icon": "list",
+ "pages": [
+ "integrations/providers/email/sendgrid",
+ "integrations/providers/email/amazon-ses",
+ "integrations/providers/email/postmark",
+ "integrations/providers/email/resend",
+ "integrations/providers/email/sendinblue",
+ "integrations/providers/email/mailersend",
+ "integrations/providers/email/mailgun",
+ "integrations/providers/email/mailjet",
+ "integrations/providers/email/infobip",
+ "integrations/providers/email/mailtrap",
+ "integrations/providers/email/mandrill",
+ "integrations/providers/email/netcore",
+ "integrations/providers/email/outlook365",
+ "integrations/providers/email/braze",
+ "integrations/providers/email/sparkpost",
+ "integrations/providers/email/plunk",
+ "integrations/providers/email/custom-smtp"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "SMS",
+ "icon": "message-sms",
+ "pages": [
+ "integrations/providers/sms/overview",
+ "integrations/providers/sms/adding-sms",
+ {
+ "group": "Providers",
+ "icon": "list",
+ "pages": [
+ "integrations/providers/sms/twilio",
+ "integrations/providers/sms/sms77",
+ "integrations/providers/sms/africas-talking",
+ "integrations/providers/sms/azure",
+ "integrations/providers/sms/bulk-sms",
+ "integrations/providers/sms/messagebird",
+ "integrations/providers/sms/simpletexting",
+ "integrations/providers/sms/infobip",
+ "integrations/providers/sms/sendchamp",
+ "integrations/providers/sms/aws-sns",
+ "integrations/providers/sms/nexmo",
+ "integrations/providers/sms/plivo",
+ "integrations/providers/sms/telnyx",
+ "integrations/providers/sms/termii",
+ "integrations/providers/sms/firetext",
+ "integrations/providers/sms/gupshup",
+ "integrations/providers/sms/clickatell"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Push",
+ "icon": "mobile",
+
+ "pages": [
+ "integrations/providers/push/overview",
+ "integrations/providers/push/adding-push",
+ {
+ "group": "Providers",
+ "icon": "list",
+ "pages": [
+ "integrations/providers/push/fcm",
+ "integrations/providers/push/apns",
+ "integrations/providers/push/expo-push",
+ "integrations/providers/push/onesignal",
+ "integrations/providers/push/pushpad",
+ "integrations/providers/push/push-webhook",
+ "integrations/providers/push/pusher-beams"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Chat",
+ "icon": "comments",
+ "pages": [
+ "integrations/providers/chat/overview",
+ "integrations/providers/chat/adding-chat",
+ {
+ "group": "Providers",
+ "icon": "list",
+ "pages": [
+ "integrations/providers/chat/discord",
+ "integrations/providers/chat/slack",
+ "integrations/providers/chat/ms-teams",
+ "integrations/providers/chat/zulip",
+ "integrations/providers/chat/whats-app"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "group": "SDKs",
+ "pages": [
+ "sdks/overview",
+ {
+ "group": "Backend",
+ "pages": [
+ "sdks/nodejs",
+ "sdks/php",
+ "sdks/laravel",
+ "sdks/python",
+ "sdks/kotlin",
+ "sdks/java",
+ "sdks/ruby",
+ "sdks/go",
+ "sdks/dotnet"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Account",
+ "pages": [
+ "account/authentication",
+ "account/sso",
+ "account/billing"
+ ]
+ },
+ {
+ "group": "Additional Resources",
+ "pages": [
+ {
+ "group": "Recipes",
+ "icon": "notebook",
+ "pages": [
+ "recipes/workflows/introduction",
+ "recipes/workflows/otp",
+ "recipes/workflows/password-reset",
+ "recipes/workflows/recent-login",
+ "recipes/workflows/invoice-receipt",
+ "recipes/workflows/shipping-confirmation",
+ "recipes/workflows/feedback-reviews",
+ "recipes/workflows/multi-workflow-digest",
+ "recipes/workflows/translations",
+ "integrations/segment"
+ ]
+ },
+ "additional-resources/security",
+ "additional-resources/idempotency",
+ "additional-resources/data-migrations",
+ "additional-resources/glossary"
+ ]
+ }
+ ]
+ },
+ {
+ "tab": "",
+ "groups": [
+ {
+ "group": "Get Started",
+ "pages": [
+ "inbox/overview"
+ ]
+ },
+ {
+ "group": "React",
+ "icon": "react",
+ "pages": [
+ "inbox/react/get-started",
+ "inbox/react/styling",
+ "inbox/react/multiple-tabs",
+ {
+ "group": "Components",
+ "pages": [
+ "inbox/react/components/overview",
+ "inbox/react/components/inbox",
+ "inbox/react/components/bell",
+ "inbox/react/components/notifications",
+ "inbox/react/components/preferences",
+ "inbox/react/components/inbox-content"
+ ]
+ },
+ {
+ "group": "Hooks",
+ "pages": [
+ "inbox/react/hooks/overview",
+ "inbox/react/hooks/novu-provider",
+ "inbox/react/hooks/use-notifications",
+ "inbox/react/hooks/use-preferences",
+ "inbox/react/hooks/use-counts",
+ "inbox/react/hooks/use-novu"
+ ]
+ },
+ "inbox/react/localization",
+ "inbox/react/production",
+ "inbox/react/migration-guide"
+ ]
+ },
+ {
+ "group": "Headless",
+ "icon": "js",
+ "pages": [
+ "inbox/headless/get-started",
+ "inbox/headless/api-reference"
+ ]
+ },
+ {
+ "group": "React Native",
+ "icon": "mobile",
+ "pages": [
+ "inbox/react-native/quickstart",
+ {
+ "group": "Hooks",
+ "pages": [
+ "inbox/react-native/hooks/novu-provider",
+ "inbox/react-native/hooks/use-notifications",
+ "inbox/react-native/hooks/use-preferences",
+ "inbox/react-native/hooks/use-counts",
+ "inbox/react-native/hooks/use-novu"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "tab": "Framework",
+ "groups": [
+ {
+ "group": "Get started",
+ "pages": [
+ "framework/overview",
+ {
+ "group": "Quickstart",
+ "pages": [
+ "framework/quickstart/nextjs",
+ "framework/quickstart/express",
+ "framework/quickstart/remix",
+ "framework/quickstart/nestjs",
+ "framework/quickstart/svelte",
+ "framework/quickstart/nuxt",
+ "framework/quickstart/h3",
+ "framework/quickstart/lambda"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Concepts",
+ "pages": [
+ "framework/studio",
+ "framework/endpoint",
+ "framework/controls"
+ ]
+ },
+ {
+ "group": "Build workflows",
+ "pages": [
+ "framework/overview",
+ "framework/tags",
+ "framework/digest",
+ "framework/delay",
+ "framework/custom",
+ "framework/in-app-channel",
+ "framework/email-channel",
+ "framework/push-channel",
+ "framework/sms-channel",
+ "framework/chat-channel"
+ ]
+ },
+ {
+ "group": "Deploy",
+ "pages": [
+ "framework/deployment/syncing",
+ "framework/deployment/production",
+ {
+ "group": "Platforms",
+ "pages": [
+ "framework/deployment/cli",
+ "framework/deployment/actions"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "SDK Reference",
+ "pages": [
+ "framework/typescript/overview",
+ "framework/typescript/client",
+ "framework/typescript/workflow",
+ {
+ "group": "Steps",
+ "pages": [
+ "framework/typescript/steps/overview",
+ "framework/typescript/steps/email",
+ "framework/typescript/steps/inApp",
+ "framework/typescript/steps/sms",
+ "framework/typescript/steps/push",
+ "framework/typescript/steps/chat",
+ "framework/typescript/steps/digest",
+ "framework/typescript/steps/delay",
+ "framework/typescript/steps/custom"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Integrations",
+ "pages": [
+ {
+ "group": "Content",
+ "pages": [
+ "framework/content/react-email",
+ "framework/content/vue-email",
+ "framework/content/svelte-email",
+ "framework/content/remix-react-email"
+ ]
+ },
+ {
+ "group": "Schema",
+ "pages": [
+ "framework/schema/zod",
+ "framework/schema/json-schema"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "tab": "Community",
+ "groups": [
+ {
+ "group": "Get Started",
+ "pages": [
+ "community/overview",
+ "community/project-differences"
+ ]
+ },
+ {
+ "group": "Self Hosting Novu",
+ "pages": [
+ "community/self-hosting-novu/overview",
+ "community/self-hosting-novu/deploy-with-docker",
+ "community/self-hosting-novu/telemetry"
+ ]
+ },
+ {
+ "group": "Community",
+ "pages": [
+ "community/code-of-conduct",
+ "community/get-involved",
+ "community/changelog",
+ "community/roadmap",
+ {
+ "group": "Machine setup",
+ "pages": [
+ "community/machine-setup",
+ "community/windows-machine-setup"
+ ]
+ },
+ "community/run-in-local-machine",
+ "community/feature-flags",
+ "community/monorepo-structure",
+ "community/add-a-new-provider"
+ ]
+ }
+ ]
+ },
+ {
+ "tab": "API Reference",
+ "openapi": {
+ "source": "https://spec.speakeasy.com/novu/novu/json-development-with-code-samples",
+ "directory": "api-reference"
+ },
+ "groups": [
+ {
+ "group": "API Reference",
+ "pages": [
+ "api-reference/overview",
+ "api-reference/rate-limiting"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "version": "v0.x",
+ "href": "https://v0.x-docs.novu.co"
+ }
+ ]
+ },
+ "styling": {
+ "codeblocks": "system"
+ },
+ "logo": {
+ "light": "/logo/logo-light-bg.svg",
+ "dark": "/logo/logo-dark-bg.svg"
+ },
+ "api": {
+ "mdx": {
+ "server": [
+ "https://api.novu.co",
+ "https://eu.api.novu.co"
+ ],
+ "auth": {
+ "method": "key",
+ "name": "Authorization"
+ }
+ }
+ },
+ "appearance": {
+ "default": "system"
+ },
+ "background": {
+ "decoration": "windows",
+ "color": {
+ "light": "#ffffff",
+ "dark": "#000000"
+ }
+ },
+ "navbar": {
+ "links": [
+ {
+ "label": "Blog",
+ "href": "https://novu.co/blog/?utm_campaign=docs_top_nav"
+ },
+ {
+ "label": "Contact us",
+ "href": "https://novu.co/contact-us-docs/?utm_campaign=docs_top_nav"
+ }
+ ],
+ "primary": {
+ "type": "button",
+ "label": "Try Novu",
+ "href": "https://dashboard-v2.novu.co/?utm_campaign=docs_top_bar_tn"
+ }
+ },
+ "footer": {
+ "socials": {
+ "twitter": "https://twitter.com/novuhq",
+ "github": "https://github.com/novuhq",
+ "linkedin": "https://www.linkedin.com/company/novuco",
+ "discord": "https://discord.gg/novu",
+ "youtube": "https://www.youtube.com/@novuhq"
+ },
+ "links": [
+ {
+ "header": "Learn",
+ "items": [
+ {
+ "label": "Blog",
+ "href": "https://novu.co/blog"
+ },
+ {
+ "label": "Changelog",
+ "href": "https://roadmap.novu.co/changelog"
+ },
+ {
+ "label": "Roadmap",
+ "href": "https://roadmap.novu.co"
+ }
+ ]
+ }
+ ]
+ },
+ "integrations": {
+ "mixpanel": {
+ "projectToken": "5823b82036a3087b7c6781180e2b2190"
+ },
+ "plausible": {
+ "domain": "docs.novu.co"
+ },
+ "intercom": {
+ "appId": "fqe0apnx"
+ }
+ },
+ "redirects": [
+ {
+ "source": "/api/client-libraries",
+ "destination": "/sdks/introduction"
+ },
+ {
+ "source": "/framework/introduction",
+ "destination": "/framework/quickstart"
+ },
+ {
+ "source": "/echo/quickstart",
+ "destination": "/getting-started/introduction"
+ },
+ {
+ "source": "/echo/:slug*",
+ "destination": "/framework/:slug*"
+ },
+ {
+ "source": "/guides/echo-guides/:slug*",
+ "destination": "/guides/framework-guides/:slug*"
+ },
+ {
+ "source": "/guides/echo-guides/echo-mjml",
+ "destination": "/guides/framework-guides/framework-mjml"
+ },
+ {
+ "source": "/guides/echo-guides/echo-nuxt-vuemail",
+ "destination": "/guides/framework-guides/framework-nuxt-vuemail"
+ },
+ {
+ "source": "/guides/echo-guides/echo-react-email",
+ "destination": "/guides/framework-guides/framework-react-email"
+ },
+ {
+ "source": "/guides/echo-guides/echo-remix",
+ "destination": "/guides/framework-guides/framework-remix"
+ },
+ {
+ "source": "/guides/integrations/segment",
+ "destination": "/integrations/segment"
+ },
+ {
+ "source": "/guides/echo-guides/echo-svelte",
+ "destination": "/guides/framework-guides/framework-svelte"
+ },
+ {
+ "source": "/guides/workflows/introduction",
+ "destination": "/recipes/workflows/introduction"
+ },
+ {
+ "source": "/guides/workflows/otp",
+ "destination": "/recipes/workflows/otp"
+ },
+ {
+ "source": "/guides/workflows/password-reset",
+ "destination": "/recipes/workflows/password-reset"
+ },
+ {
+ "source": "/guides/workflows/recent-login",
+ "destination": "/recipes/workflows/recent-login"
+ },
+ {
+ "source": "/guides/workflows/invoice-receipt",
+ "destination": "/recipes/workflows/invoice-receipt"
+ },
+ {
+ "source": "/guides/workflows/shipping-confirmation",
+ "destination": "/recipes/workflows/shipping-confirmation"
+ },
+ {
+ "source": "/guides/workflows/feedback-reviews",
+ "destination": "/recipes/workflows/feedback-reviews"
+ },
+ {
+ "source": "/channels-and-providers/push/fcm",
+ "destination": "/integrations/providers/push/fcm"
+ },
+ {
+ "source": "/self-hosting-novu/deploy-with-docker",
+ "destination": "/community/deploy-with-docker"
+ },
+ {
+ "source": "/notification-center/client/react/api-reference",
+ "destination": "/inbox/react/api-reference"
+ },
+ {
+ "source": "/subscribers/subscribers",
+ "destination": "/concepts/subscribers"
+ },
+ {
+ "source": "/quickstarts/react",
+ "destination": "/inbox/react/get-started"
+ },
+ {
+ "source": "/notification-center/client/react/customization",
+ "destination": "inbox/react/styling"
+ },
+ {
+ "source": "/channels-and-providers/push/overview",
+ "destination": "/integrations/providers/push/overview"
+ },
+ {
+ "source": "/workflows/notification-workflows",
+ "destination": "/concepts/workflows"
+ },
+ {
+ "source": "/channels-and-providers/sms/nexmo",
+ "destination": "/integrations/providers/sms/nexmo"
+ },
+ {
+ "source": "/channels-and-providers/:slug*",
+ "destination": "/integrations/providers/:slug*"
+ },
+ {
+ "source": "/notification-center/introduction",
+ "destination": "/inbox/overview"
+ },
+ {
+ "source": "/sdks/introduction",
+ "destination": "/sdks/framework/typescript/overview"
+ },
+ {
+ "source": "/subscribers/topics",
+ "destination": "/concepts/topics"
+ },
+ {
+ "source": "/platform/environments",
+ "destination": "/concepts/environments"
+ },
+ {
+ "source": "/tenants/introduction",
+ "destination": "/concepts/tenants"
+ },
+ {
+ "source": "/guides/how-to-integrate-segment-with-novu",
+ "destination": "/guides/integrations/segment"
+ },
+ {
+ "source": "/how-to/introduction",
+ "destination": "/guides/workflows/introduction"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/essentials/code.mdx b/essentials/code.mdx
deleted file mode 100644
index d2a462a7..00000000
--- a/essentials/code.mdx
+++ /dev/null
@@ -1,37 +0,0 @@
----
-title: 'Code Blocks'
-description: 'Display inline code and code blocks'
-icon: 'code'
----
-
-## Basic
-
-### Inline Code
-
-To denote a `word` or `phrase` as code, enclose it in backticks (`).
-
-```
-To denote a `word` or `phrase` as code, enclose it in backticks (`).
-```
-
-### Code Block
-
-Use [fenced code blocks](https://www.markdownguide.org/extended-syntax/#fenced-code-blocks) by enclosing code in three backticks and follow the leading ticks with the programming language of your snippet to get syntax highlighting. Optionally, you can also write the name of your code after the programming language.
-
-```java HelloWorld.java
-class HelloWorld {
- public static void main(String[] args) {
- System.out.println("Hello, World!");
- }
-}
-```
-
-````md
-```java HelloWorld.java
-class HelloWorld {
- public static void main(String[] args) {
- System.out.println("Hello, World!");
- }
-}
-```
-````
diff --git a/essentials/images.mdx b/essentials/images.mdx
deleted file mode 100644
index 4c151777..00000000
--- a/essentials/images.mdx
+++ /dev/null
@@ -1,59 +0,0 @@
----
-title: 'Images and Embeds'
-description: 'Add image, video, and other HTML elements'
-icon: 'image'
----
-
-
-
-## Image
-
-### Using Markdown
-
-The [markdown syntax](https://www.markdownguide.org/basic-syntax/#images) lets you add images using the following code
-
-```md
-
-```
-
-Note that the image file size must be less than 5MB. Otherwise, we recommend hosting on a service like [Cloudinary](https://cloudinary.com/) or [S3](https://aws.amazon.com/s3/). You can then use that URL and embed.
-
-### Using Embeds
-
-To get more customizability with images, you can also use [embeds](/writing-content/embed) to add images
-
-```html
-
-```
-
-## Embeds and HTML elements
-
-
-
-
-
-
-
-Mintlify supports [HTML tags in Markdown](https://www.markdownguide.org/basic-syntax/#html). This is helpful if you prefer HTML tags to Markdown syntax, and lets you create documentation with infinite flexibility.
-
-
-
-### iFrames
-
-Loads another HTML page within the document. Most commonly used for embedding videos.
-
-```html
-
-```
diff --git a/essentials/markdown.mdx b/essentials/markdown.mdx
deleted file mode 100644
index c8ad9c1f..00000000
--- a/essentials/markdown.mdx
+++ /dev/null
@@ -1,88 +0,0 @@
----
-title: 'Markdown Syntax'
-description: 'Text, title, and styling in standard markdown'
-icon: 'text-size'
----
-
-## Titles
-
-Best used for section headers.
-
-```md
-## Titles
-```
-
-### Subtitles
-
-Best use to subsection headers.
-
-```md
-### Subtitles
-```
-
-
-
-Each **title** and **subtitle** creates an anchor and also shows up on the table of contents on the right.
-
-
-
-## Text Formatting
-
-We support most markdown formatting. Simply add `**`, `_`, or `~` around text to format it.
-
-| Style | How to write it | Result |
-| ------------- | ----------------- | --------------- |
-| Bold | `**bold**` | **bold** |
-| Italic | `_italic_` | _italic_ |
-| Strikethrough | `~strikethrough~` | ~strikethrough~ |
-
-You can combine these. For example, write `**_bold and italic_**` to get **_bold and italic_** text.
-
-You need to use HTML to write superscript and subscript text. That is, add `` or `` around your text.
-
-| Text Size | How to write it | Result |
-| ----------- | ------------------------ | ---------------------- |
-| Superscript | `superscript` | superscript |
-| Subscript | `subscript` | subscript |
-
-## Linking to Pages
-
-You can add a link by wrapping text in `[]()`. You would write `[link to google](https://google.com)` to [link to google](https://google.com).
-
-Links to pages in your docs need to be root-relative. Basically, you should include the entire folder path. For example, `[link to text](/writing-content/text)` links to the page "Text" in our components section.
-
-Relative links like `[link to text](../text)` will open slower because we cannot optimize them as easily.
-
-## Blockquotes
-
-### Singleline
-
-To create a blockquote, add a `>` in front of a paragraph.
-
-> Dorothy followed her through many of the beautiful rooms in her castle.
-
-```md
-> Dorothy followed her through many of the beautiful rooms in her castle.
-```
-
-### Multiline
-
-> Dorothy followed her through many of the beautiful rooms in her castle.
->
-> The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood.
-
-```md
-> Dorothy followed her through many of the beautiful rooms in her castle.
->
-> The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood.
-```
-
-### LaTeX
-
-Mintlify supports [LaTeX](https://www.latex-project.org) through the Latex component.
-
-8 x (vk x H1 - H2) = (0,1)
-
-```md
-8 x (vk x H1 - H2) = (0,1)
-```
diff --git a/essentials/navigation.mdx b/essentials/navigation.mdx
deleted file mode 100644
index ca44bb64..00000000
--- a/essentials/navigation.mdx
+++ /dev/null
@@ -1,66 +0,0 @@
----
-title: 'Navigation'
-description: 'The navigation field in mint.json defines the pages that go in the navigation menu'
-icon: 'map'
----
-
-The navigation menu is the list of links on every website.
-
-You will likely update `mint.json` every time you add a new page. Pages do not show up automatically.
-
-## Navigation syntax
-
-Our navigation syntax is recursive which means you can make nested navigation groups. You don't need to include `.mdx` in page names.
-
-
-
-```json Regular Navigation
-"navigation": [
- {
- "group": "Getting Started",
- "pages": ["quickstart"]
- }
-]
-```
-
-```json Nested Navigation
-"navigation": [
- {
- "group": "Getting Started",
- "pages": [
- "quickstart",
- {
- "group": "Nested Reference Pages",
- "pages": ["nested-reference-page"]
- }
- ]
- }
-]
-```
-
-
-
-## Folders
-
-Simply put your MDX files in folders and update the paths in `mint.json`.
-
-For example, to have a page at `https://yoursite.com/your-folder/your-page` you would make a folder called `your-folder` containing an MDX file called `your-page.mdx`.
-
-
-
-You cannot use `api` for the name of a folder unless you nest it inside another folder. Mintlify uses Next.js which reserves the top-level `api` folder for internal server calls. A folder name such as `api-reference` would be accepted.
-
-
-
-```json Navigation With Folder
-"navigation": [
- {
- "group": "Group Name",
- "pages": ["your-folder/your-page"]
- }
-]
-```
-
-## Hidden Pages
-
-MDX files not included in `mint.json` will not show up in the sidebar but are accessible through the search bar and by linking directly to them.
diff --git a/essentials/settings.mdx b/essentials/settings.mdx
deleted file mode 100644
index ae6e7d6a..00000000
--- a/essentials/settings.mdx
+++ /dev/null
@@ -1,318 +0,0 @@
----
-title: 'Global Settings'
-description: 'Mintlify gives you complete control over the look and feel of your documentation using the mint.json file'
-icon: 'gear'
----
-
-Every Mintlify site needs a `mint.json` file with the core configuration settings. Learn more about the [properties](#properties) below.
-
-## Properties
-
-
-Name of your project. Used for the global title.
-
-Example: `mintlify`
-
-
-
-
- An array of groups with all the pages within that group
-
-
- The name of the group.
-
- Example: `Settings`
-
-
-
- The relative paths to the markdown files that will serve as pages.
-
- Example: `["customization", "page"]`
-
-
-
-
-
-
-
- Path to logo image or object with path to "light" and "dark" mode logo images
-
-
- Path to the logo in light mode
-
-
- Path to the logo in dark mode
-
-
- Where clicking on the logo links you to
-
-
-
-
-
- Path to the favicon image
-
-
-
- Hex color codes for your global theme
-
-
- The primary color. Used for most often for highlighted content, section
- headers, accents, in light mode
-
-
- The primary color for dark mode. Used for most often for highlighted
- content, section headers, accents, in dark mode
-
-
- The primary color for important buttons
-
-
- The color of the background in both light and dark mode
-
-
- The hex color code of the background in light mode
-
-
- The hex color code of the background in dark mode
-
-
-
-
-
-
-
- Array of `name`s and `url`s of links you want to include in the topbar
-
-
- The name of the button.
-
- Example: `Contact us`
-
-
- The url once you click on the button. Example: `https://mintlify.com/contact`
-
-
-
-
-
-
-
-
- Link shows a button. GitHub shows the repo information at the url provided including the number of GitHub stars.
-
-
- If `link`: What the button links to.
-
- If `github`: Link to the repository to load GitHub information from.
-
-
- Text inside the button. Only required if `type` is a `link`.
-
-
-
-
-
-
- Array of version names. Only use this if you want to show different versions
- of docs with a dropdown in the navigation bar.
-
-
-
- An array of the anchors, includes the `icon`, `color`, and `url`.
-
-
- The [Font Awesome](https://fontawesome.com/search?s=brands%2Cduotone) icon used to feature the anchor.
-
- Example: `comments`
-
-
- The name of the anchor label.
-
- Example: `Community`
-
-
- The start of the URL that marks what pages go in the anchor. Generally, this is the name of the folder you put your pages in.
-
-
- The hex color of the anchor icon background. Can also be a gradient if you pass an object with the properties `from` and `to` that are each a hex color.
-
-
- Used if you want to hide an anchor until the correct docs version is selected.
-
-
- Pass `true` if you want to hide the anchor until you directly link someone to docs inside it.
-
-
- One of: "brands", "duotone", "light", "sharp-solid", "solid", or "thin"
-
-
-
-
-
-
- Override the default configurations for the top-most anchor.
-
-
- The name of the top-most anchor
-
-
- Font Awesome icon.
-
-
- One of: "brands", "duotone", "light", "sharp-solid", "solid", or "thin"
-
-
-
-
-
- An array of navigational tabs.
-
-
- The name of the tab label.
-
-
- The start of the URL that marks what pages go in the tab. Generally, this
- is the name of the folder you put your pages in.
-
-
-
-
-
- Configuration for API settings. Learn more about API pages at [API Components](/api-playground/demo).
-
-
- The base url for all API endpoints. If `baseUrl` is an array, it will enable for multiple base url
- options that the user can toggle.
-
-
-
-
-
- The authentication strategy used for all API endpoints.
-
-
- The name of the authentication parameter used in the API playground.
-
- If method is `basic`, the format should be `[usernameName]:[passwordName]`
-
-
- The default value that's designed to be a prefix for the authentication input field.
-
- E.g. If an `inputPrefix` of `AuthKey` would inherit the default input result of the authentication field as `AuthKey`.
-
-
-
-
-
- Configurations for the API playground
-
-
-
- Whether the playground is showing, hidden, or only displaying the endpoint with no added user interactivity `simple`
-
- Learn more at the [playground guides](/api-playground/demo)
-
-
-
-
-
- Enabling this flag ensures that key ordering in OpenAPI pages matches the key ordering defined in the OpenAPI file.
-
- This behavior will soon be enabled by default, at which point this field will be deprecated.
-
-
-
-
-
-
- A string or an array of strings of URL(s) or relative path(s) pointing to your
- OpenAPI file.
-
- Examples:
-
- ```json Absolute
- "openapi": "https://example.com/openapi.json"
- ```
- ```json Relative
- "openapi": "/openapi.json"
- ```
- ```json Multiple
- "openapi": ["https://example.com/openapi1.json", "/openapi2.json", "/openapi3.json"]
- ```
-
-
-
-
-
- An object of social media accounts where the key:property pair represents the social media platform and the account url.
-
- Example:
- ```json
- {
- "twitter": "https://twitter.com/mintlify",
- "website": "https://mintlify.com"
- }
- ```
-
-
- One of the following values `website`, `facebook`, `twitter`, `discord`, `slack`, `github`, `linkedin`, `instagram`, `hacker-news`
-
- Example: `twitter`
-
-
- The URL to the social platform.
-
- Example: `https://twitter.com/mintlify`
-
-
-
-
-
- Configurations to enable feedback buttons
-
-
-
- Enables a button to allow users to suggest edits via pull requests
-
-
- Enables a button to allow users to raise an issue about the documentation
-
-
-
-
-
- Customize the dark mode toggle.
-
-
- Set if you always want to show light or dark mode for new users. When not
- set, we default to the same mode as the user's operating system.
-
-
- Set to true to hide the dark/light mode toggle. You can combine `isHidden` with `default` to force your docs to only use light or dark mode. For example:
-
-
- ```json Only Dark Mode
- "modeToggle": {
- "default": "dark",
- "isHidden": true
- }
- ```
-
- ```json Only Light Mode
- "modeToggle": {
- "default": "light",
- "isHidden": true
- }
- ```
-
-
-
-
-
-
-
-
- A background image to be displayed behind every page. See example with
- [Infisical](https://infisical.com/docs) and [FRPC](https://frpc.io).
-
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 00000000..4f94bbda
Binary files /dev/null and b/favicon.ico differ
diff --git a/favicon.png b/favicon.png
index f5dde707..b9dacf40 100644
Binary files a/favicon.png and b/favicon.png differ
diff --git a/framework-terminal.js b/framework-terminal.js
new file mode 100644
index 00000000..e97579c8
--- /dev/null
+++ b/framework-terminal.js
@@ -0,0 +1,2871 @@
+/**
+ * @type {HTMLElement}
+ * @description Novu Echo Terminal Code Snippet
+ *
+ * DO NOT RENAME THIS FILE, IT IS REFERENCED EXTERNALLY FROM THIS SITE
+ */
+
+var $t = Object.defineProperty;
+var kt = (o, e, n) => (e in o ? $t(o, e, { enumerable: !0, configurable: !0, writable: !0, value: n }) : (o[e] = n));
+var U = (o, e, n) => (kt(o, typeof e != "symbol" ? e + "" : e, n), n);
+(function () {
+ const e = document.createElement("link").relList;
+ if (e && e.supports && e.supports("modulepreload")) return;
+ for (const i of document.querySelectorAll('link[rel="modulepreload"]')) t(i);
+ new MutationObserver((i) => {
+ for (const l of i) if (l.type === "childList") for (const s of l.addedNodes) s.tagName === "LINK" && s.rel === "modulepreload" && t(s);
+ }).observe(document, { childList: !0, subtree: !0 });
+ function n(i) {
+ const l = {};
+ return (
+ i.integrity && (l.integrity = i.integrity),
+ i.referrerPolicy && (l.referrerPolicy = i.referrerPolicy),
+ i.crossOrigin === "use-credentials" ? (l.credentials = "include") : i.crossOrigin === "anonymous" ? (l.credentials = "omit") : (l.credentials = "same-origin"),
+ l
+ );
+ }
+ function t(i) {
+ if (i.ep) return;
+ i.ep = !0;
+ const l = n(i);
+ fetch(i.href, l);
+ }
+})();
+function O() { }
+function bt(o, e) {
+ for (const n in e) o[n] = e[n];
+ return o;
+}
+function ot(o) {
+ return o();
+}
+function ye() {
+ return Object.create(null);
+}
+function fe(o) {
+ o.forEach(ot);
+}
+function it(o) {
+ return typeof o == "function";
+}
+function B(o, e) {
+ return o != o ? e == e : o !== e || (o && typeof o == "object") || typeof o == "function";
+}
+function vt(o) {
+ return Object.keys(o).length === 0;
+}
+function lt(o, e, n, t) {
+ if (o) {
+ const i = st(o, e, n, t);
+ return o[0](i);
+ }
+}
+function st(o, e, n, t) {
+ return o[1] && t ? bt(n.ctx.slice(), o[1](t(e))) : n.ctx;
+}
+function rt(o, e, n, t) {
+ if (o[2] && t) {
+ const i = o[2](t(n));
+ if (e.dirty === void 0) return i;
+ if (typeof i == "object") {
+ const l = [],
+ s = Math.max(e.dirty.length, i.length);
+ for (let p = 0; p < s; p += 1) l[p] = e.dirty[p] | i[p];
+ return l;
+ }
+ return e.dirty | i;
+ }
+ return e.dirty;
+}
+function ct(o, e, n, t, i, l) {
+ if (i) {
+ const s = st(e, n, t, l);
+ o.p(s, i);
+ }
+}
+function ft(o) {
+ if (o.ctx.length > 32) {
+ const e = [],
+ n = o.ctx.length / 32;
+ for (let t = 0; t < n; t++) e[t] = -1;
+ return e;
+ }
+ return -1;
+}
+function E(o, e) {
+ o.appendChild(e);
+}
+function b(o, e, n) {
+ o.insertBefore(e, n || null);
+}
+function k(o) {
+ o.parentNode && o.parentNode.removeChild(o);
+}
+function K(o, e) {
+ for (let n = 0; n < o.length; n += 1) o[n] && o[n].d(e);
+}
+function x(o) {
+ return document.createElement(o);
+}
+function J(o) {
+ return document.createElementNS("http://www.w3.org/2000/svg", o);
+}
+function re(o) {
+ return document.createTextNode(o);
+}
+function W() {
+ return re(" ");
+}
+function D() {
+ return re("");
+}
+function ae(o, e, n, t) {
+ return o.addEventListener(e, n, t), () => o.removeEventListener(e, n, t);
+}
+function $(o, e, n) {
+ n == null ? o.removeAttribute(e) : o.getAttribute(e) !== n && o.setAttribute(e, n);
+}
+function yt(o) {
+ return Array.from(o.childNodes);
+}
+function ge(o, e) {
+ (e = "" + e), o.data !== e && (o.data = e);
+}
+function Z(o, e, n, t) {
+ n == null ? o.style.removeProperty(e) : o.style.setProperty(e, n, t ? "important" : "");
+}
+function z(o, e, n) {
+ o.classList.toggle(e, !!n);
+}
+function Ct(o, e, { bubbles: n = !1, cancelable: t = !1 } = {}) {
+ return new CustomEvent(o, { detail: e, bubbles: n, cancelable: t });
+}
+class oe {
+ constructor(e = !1) {
+ U(this, "is_svg", !1);
+ U(this, "e");
+ U(this, "n");
+ U(this, "t");
+ U(this, "a");
+ (this.is_svg = e), (this.e = this.n = null);
+ }
+ c(e) {
+ this.h(e);
+ }
+ m(e, n, t = null) {
+ this.e || (this.is_svg ? (this.e = J(n.nodeName)) : (this.e = x(n.nodeType === 11 ? "TEMPLATE" : n.nodeName)), (this.t = n.tagName !== "TEMPLATE" ? n : n.content), this.c(e)), this.i(t);
+ }
+ h(e) {
+ (this.e.innerHTML = e), (this.n = Array.from(this.e.nodeName === "TEMPLATE" ? this.e.content.childNodes : this.e.childNodes));
+ }
+ i(e) {
+ for (let n = 0; n < this.n.length; n += 1) b(this.t, this.n[n], e);
+ }
+ p(e) {
+ this.d(), this.h(e), this.i(this.a);
+ }
+ d() {
+ this.n.forEach(k);
+ }
+}
+function me(o, e) {
+ return new o(e);
+}
+let ce;
+function se(o) {
+ ce = o;
+}
+function at() {
+ if (!ce) throw new Error("Function called outside component initialization");
+ return ce;
+}
+function de(o) {
+ at().$$.on_destroy.push(o);
+}
+function Lt() {
+ const o = at();
+ return (e, n, { cancelable: t = !1 } = {}) => {
+ const i = o.$$.callbacks[e];
+ if (i) {
+ const l = Ct(e, n, { cancelable: t });
+ return (
+ i.slice().forEach((s) => {
+ s.call(o, l);
+ }),
+ !l.defaultPrevented
+ );
+ }
+ return !0;
+ };
+}
+function pt(o, e) {
+ const n = o.$$.callbacks[e.type];
+ n && n.slice().forEach((t) => t.call(this, e));
+}
+const dirtyComponents = [],
+ we = [];
+let ne = [];
+const Ce = [],
+ xt = Promise.resolve();
+let $e = !1;
+function At() {
+ $e || (($e = !0), xt.then(ut));
+}
+function callback(o) {
+ ne.push(o);
+}
+const _e = new Set();
+let Y = 0;
+function ut() {
+ if (Y !== 0) return;
+ const o = ce;
+ do {
+ try {
+ for (; Y < dirtyComponents.length;) {
+ const e = dirtyComponents[Y];
+ Y++, se(e), Mt(e.$$);
+ }
+ } catch (e) {
+ throw ((dirtyComponents.length = 0), (Y = 0), e);
+ }
+ for (se(null), dirtyComponents.length = 0, Y = 0; we.length;) we.pop()();
+ for (let e = 0; e < ne.length; e += 1) {
+ const n = ne[e];
+ _e.has(n) || (_e.add(n), n());
+ }
+ ne.length = 0;
+ } while (dirtyComponents.length);
+ for (; Ce.length;) Ce.pop()();
+ ($e = !1), _e.clear(), se(o);
+}
+function Mt(o) {
+ if (o.fragment !== null) {
+ o.update(), fe(o.before_update);
+ const e = o.dirty;
+ (o.dirty = [-1]), o.fragment && o.fragment.p(o.ctx, e), o.after_update.forEach(callback);
+ }
+}
+function Tt(o) {
+ const e = [],
+ n = [];
+ ne.forEach((t) => (o.indexOf(t) === -1 ? e.push(t) : n.push(t))), n.forEach((t) => t()), (ne = e);
+}
+const outroTransitionSet = new Set();
+let X;
+function H() {
+ X = { r: 0, c: [], p: X };
+}
+function S() {
+ X.r || fe(X.c), (X = X.p);
+}
+function h(o, e) {
+ o && o.i && (outroTransitionSet.delete(o), o.i(e));
+}
+function handleOutroTransition(component, duration, shouldDestroy, onComplete) {
+ if (component && component.o) {
+ if (outroTransitionSet.has(component)) return;
+ outroTransitionSet.add(component),
+ X.c.push(() => {
+ outroTransitionSet.delete(component), onComplete && (shouldDestroy && component.d(1), onComplete());
+ }),
+ component.o(duration);
+ } else onComplete && onComplete();
+}
+function I(o) {
+ return (o == null ? void 0 : o.length) !== void 0 ? o : Array.from(o);
+}
+function P(o) {
+ o && o.c();
+}
+function M(component, target, anchor) {
+ const { fragment: fragment, after_update: after_update } = component.$$;
+ fragment && fragment.m(target, anchor),
+ callback(() => {
+ const l = component.$$.on_mount.map(ot).filter(it);
+ component.$$.on_destroy ? component.$$.on_destroy.push(...l) : fe(l), (component.$$.on_mount = []);
+ }),
+ after_update.forEach(callback);
+}
+function cleanupComponents(o, detachNode) {
+ const componentState = o.$$;
+ componentState.fragment !== null && (Tt(componentState.after_update), fe(componentState.on_destroy), componentState.fragment && componentState.fragment.d(detachNode), (componentState.on_destroy = componentState.fragment = null), (componentState.ctx = []));
+}
+function markComponentAsDirty(component, dirtyIndex) {
+ component.$$.dirty[0] === -1 && (dirtyComponents.push(component), At(), component.$$.dirty.fill(0)), (component.$$.dirty[(dirtyIndex / 31) | 0] |= 1 << dirtyIndex % 31);
+}
+function renderComponent(component, options, createFragment, updateFunction, i, l, s, p = [-1]) {
+ const r = ce;
+ se(component);
+ const f = (component.$$ = {
+ fragment: null,
+ ctx: [],
+ props: l,
+ update: O,
+ not_equal: i,
+ bound: ye(),
+ on_mount: [],
+ on_destroy: [],
+ on_disconnect: [],
+ before_update: [],
+ after_update: [],
+ context: new Map(options.context || (r ? r.$$.context : [])),
+ callbacks: ye(),
+ dirty: p,
+ skip_bound: !1,
+ root: options.target || r.$$.root,
+ });
+ s && s(f.root);
+ let a = !1;
+ if (
+ ((f.ctx = createFragment
+ ? createFragment(component, options.props || {}, (c, u, ..._) => {
+ const g = _.length ? _[0] : u;
+ return f.ctx && i(f.ctx[c], (f.ctx[c] = g)) && (!f.skip_bound && f.bound[c] && f.bound[c](g), a && markComponentAsDirty(component, c)), u;
+ })
+ : []),
+ f.update(),
+ (a = !0),
+ fe(f.before_update),
+ (f.fragment = updateFunction ? updateFunction(f.ctx) : !1),
+ options.target)
+ ) {
+ if (options.hydrate) {
+ const c = yt(options.target);
+ f.fragment && f.fragment.l(c), c.forEach(k);
+ } else f.fragment && f.fragment.c();
+ options.intro && h(component.$$.fragment), M(component, options.target, options.anchor), ut();
+ }
+ se(r);
+}
+class Component {
+ constructor() {
+ U(this, "$$");
+ U(this, "$$set");
+ }
+ $destroy() {
+ cleanupComponents(this, 1), (this.$destroy = O);
+ }
+ $on(e, n) {
+ if (!it(n)) return O;
+ const t = this.$$.callbacks[e] || (this.$$.callbacks[e] = []);
+ return (
+ t.push(n),
+ () => {
+ const i = t.indexOf(n);
+ i !== -1 && t.splice(i, 1);
+ }
+ );
+ }
+ $set(e) {
+ this.$$set && !vt(e) && ((this.$$.skip_bound = !0), this.$$set(e), (this.$$.skip_bound = !1));
+ }
+}
+const Pt = "4";
+typeof window < "u" && (window.__svelte || (window.__svelte = { v: new Set() })).v.add(Pt);
+const ee = [];
+function Ot(o, e = O) {
+ let n;
+ const t = new Set();
+ function i(p) {
+ if (B(o, p) && ((o = p), n)) {
+ const r = !ee.length;
+ for (const f of t) f[1](), ee.push(f, o);
+ if (r) {
+ for (let f = 0; f < ee.length; f += 2) ee[f][0](ee[f + 1]);
+ ee.length = 0;
+ }
+ }
+ }
+ function l(p) {
+ i(p(o));
+ }
+ function s(p, r = O) {
+ const f = [p, r];
+ return (
+ t.add(f),
+ t.size === 1 && (n = e(i, l) || O),
+ p(o),
+ () => {
+ t.delete(f), t.size === 0 && n && (n(), (n = null));
+ }
+ );
+ }
+ return { set: i, update: l, subscribe: s };
+}
+const be = Ot(0);
+function Et(o) {
+ let e,
+ n,
+ t = o[0] + 1 + "",
+ i,
+ l;
+ const s = o[2].default,
+ p = lt(s, o, o[1], null);
+ return {
+ c() {
+ (e = x("div")), (n = x("span")), (i = re(t)), p && p.c(), $(n, "class", "line-number"), $(e, "class", "line");
+ },
+ m(r, f) {
+ b(r, e, f), E(e, n), E(n, i), p && p.m(e, null), (l = !0);
+ },
+ p(r, [f]) {
+ (!l || f & 1) && t !== (t = r[0] + 1 + "") && ge(i, t), p && p.p && (!l || f & 2) && ct(p, s, r, r[1], l ? rt(s, r[1], f, null) : ft(r[1]), null);
+ },
+ i(r) {
+ l || (h(p, r), (l = !0));
+ },
+ o(r) {
+ handleOutroTransition(p, r), (l = !1);
+ },
+ d(r) {
+ r && k(e), p && p.d(r);
+ },
+ };
+}
+function It(o, e, n) {
+ let { $$slots: t = {}, $$scope: i } = e,
+ { index: l = 0 } = e;
+ return (
+ (o.$$set = (s) => {
+ "index" in s && n(0, (l = s.index)), "$$scope" in s && n(1, (i = s.$$scope));
+ }),
+ [l, i, t]
+ );
+}
+class ie extends Component {
+ constructor(e) {
+ super(), renderComponent(this, e, It, Et, B, { index: 0 });
+ }
+}
+function Ht(o) {
+ let e,
+ n,
+ t = `calc(${o[1]}em * 1.35 + 50px)`,
+ i = `calc(${o[2] + 3}ch + 14px - ${o[4]}px)`,
+ l,
+ s,
+ p;
+ const r = o[6].default,
+ f = lt(r, o, o[5], null);
+ return {
+ c() {
+ (e = x("div")), (n = x("div")), f && f.c(), $(n, "class", "nv-snippet-window"), z(n, "is-pop", o[0]), $(e, "class", "nv-snippet-window-wrapper terminal-cqc4oo"), z(e, "is-noninteractable", o[3]), Z(e, "top", t), Z(e, "left", i);
+ },
+ m(a, c) {
+ b(a, e, c), E(e, n), f && f.m(n, null), (l = !0), s || ((p = ae(e, "mousemove", o[7])), (s = !0));
+ },
+ p(a, [c]) {
+ f && f.p && (!l || c & 32) && ct(f, r, a, a[5], l ? rt(r, a[5], c, null) : ft(a[5]), null),
+ (!l || c & 1) && z(n, "is-pop", a[0]),
+ (!l || c & 8) && z(e, "is-noninteractable", a[3]),
+ c & 2 && t !== (t = `calc(${a[1]}em * 1.35 + 50px)`) && Z(e, "top", t),
+ c & 20 && i !== (i = `calc(${a[2] + 3}ch + 14px - ${a[4]}px)`) && Z(e, "left", i);
+ },
+ i(a) {
+ l || (h(f, a), (l = !0));
+ },
+ o(a) {
+ handleOutroTransition(f, a), (l = !1);
+ },
+ d(a) {
+ a && k(e), f && f.d(a), (s = !1), p();
+ },
+ };
+}
+function createComponentState(o, e, n) {
+ let { $$slots: t = {}, $$scope: i } = e,
+ { pop: l = !1 } = e,
+ { line: s } = e,
+ { pos: p } = e,
+ { noninteractable: r = !1 } = e,
+ f = 0;
+ be.subscribe((c) => {
+ n(4, (f = c));
+ });
+ function a(c) {
+ pt.call(this, o, c);
+ }
+ return (
+ (o.$$set = (c) => {
+ "pop" in c && n(0, (l = c.pop)), "line" in c && n(1, (s = c.line)), "pos" in c && n(2, (p = c.pos)), "noninteractable" in c && n(3, (r = c.noninteractable)), "$$scope" in c && n(5, (i = c.$$scope));
+ }),
+ [l, s, p, r, f, i, t, a]
+ );
+}
+class G extends Component {
+ constructor(e) {
+ super(), renderComponent(this, e, createComponentState, Ht, B, { pop: 0, line: 1, pos: 2, noninteractable: 3 });
+ }
+}
+function removeHtmlTags(text) {
+ return (text == null ? void 0 : text.replaceAll(/(<\/?[^>]+>)/g, "")) ?? "";
+}
+const TERMINAL_INPUT_SPEED = 2;
+function calcTime(time) {
+ return Math.floor(time / TERMINAL_INPUT_SPEED);
+}
+function addLine(line, spaces = 0, time = 100) {
+ return { type: "add-line", line: line, spaces: spaces, time: time };
+}
+function addText(line, text, time = 65) {
+ const t = [];
+ let i = 0,
+ l = "";
+ for (const s of text.split(/(<\/?[^>]+>)/g)) {
+ if (((l += s), s === "" || s === "")) {
+ t.push({ type: "rewrite", line: line, time: 0, delete: i, text: l });
+ continue;
+ }
+ if (s === "" || s === "") {
+ (l = s), (i = 0);
+ continue;
+ }
+ if (s[0] === "<") {
+ t.push({ type: "write", line: line, time: calcTime(time), text: s });
+ continue;
+ }
+ for (const p of s.split("")) (i += 1), t.push({ type: "write", line: line, time: calcTime(time), text: p });
+ }
+ return t;
+}
+function openCompletions(o, { completion: e, completions: n, time: t = 800, text: i = "", interval: l = 120, written: s = 0 }) {
+ return [{ type: "start-completion", line: o, time: 0, completions: n, completion: e, index: 0, n: 0 }, ...addText(o, i.slice(s ?? 0), l), { type: "end-completion", line: o, time: calcTime(t), text: e.slice((i == null ? void 0 : i.length) ?? 0) }];
+}
+function processAnimationStep(animationSteps, currentStepIndex, animation, textLines) {
+ const currentStep = animationSteps[currentStepIndex];
+ return currentStep
+ ? (currentStep.type === "add-line" && (textLines.splice(currentStep.line, 0, ""), (textLines[currentStep.line] = " ".repeat(currentStep.spaces)), (animation.cursor = { line: currentStep.line, pos: removeHtmlTags(textLines[currentStep.line]).length })),
+ currentStep.type === "write" && ((textLines[currentStep.line] += currentStep.text), (animation.cursor = { line: currentStep.line, pos: removeHtmlTags(textLines[currentStep.line]).length }), animation.completionWindow && ((animation.completionWindow.pos = removeHtmlTags(textLines[currentStep.line]).length), (animation.completionWindow.written += 1))),
+ currentStep.type === "rewrite" && ((textLines[currentStep.line] = textLines[currentStep.line].slice(0, -currentStep.delete) + currentStep.text), (animation.cursor = { line: currentStep.line, pos: removeHtmlTags(textLines[currentStep.line]).length }), animation.completionWindow && (animation.completionWindow.pos = removeHtmlTags(textLines[currentStep.line]).length)),
+ currentStep.type === "start-completion" && (animation.completionWindow = { completion: currentStep.completion, completions: currentStep.completions, line: currentStep.line, pos: removeHtmlTags(textLines[currentStep.line]).length - (currentStep.n ?? 0), written: currentStep.n ?? 0, index: 0 }),
+ currentStep.type === "change-completion" && (animation.completionWindow.index = currentStep.index),
+ currentStep.type === "end-completion" && ((animation.completionWindow = void 0), (textLines[currentStep.line] += currentStep.text), (animation.cursor = { line: currentStep.line, pos: removeHtmlTags(textLines[currentStep.line]).length })),
+ currentStep.type === "callback" && currentStep.callback(),
+ { animation: animation, lines: textLines })
+ : { animation: animation, lines: textLines };
+}
+function Vt(o) {
+ let e, n;
+ return {
+ c() {
+ (e = J("svg")),
+ (n = J("path")),
+ $(
+ n,
+ "d",
+ "M14.45 4.5L9.44995 2H8.55005L1.55005 5.5L1 6.39001V10.89L1.55005 11.79L6.55005 14.29H7.44995L14.45 10.79L15 9.89001V5.39001L14.45 4.5ZM6.44995 13.14L1.94995 10.89V7.17004L6.44995 9.17004V13.14ZM6.94995 8.33997L2.29004 6.22998L8.94995 2.89001L13.62 5.22998L6.94995 8.33997ZM13.95 9.89001L7.44995 13.14V9.20996L13.95 6.20996V9.89001Z"
+ ),
+ $(n, "fill", "#75BEFF"),
+ $(e, "width", "16"),
+ $(e, "height", "16"),
+ $(e, "viewBox", "0 0 16 16"),
+ $(e, "fill", "none"),
+ $(e, "xmlns", "http://www.w3.org/2000/svg");
+ },
+ m(t, i) {
+ b(t, e, i), E(e, n);
+ },
+ p: O,
+ i: O,
+ o: O,
+ d(t) {
+ t && k(e);
+ },
+ };
+}
+class Le extends Component {
+ constructor(e) {
+ super(), renderComponent(this, e, null, Vt, B, {});
+ }
+}
+function Bt(o) {
+ let e, n;
+ return {
+ c() {
+ (e = J("svg")),
+ (n = J("path")),
+ $(
+ n,
+ "d",
+ "M13.51 4L8.51001 1H7.51001L2.51001 4L2.02002 4.85999V10.86L2.51001 11.71L7.51001 14.71H8.51001L13.51 11.71L14 10.86V4.85999L13.51 4ZM7.51001 13.5601L3.01001 10.86V5.69995L7.51001 8.15002V13.5601ZM3.27002 4.69995L8.01001 1.85999L12.75 4.69995L8.01001 7.29004L3.27002 4.69995ZM13.01 10.86L8.51001 13.5601V8.15002L13.01 5.69995V10.86Z"
+ ),
+ $(n, "fill", "#B180D7"),
+ $(e, "width", "16"),
+ $(e, "height", "16"),
+ $(e, "viewBox", "0 0 16 16"),
+ $(e, "fill", "none"),
+ $(e, "xmlns", "http://www.w3.org/2000/svg");
+ },
+ m(t, i) {
+ b(t, e, i), E(e, n);
+ },
+ p: O,
+ i: O,
+ o: O,
+ d(t) {
+ t && k(e);
+ },
+ };
+}
+class xe extends Component {
+ constructor(e) {
+ super(), renderComponent(this, e, null, Bt, B, {});
+ }
+}
+function Rt(o) {
+ let e, n, t;
+ return {
+ c() {
+ (e = J("svg")),
+ (n = J("path")),
+ (t = J("path")),
+ $(n, "fill-rule", "evenodd"),
+ $(n, "clip-rule", "evenodd"),
+ $(n, "d", "M4 6H12V7H4V6ZM12 9H4V10H12V9Z"),
+ $(n, "fill", "#C5C5C5"),
+ $(t, "fill-rule", "evenodd"),
+ $(t, "clip-rule", "evenodd"),
+ $(t, "d", "M1 4L2 3H14L15 4V12L14 13H2L1 12V4ZM2 4V12H14V4H2Z"),
+ $(t, "fill", "#C5C5C5"),
+ $(e, "width", "16"),
+ $(e, "height", "16"),
+ $(e, "viewBox", "0 0 16 16"),
+ $(e, "fill", "none"),
+ $(e, "xmlns", "http://www.w3.org/2000/svg");
+ },
+ m(i, l) {
+ b(i, e, l), E(e, n), E(e, t);
+ },
+ p: O,
+ i: O,
+ o: O,
+ d(i) {
+ i && k(e);
+ },
+ };
+}
+class Ae extends Component {
+ constructor(e) {
+ super(), renderComponent(this, e, null, Rt, B, {});
+ }
+}
+function Me(o, e, n) {
+ const t = o.slice();
+ return (t[8] = e[n]), (t[10] = n), t;
+}
+function Te(o) {
+ let e,
+ n,
+ t,
+ i,
+ l,
+ s = o[8].text.slice(0, o[0].written) + "",
+ p,
+ r = o[8].text.slice(o[0].written) + "",
+ f,
+ a,
+ c,
+ u,
+ _;
+ var g = o[8].type === "constant" ? Ae : o[8].type === "method" ? xe : Le;
+ function m(y, d) {
+ return {};
+ }
+ g && (t = me(g, m()));
+ function L(...y) {
+ return o[6](o[8], o[10], ...y);
+ }
+ return {
+ c() {
+ (e = x("div")),
+ (n = x("div")),
+ t && P(t.$$.fragment),
+ (i = W()),
+ (l = x("sp")),
+ (p = re(s)),
+ (f = re(r)),
+ (a = W()),
+ $(n, "class", "nv-completion-icon"),
+ $(e, "class", "nv-completion-item terminal-10kv6jm"),
+ z(e, "is-active", o[10] === o[0].index);
+ },
+ m(y, d) {
+ b(y, e, d), E(e, n), t && M(t, n, null), E(e, i), E(e, l), E(l, p), E(e, f), E(e, a), (c = !0), u || ((_ = ae(e, "click", L)), (u = !0));
+ },
+ p(y, d) {
+ if (((o = y), d & 16 && g !== (g = o[8].type === "constant" ? Ae : o[8].type === "method" ? xe : Le))) {
+ if (t) {
+ H();
+ const v = t;
+ handleOutroTransition(v.$$.fragment, 1, 0, () => {
+ cleanupComponents(v, 1);
+ }),
+ S();
+ }
+ g ? ((t = me(g, m())), P(t.$$.fragment), h(t.$$.fragment, 1), M(t, n, null)) : (t = null);
+ }
+ (!c || d & 17) && s !== (s = o[8].text.slice(0, o[0].written) + "") && ge(p, s), (!c || d & 17) && r !== (r = o[8].text.slice(o[0].written) + "") && ge(f, r), (!c || d & 1) && z(e, "is-active", o[10] === o[0].index);
+ },
+ i(y) {
+ c || (t && h(t.$$.fragment, y), (c = !0));
+ },
+ o(y) {
+ t && handleOutroTransition(t.$$.fragment, y), (c = !1);
+ },
+ d(y) {
+ y && k(e), t && cleanupComponents(t), (u = !1), _();
+ },
+ };
+}
+function Ne(o) {
+ let e,
+ n = o[4].at(o[0].index).description + "";
+ return {
+ c() {
+ (e = x("div")), $(e, "class", "nv-completion-description");
+ },
+ m(t, i) {
+ b(t, e, i), (e.innerHTML = n);
+ },
+ p(t, i) {
+ i & 17 && n !== (n = t[4].at(t[0].index).description + "") && (e.innerHTML = n);
+ },
+ d(t) {
+ t && k(e);
+ },
+ };
+}
+function qt(o) {
+ var a;
+ let e,
+ n,
+ t = (a = o[4].at(o[0].index)) == null ? void 0 : a.description,
+ i,
+ l,
+ s = I(o[4]),
+ p = [];
+ for (let c = 0; c < s.length; c += 1) p[c] = Te(Me(o, s, c));
+ const r = (c) =>
+ handleOutroTransition(p[c], 1, 1, () => {
+ p[c] = null;
+ });
+ let f = t && Ne(o);
+ return {
+ c() {
+ e = x("div");
+ for (let c = 0; c < p.length; c += 1) p[c].c();
+ (n = W()), f && f.c(), (i = D()), $(e, "class", "nv-completion-items terminal-10kv6jm"), z(e, "is-selectable", o[1]);
+ },
+ m(c, u) {
+ b(c, e, u);
+ for (let _ = 0; _ < p.length; _ += 1) p[_] && p[_].m(e, null);
+ b(c, n, u), f && f.m(c, u), b(c, i, u), (l = !0);
+ },
+ p(c, u) {
+ var _;
+ if (u & 51) {
+ s = I(c[4]);
+ let g;
+ for (g = 0; g < s.length; g += 1) {
+ const m = Me(c, s, g);
+ p[g] ? (p[g].p(m, u), h(p[g], 1)) : ((p[g] = Te(m)), p[g].c(), h(p[g], 1), p[g].m(e, null));
+ }
+ for (H(), g = s.length; g < p.length; g += 1) r(g);
+ S();
+ }
+ (!l || u & 2) && z(e, "is-selectable", c[1]), u & 17 && (t = (_ = c[4].at(c[0].index)) == null ? void 0 : _.description), t ? (f ? f.p(c, u) : ((f = Ne(c)), f.c(), f.m(i.parentNode, i))) : f && (f.d(1), (f = null));
+ },
+ i(c) {
+ if (!l) {
+ for (let u = 0; u < s.length; u += 1) h(p[u]);
+ l = !0;
+ }
+ },
+ o(c) {
+ p = p.filter(Boolean);
+ for (let u = 0; u < p.length; u += 1) handleOutroTransition(p[u]);
+ l = !1;
+ },
+ d(c) {
+ c && (k(e), k(n), k(i)), K(p, c), f && f.d(c);
+ },
+ };
+}
+function jt(o) {
+ let e, n;
+ return (
+ (e = new G({ props: { line: o[0].line + 1, pos: o[0].pos, noninteractable: o[2], pop: o[3], $$slots: { default: [qt] }, $$scope: { ctx: o } } })),
+ e.$on("mousemove", o[7]),
+ {
+ c() {
+ P(e.$$.fragment);
+ },
+ m(t, i) {
+ M(e, t, i), (n = !0);
+ },
+ p(t, [i]) {
+ const l = {};
+ i & 1 && (l.line = t[0].line + 1), i & 1 && (l.pos = t[0].pos), i & 4 && (l.noninteractable = t[2]), i & 8 && (l.pop = t[3]), i & 2067 && (l.$$scope = { dirty: i, ctx: t }), e.$set(l);
+ },
+ i(t) {
+ n || (h(e.$$.fragment, t), (n = !0));
+ },
+ o(t) {
+ handleOutroTransition(e.$$.fragment, t), (n = !1);
+ },
+ d(t) {
+ cleanupComponents(e, t);
+ },
+ }
+ );
+}
+function Zt(o, e, n) {
+ const t = Lt();
+ let { selectable: i = !1 } = e,
+ { noninteractable: l = !1 } = e,
+ { pop: s = !1 } = e,
+ { completions: p } = e,
+ { completionWindow: r } = e;
+ const f = (c, u, _) => {
+ i && (n(0, (r.completion = c.text), r), t("framework-trigger", c.text), n(0, (r.index = u), r));
+ };
+ function a(c) {
+ pt.call(this, o, c);
+ }
+ return (
+ (o.$$set = (c) => {
+ "selectable" in c && n(1, (i = c.selectable)),
+ "noninteractable" in c && n(2, (l = c.noninteractable)),
+ "pop" in c && n(3, (s = c.pop)),
+ "completions" in c && n(4, (p = c.completions)),
+ "completionWindow" in c && n(0, (r = c.completionWindow));
+ }),
+ [r, i, l, s, p, t, f, a]
+ );
+}
+class mt extends Component {
+ constructor(e) {
+ super(), renderComponent(this, e, Zt, jt, B, { selectable: 1, noninteractable: 2, pop: 3, completions: 4, completionWindow: 0 });
+ }
+}
+function Pe(o, e, n) {
+ const t = o.slice();
+ return (t[25] = e[n]), (t[27] = n), t;
+}
+function Oe(o, e, n) {
+ const t = o.slice();
+ return (t[25] = e[n]), (t[27] = n), t;
+}
+function CompletionPopover(o) {
+ let e,
+ n,
+ t,
+ i,
+ l,
+ s,
+ p,
+ r,
+ f = I(o[4]),
+ a = [];
+ for (let d = 0; d < f.length; d += 1) a[d] = Ee(Pe(o, f, d));
+ const c = (d) =>
+ handleOutroTransition(a[d], 1, 1, () => {
+ a[d] = null;
+ });
+ let u = o[2] === "framework-payload" && PayloadCompletionPopover(o),
+ _ = o[2] === "framework-seen" && SeenCompletionPopover(o),
+ g = o[2] === "framework-event" && EventCompletionPopover(o),
+ m = o[2] === "framework-client" && EchoCompletionPopover(o),
+ L = o[2] === "framework-step" && StepCompletionPopover(o),
+ y = o[2] === "framework-trigger" && TriggerCompletionPopover(o);
+ return {
+ c() {
+ for (let d = 0; d < a.length; d += 1) a[d].c();
+ (e = W()), u && u.c(), (n = W()), _ && _.c(), (t = W()), g && g.c(), (i = W()), m && m.c(), (l = W()), L && L.c(), (s = W()), y && y.c(), (p = D());
+ },
+ m(d, v) {
+ for (let C = 0; C < a.length; C += 1) a[C] && a[C].m(d, v);
+ b(d, e, v), u && u.m(d, v), b(d, n, v), _ && _.m(d, v), b(d, t, v), g && g.m(d, v), b(d, i, v), m && m.m(d, v), b(d, l, v), L && L.m(d, v), b(d, s, v), y && y.m(d, v), b(d, p, v), (r = !0);
+ },
+ p(d, v) {
+ if (v & 16) {
+ f = I(d[4]);
+ let C;
+ for (C = 0; C < f.length; C += 1) {
+ const le = Pe(d, f, C);
+ a[C] ? (a[C].p(le, v), h(a[C], 1)) : ((a[C] = Ee(le)), a[C].c(), h(a[C], 1), a[C].m(e.parentNode, e));
+ }
+ for (H(), C = f.length; C < a.length; C += 1) c(C);
+ S();
+ }
+ d[2] === "framework-payload"
+ ? u
+ ? (u.p(d, v), v & 4 && h(u, 1))
+ : ((u = PayloadCompletionPopover(d)), u.c(), h(u, 1), u.m(n.parentNode, n))
+ : u &&
+ (H(),
+ handleOutroTransition(u, 1, 1, () => {
+ u = null;
+ }),
+ S()),
+ d[2] === "framework-seen"
+ ? _
+ ? v & 4 && h(_, 1)
+ : ((_ = SeenCompletionPopover(d)), _.c(), h(_, 1), _.m(t.parentNode, t))
+ : _ &&
+ (H(),
+ handleOutroTransition(_, 1, 1, () => {
+ _ = null;
+ }),
+ S()),
+ d[2] === "framework-event"
+ ? g
+ ? (g.p(d, v), v & 4 && h(g, 1))
+ : ((g = EventCompletionPopover(d)), g.c(), h(g, 1), g.m(i.parentNode, i))
+ : g &&
+ (H(),
+ handleOutroTransition(g, 1, 1, () => {
+ g = null;
+ }),
+ S()),
+ d[2] === "framework-client"
+ ? m
+ ? (m.p(d, v), v & 4 && h(m, 1))
+ : ((m = EchoCompletionPopover(d)), m.c(), h(m, 1), m.m(l.parentNode, l))
+ : m &&
+ (H(),
+ handleOutroTransition(m, 1, 1, () => {
+ m = null;
+ }),
+ S()),
+ d[2] === "framework-step"
+ ? L
+ ? (L.p(d, v), v & 4 && h(L, 1))
+ : ((L = StepCompletionPopover(d)), L.c(), h(L, 1), L.m(s.parentNode, s))
+ : L &&
+ (H(),
+ handleOutroTransition(L, 1, 1, () => {
+ L = null;
+ }),
+ S()),
+ d[2] === "framework-trigger"
+ ? y
+ ? (y.p(d, v), v & 4 && h(y, 1))
+ : ((y = TriggerCompletionPopover(d)), y.c(), h(y, 1), y.m(p.parentNode, p))
+ : y &&
+ (H(),
+ handleOutroTransition(y, 1, 1, () => {
+ y = null;
+ }),
+ S());
+ },
+ i(d) {
+ if (!r) {
+ for (let v = 0; v < f.length; v += 1) h(a[v]);
+ h(u), h(_), h(g), h(m), h(L), h(y), (r = !0);
+ }
+ },
+ o(d) {
+ a = a.filter(Boolean);
+ for (let v = 0; v < a.length; v += 1) handleOutroTransition(a[v]);
+ handleOutroTransition(u), handleOutroTransition(_), handleOutroTransition(g), handleOutroTransition(m), handleOutroTransition(L), handleOutroTransition(y), (r = !1);
+ },
+ d(d) {
+ d && (k(e), k(n), k(t), k(i), k(l), k(s), k(p)), K(a, d), u && u.d(d), _ && _.d(d), g && g.d(d), m && m.d(d), L && L.d(d), y && y.d(d);
+ },
+ };
+}
+function Dt(o) {
+ let e,
+ n,
+ t,
+ i,
+ l = I(o[5]),
+ s = [];
+ for (let a = 0; a < l.length; a += 1) s[a] = Re(Oe(o, l, a));
+ const p = (a) =>
+ handleOutroTransition(s[a], 1, 1, () => {
+ s[a] = null;
+ });
+ let r = o[3].completionWindow && qe(o),
+ f = o[3].cursor && TerminalCursor(o);
+ return {
+ c() {
+ for (let a = 0; a < s.length; a += 1) s[a].c();
+ (e = W()), r && r.c(), (n = W()), f && f.c(), (t = D());
+ },
+ m(a, c) {
+ for (let u = 0; u < s.length; u += 1) s[u] && s[u].m(a, c);
+ b(a, e, c), r && r.m(a, c), b(a, n, c), f && f.m(a, c), b(a, t, c), (i = !0);
+ },
+ p(a, c) {
+ if (c & 32) {
+ l = I(a[5]);
+ let u;
+ for (u = 0; u < l.length; u += 1) {
+ const _ = Oe(a, l, u);
+ s[u] ? (s[u].p(_, c), h(s[u], 1)) : ((s[u] = Re(_)), s[u].c(), h(s[u], 1), s[u].m(e.parentNode, e));
+ }
+ for (H(), u = l.length; u < s.length; u += 1) p(u);
+ S();
+ }
+ a[3].completionWindow
+ ? r
+ ? (r.p(a, c), c & 8 && h(r, 1))
+ : ((r = qe(a)), r.c(), h(r, 1), r.m(n.parentNode, n))
+ : r &&
+ (H(),
+ handleOutroTransition(r, 1, 1, () => {
+ r = null;
+ }),
+ S()),
+ a[3].cursor ? (f ? f.p(a, c) : ((f = TerminalCursor(a)), f.c(), f.m(t.parentNode, t))) : f && (f.d(1), (f = null));
+ },
+ i(a) {
+ if (!i) {
+ for (let c = 0; c < l.length; c += 1) h(s[c]);
+ h(r), (i = !0);
+ }
+ },
+ o(a) {
+ s = s.filter(Boolean);
+ for (let c = 0; c < s.length; c += 1) handleOutroTransition(s[c]);
+ handleOutroTransition(r), (i = !1);
+ },
+ d(a) {
+ a && (k(e), k(n), k(t)), K(s, a), r && r.d(a), f && f.d(a);
+ },
+ };
+}
+function Ft(o) {
+ let e,
+ n = o[25] + "",
+ t;
+ return {
+ c() {
+ (e = new oe(!1)), (t = D()), (e.a = t);
+ },
+ m(i, l) {
+ e.m(n, i, l), b(i, t, l);
+ },
+ p(i, l) {
+ l & 16 && n !== (n = i[25] + "") && e.p(n);
+ },
+ d(i) {
+ i && (k(t), e.d());
+ },
+ };
+}
+function Ee(o) {
+ let e, n;
+ return (
+ (e = new ie({ props: { index: o[27], $$slots: { default: [Ft] }, $$scope: { ctx: o } } })),
+ {
+ c() {
+ P(e.$$.fragment);
+ },
+ m(t, i) {
+ M(e, t, i), (n = !0);
+ },
+ p(t, i) {
+ const l = {};
+ i & 536870928 && (l.$$scope = { dirty: i, ctx: t }), e.$set(l);
+ },
+ i(t) {
+ n || (h(e.$$.fragment, t), (n = !0));
+ },
+ o(t) {
+ handleOutroTransition(e.$$.fragment, t), (n = !1);
+ },
+ d(t) {
+ cleanupComponents(e, t);
+ },
+ }
+ );
+}
+function StepCompletionPopover(o) {
+ let e, n;
+ return (
+ (e = new mt({ props: { completions: o[13], completionWindow: o[7], selectable: !0, pop: !0 } })),
+ e.$on("mousemove", o[16]),
+ // Commented out to disable the completion popover from modifying the terminal.
+ // e.$on("framework-payload", o[17]),
+ {
+ c() {
+ P(e.$$.fragment);
+ },
+ m(t, i) {
+ M(e, t, i), (n = !0);
+ },
+ p(t, i) {
+ const l = {};
+ i & 128 && (l.completionWindow = t[7]), e.$set(l);
+ },
+ i(t) {
+ n || (h(e.$$.fragment, t), (n = !0));
+ },
+ o(t) {
+ handleOutroTransition(e.$$.fragment, t), (n = !1);
+ },
+ d(t) {
+ cleanupComponents(e, t);
+ },
+ }
+ );
+}
+function SeenCompletionPopover(o) {
+ let e, n;
+ return (
+ (e = new G({ props: { line: 15, pos: 33, pop: !0, noninteractable: !0, $$slots: { default: [SeenCompletionPopoverContent] }, $$scope: { ctx: o } } })),
+ {
+ c() {
+ P(e.$$.fragment);
+ },
+ m(t, i) {
+ M(e, t, i), (n = !0);
+ },
+ i(t) {
+ n || (h(e.$$.fragment, t), (n = !0));
+ },
+ o(t) {
+ handleOutroTransition(e.$$.fragment, t), (n = !1);
+ },
+ d(t) {
+ cleanupComponents(e, t);
+ },
+ }
+ );
+}
+function SeenCompletionPopoverContent(o) {
+ let e,
+ seenCompletionPopoverContent = "(property) seen: booleanFlag indicating if the notification has been seen.";
+ return {
+ c() {
+ (e = x("div")), $(e, "class", "nv-completion-item");
+ },
+ m(t, i) {
+ b(t, e, i), (e.innerHTML = seenCompletionPopoverContent);
+ },
+ p: O,
+ d(t) {
+ t && k(e);
+ },
+ };
+}
+function EventCompletionPopover(o) {
+ let e, n;
+ return (
+ (e = new G({ props: { line: 3, pos: 50, pop: !0, noninteractable: !0, $$slots: { default: [Ut] }, $$scope: { ctx: o } } })),
+ {
+ c() {
+ P(e.$$.fragment);
+ },
+ m(t, i) {
+ M(e, t, i), (n = !0);
+ },
+ p(t, i) {
+ const l = {};
+ i & 536870912 && (l.$$scope = { dirty: i, ctx: t }), e.$set(l);
+ },
+ i(t) {
+ n || (h(e.$$.fragment, t), (n = !0));
+ },
+ o(t) {
+ handleOutroTransition(e.$$.fragment, t), (n = !1);
+ },
+ d(t) {
+ cleanupComponents(e, t);
+ },
+ }
+ );
+}
+function Ut(o) {
+ let e;
+ return {
+ c() {
+ (e = x("div")), $(e, "class", "nv-completion-item");
+ },
+ m(n, t) {
+ b(n, e, t), (e.innerHTML = o[11]);
+ },
+ p: O,
+ d(n) {
+ n && k(e);
+ },
+ };
+}
+function EchoCompletionPopover(o) {
+ let e, n;
+ return (
+ (e = new G({ props: { line: 3, pos: 17, pop: !0, noninteractable: !0, $$slots: { default: [Jt] }, $$scope: { ctx: o } } })),
+ {
+ c() {
+ P(e.$$.fragment);
+ },
+ m(t, i) {
+ M(e, t, i), (n = !0);
+ },
+ p(t, i) {
+ const l = {};
+ i & 536870912 && (l.$$scope = { dirty: i, ctx: t }), e.$set(l);
+ },
+ i(t) {
+ n || (h(e.$$.fragment, t), (n = !0));
+ },
+ o(t) {
+ handleOutroTransition(e.$$.fragment, t), (n = !1);
+ },
+ d(t) {
+ cleanupComponents(e, t);
+ },
+ }
+ );
+}
+function Jt(o) {
+ let e;
+ return {
+ c() {
+ (e = x("div")), $(e, "class", "nv-completion-item");
+ },
+ m(n, t) {
+ b(n, e, t), (e.innerHTML = o[9]);
+ },
+ p: O,
+ d(n) {
+ n && k(e);
+ },
+ };
+}
+function PayloadCompletionPopover(o) {
+ let e, n;
+ return (
+ (e = new G({ props: { line: 5, pos: 37, pop: !0, noninteractable: !0, $$slots: { default: [Gt] }, $$scope: { ctx: o } } })),
+ {
+ c() {
+ P(e.$$.fragment);
+ },
+ m(t, i) {
+ M(e, t, i), (n = !0);
+ },
+ p(t, i) {
+ const l = {};
+ i & 536870912 && (l.$$scope = { dirty: i, ctx: t }), e.$set(l);
+ },
+ i(t) {
+ n || (h(e.$$.fragment, t), (n = !0));
+ },
+ o(t) {
+ handleOutroTransition(e.$$.fragment, t), (n = !1);
+ },
+ d(t) {
+ cleanupComponents(e, t);
+ },
+ }
+ );
+}
+function Gt(o) {
+ let e;
+ return {
+ c() {
+ (e = x("div")), $(e, "class", "nv-completion-item");
+ },
+ m(n, t) {
+ b(n, e, t), (e.innerHTML = o[10]);
+ },
+ p: O,
+ d(n) {
+ n && k(e);
+ },
+ };
+}
+function TriggerCompletionPopover(o) {
+ let e, n;
+ return (
+ (e = new G({ props: { line: 20, pos: 16, pop: !0, noninteractable: !0, $$slots: { default: [Qt] }, $$scope: { ctx: o } } })),
+ {
+ c() {
+ P(e.$$.fragment);
+ },
+ m(t, i) {
+ M(e, t, i), (n = !0);
+ },
+ p(t, i) {
+ const l = {};
+ i & 536871040 && (l.$$scope = { dirty: i, ctx: t }), e.$set(l);
+ },
+ i(t) {
+ n || (h(e.$$.fragment, t), (n = !0));
+ },
+ o(t) {
+ handleOutroTransition(e.$$.fragment, t), (n = !1);
+ },
+ d(t) {
+ cleanupComponents(e, t);
+ },
+ }
+ );
+}
+function Qt(o) {
+ let e,
+ n = o[12](o[7].completion) + "";
+ return {
+ c() {
+ (e = x("div")), $(e, "class", "nv-completion-item");
+ },
+ m(t, i) {
+ b(t, e, i), (e.innerHTML = n);
+ },
+ p(t, i) {
+ i & 128 && n !== (n = t[12](t[7].completion) + "") && (e.innerHTML = n);
+ },
+ d(t) {
+ t && k(e);
+ },
+ };
+}
+function Xt(o) {
+ let e,
+ n = o[25] + "",
+ t;
+ return {
+ c() {
+ (e = new oe(!1)), (t = D()), (e.a = t);
+ },
+ m(i, l) {
+ e.m(n, i, l), b(i, t, l);
+ },
+ p(i, l) {
+ l & 32 && n !== (n = i[25] + "") && e.p(n);
+ },
+ d(i) {
+ i && (k(t), e.d());
+ },
+ };
+}
+function Re(o) {
+ let e, n;
+ return (
+ (e = new ie({ props: { index: o[27], $$slots: { default: [Xt] }, $$scope: { ctx: o } } })),
+ {
+ c() {
+ P(e.$$.fragment);
+ },
+ m(t, i) {
+ M(e, t, i), (n = !0);
+ },
+ p(t, i) {
+ const l = {};
+ i & 536870944 && (l.$$scope = { dirty: i, ctx: t }), e.$set(l);
+ },
+ i(t) {
+ n || (h(e.$$.fragment, t), (n = !0));
+ },
+ o(t) {
+ handleOutroTransition(e.$$.fragment, t), (n = !1);
+ },
+ d(t) {
+ cleanupComponents(e, t);
+ },
+ }
+ );
+}
+function qe(o) {
+ let e, n;
+ return (
+ (e = new mt({ props: { completions: o[8], completionWindow: o[3].completionWindow } })),
+ {
+ c() {
+ P(e.$$.fragment);
+ },
+ m(t, i) {
+ M(e, t, i), (n = !0);
+ },
+ p(t, i) {
+ const l = {};
+ i & 256 && (l.completions = t[8]), i & 8 && (l.completionWindow = t[3].completionWindow), e.$set(l);
+ },
+ i(t) {
+ n || (h(e.$$.fragment, t), (n = !0));
+ },
+ o(t) {
+ handleOutroTransition(e.$$.fragment, t), (n = !1);
+ },
+ d(t) {
+ cleanupComponents(e, t);
+ },
+ }
+ );
+}
+function TerminalCursor(o) {
+ let e,
+ n = `calc(${o[3].cursor.line}em * 1.35 + 52px)`,
+ t = `calc(${o[3].cursor.pos + 3}ch + 14px - ${o[6]}px)`;
+ return {
+ c() {
+ (e = x("span")), $(e, "class", "nv-cursor"), Z(e, "top", n), Z(e, "left", t);
+ },
+ m(i, l) {
+ b(i, e, l);
+ },
+ p(i, l) {
+ l & 8 && n !== (n = `calc(${i[3].cursor.line}em * 1.35 + 52px)`) && Z(e, "top", n), l & 72 && t !== (t = `calc(${i[3].cursor.pos + 3}ch + 14px - ${i[6]}px)`) && Z(e, "left", t);
+ },
+ d(i) {
+ i && k(e);
+ },
+ };
+}
+function Yt(o) {
+ let e, n, t, i, l, s;
+ const p = [Dt, CompletionPopover],
+ r = [];
+ function f(a, c) {
+ return a[0] ? 0 : 1;
+ }
+ return (
+ (n = f(o)),
+ (t = r[n] = p[n](o)),
+ {
+ c() {
+ (e = x("div")), t.c();
+ },
+ m(a, c) {
+ b(a, e, c), r[n].m(e, null), o[18](e), (i = !0), l || ((s = ae(e, "mousemove", o[15])), (l = !0));
+ },
+ p(a, [c]) {
+ let u = n;
+ (n = f(a)),
+ n === u
+ ? r[n].p(a, c)
+ : (H(),
+ handleOutroTransition(r[u], 1, 1, () => {
+ r[u] = null;
+ }),
+ S(),
+ (t = r[n]),
+ t ? t.p(a, c) : ((t = r[n] = p[n](a)), t.c()),
+ h(t, 1),
+ t.m(e, null));
+ },
+ i(a) {
+ i || (h(t), (i = !0));
+ },
+ o(a) {
+ handleOutroTransition(t), (i = !1);
+ },
+ d(a) {
+ a && k(e), r[n].d(), o[18](null), (l = !1), s();
+ },
+ }
+ );
+}
+function initializeEchoNode(o, options, transitionToState) {
+ let t,
+ { animated: i = !0 } = options;
+ const frameworkCompletionPopoverContent = `(method) workflow(workflowId, async (event) => { ... }): Workflow
+The Notifications as Code workflow definition.`,
+ stepCompletionPopoverContent = `(property) payload: { postId: string }
+The payload for the event, provided during trigger.`,
+ eventCompletionPopoverContent = `(parameter) event: {
+ environment: { ... };
+ inputs: { ... };
+ payload: {
+ postId: string
+ };
+ step: Step;
+ subscriber: {
+ firstName: string;
+ ...
+ };
+}
+The event that triggered the workflow.`,
+ triggerCompletionPopoverContent = (chosenModel) => `(method) trigger: Trigger
+Trigger a notification workflow.`,
+ payloadCompletionPopoverContent = [
+ { type: "constant", text: "(property) postId: string" },
+ ];
+
+ const actionWaitTime = `\`\${number} minute\` | \`\${number} hour\` | \`\${number} day\` | \`\${number} week\``;
+ const stepCompletionDescription = (stepId, stepType, textDescription = '') => {
+ if (stepType === "channel") {
+ return `(method) ${stepId}: ChannelStep<{ body: string }, { seen: boolean; ... }>
+${textDescription}`;
+ } else if (stepType === "action") {
+ if (stepId === "digest") {
+ return `(method) digest: ActionStep<${actionWaitTime}, { events: { id: string; timestamp: string; payload: { ... } }[]; }>
+${textDescription}`;
+ } else if (stepId === 'delay') {
+ return `(method) delay: ActionStep<${actionWaitTime}, { duration: number }>
+${textDescription}`;
+ }
+ }
+ }
+ let finalStateSnippet = `import { workflow } from'@novu/framework';
+
+const commentWorkflow = workflow('comment-on-post', async (event) => {
+ const inAppResponse = await event.step.inApp('notify-user', async () => ({
+ body: renderReactComponent(event.payload.postId)
+ }));
+
+ const { events } = await event.step.digest('1 week');
+
+ await event.step.email('weekly-comments', async (inputs) => {
+ return {
+ subject: \`Weekly post comments ($\{events.length})\`,
+ body: renderReactEmail(inputs, events)
+ };
+ }, { skip: () => inAppResponse.seen });
+}, { payloadSchema: z.object({ postId: z.string() }) }
+);
+
+// Use the same familiar syntax to send a notification
+commentWorkflow.trigger({
+ to: { subscriberId: 'joe@acme.com' },
+ payload: { postId: '12345' }
+});`.trim().split(`
+`);
+ const eventCompletions = [
+ {
+ text: "environment", description: `(property) environment: { id: 'development' | 'production', ... }
+The environment the workflow is running in.` },
+ { text: "inputs" },
+ {
+ text: "payload", description: `(property) payload: { postId: string }
+The payload for the event, provided during trigger.` },
+ {
+ text: "step", description: `(method) step: { chat: ChannelStep, delay: ActionStep, ... }
+Define a step in your workflow.` },
+ {
+ text: "subscriber", description: `(property) subscriber: { firstName: string, lastName: string, subscriberId: string }
+The subscriber receiving the notification.` },
+ ];
+
+ const stepCompletions = [
+ { type: "method", text: "chat", description: stepCompletionDescription("chat", "channel", "Send a chat message.") },
+ { type: "method", text: "delay", description: stepCompletionDescription("delay", "action", "Delay the workflow for a period of time.") },
+ { type: "method", text: "digest", description: stepCompletionDescription("digest", "action", "Aggregate events for a period of time.") },
+ { type: "method", text: "email", description: stepCompletionDescription("email", "channel", "Send an email.") },
+ { type: "method", text: "inApp", description: stepCompletionDescription("inApp", "channel", "Send an in-app notification.") },
+ { type: "method", text: "push", description: stepCompletionDescription("push", "channel", "Send a push notification.") },
+ { type: "method", text: "sms", description: stepCompletionDescription("sms", "channel", "Send an SMS.") },
+ ];
+
+ const payloadCompletions = [
+ { text: "postId", description: `(property) postId: string` },
+ ];
+
+ const inAppInputCompletions = [
+ {
+ text: "body", description: `(property) body: string
+The body of the in-app notification.` },
+ ];
+
+ const emailInputCompletions = [
+ {
+ text: "body", description: `(property) body: string
+The body of the email.`},
+ {
+ text: "subject", description: `(property) subject: string
+The subject of the email.` },
+ ];
+
+ const triggerCompletions = [
+ {
+ text: "payload", description: `(property) payload: { postId: string }
+The event payload for the workflow.` },
+ {
+ text: "to", description: `(property) to: { subscriberId: string, firstName: string, lastName: string }
+The subscriber to notify.` },
+ ];
+
+ const channelResultCompletions = [
+ {
+ text: "seen", description: `(property) seen: boolean
+Flag indicating if the notification has been seen.` },
+ {
+ text: "read", description: `(property) read: boolean
+Flag indicating if the notification has been read.` },
+ ];
+
+ const subscriberCompletions = [
+ {
+ text: "firstName", description: `(property) firstName: string
+The subscriber's first name.` },
+ {
+ text: "lastName", description: `(property) lastName: string
+The subscriber's last name.` },
+ {
+ text: "subscriberId", description: `(property) subscriberId: string
+The subscriber's unique identifier.` },
+ ];
+
+ const stepOptionsCompletions = [
+ {
+ type: "method", text: 'skip', description: `(method) skip: () => boolean
+Skip the step if the condition is met.` },
+ ];
+
+ const transitions = [
+ addLine(3, 2, 100),
+ addText(3, "const inAppResponse = await event."),
+ openCompletions(3, {
+ interval: 1500,
+ completion: "step",
+ completions: eventCompletions,
+ text: "st",
+ }),
+ addText(3, "."),
+ openCompletions(3, {
+ interval: 1000,
+ completion: "inApp",
+ completions: stepCompletions,
+ text: "in",
+ }),
+ // TODO: Automatic completion of the function signature, including brackets and parameters
+ addText(3, `('notify-user', async () => ({`),
+ addText(4, ""),
+ addLine(4, 4, 500),
+ addText(4, 'b', 200),
+ openCompletions(4, {
+ interval: 500,
+ completion: "body",
+ completions: inAppInputCompletions,
+ text: "bo",
+ written: 1,
+ }),
+ addText(4, ": renderReactComponent(event."),
+ openCompletions(4, {
+ interval: 1000,
+ completion: "payload",
+ completions: eventCompletions,
+ text: "pay",
+ }),
+ addText(4, "."),
+ openCompletions(4, {
+ interval: 1500,
+ completion: "postId",
+ completions: payloadCompletions,
+ }),
+ addText(4, ")"),
+ addLine(5, 2, 500),
+ addText(5, "}));"),
+ addLine(6, 0),
+ addLine(7, 2, 500),
+ addText(7, "const { events } = await event."),
+ openCompletions(7, {
+ interval: 1500,
+ completion: "step",
+ completions: eventCompletions,
+ text: "st",
+ }),
+ addText(7, "."),
+ openCompletions(7, {
+ interval: 1500,
+ completion: "digest",
+ completions: stepCompletions,
+ text: "di",
+ }),
+ addText(7, "('1 week');"),
+ { type: "wait", time: 2000 },
+ addLine(8, 0),
+ addLine(9, 2, 500),
+ addText(9, `await event.`),
+ openCompletions(9, {
+ interval: 1500,
+ completion: "step",
+ completions: eventCompletions,
+ text: "st",
+ }),
+ addText(9, "."),
+ openCompletions(9, {
+ interval: 50,
+ completion: "email",
+ completions: stepCompletions,
+ text: "email",
+ }),
+ addText(9, `('weekly-comments', async (inputs) => {`),
+ addLine(10, 4),
+ addText(10, "return {"),
+ addLine(11, 6),
+ addText(11, "s", 200),
+ openCompletions(11, {
+ interval: 1000,
+ completion: "subject",
+ completions: emailInputCompletions,
+ text: "sub",
+ written: 1,
+ }),
+ addText(11, ": `Weekly post comments (${events."),
+ openCompletions(11, {
+ interval: 500,
+ completion: "length",
+ completions: [
+ {
+ text: "length", description: `(property) Array.length: number
+Gets or sets the length of the array. This is a number one higher than the highest index in the array.` },
+ ],
+ text: "le",
+ }),
+ addText(11, " + 1})`,"),
+ addLine(12, 6),
+ addText(12, "b", 150),
+ openCompletions(12, {
+ interval: 500,
+ completion: "body",
+ completions: emailInputCompletions,
+ text: "bo",
+ written: 1,
+ }),
+ addText(12, ": renderReactEmail(inputs, events)"),
+ addLine(13, 4),
+ addText(13, "};"),
+ addLine(14, 2, 500),
+ addText(14, "}, { s", 150),
+ openCompletions(14, {
+ interval: 500,
+ completion: "skip",
+ completions: stepOptionsCompletions,
+ text: "s",
+ written: 1,
+ }),
+ addText(14, `: () => inAppResponse.`),
+ openCompletions(14, {
+ interval: 1000,
+ completion: "seen",
+ completions: channelResultCompletions,
+ text: "se",
+ }),
+ addText(14, " });"),
+ addLine(18, 0),
+ addText(18, `// Use the same familiar syntax to send a notification`, 20),
+ addLine(19, 0),
+ addText(19, `commentWorkflow.trigger({`),
+ addLine(20, 2),
+ openCompletions(20, {
+ interval: 500,
+ completion: "to",
+ completions: triggerCompletions,
+ text: "to",
+ }),
+ addText(20, ": { "),
+ openCompletions(20, {
+ interval: 200,
+ completion: "subscriberId",
+ completions: subscriberCompletions,
+ text: "subs",
+ }),
+ addText(20, ": 'joe@acme.com' },"),
+ addLine(21, 2),
+ openCompletions(21, {
+ interval: 100,
+ completion: "payload",
+ completions: triggerCompletions,
+ text: "payload",
+ }),
+ addText(21, ": { "),
+ openCompletions(21, {
+ interval: 500,
+ completion: "postId",
+ completions: payloadCompletions,
+ }),
+ addText(21, ": '12345' }"),
+ addLine(22, 0),
+ addText(22, `});`),
+ {
+ type: "callback",
+ time: 100,
+ callback: () => {
+ currentState = "framework-client";
+ transitionToState(0, (i = !1)), transitionToState(2, (m = "framework-client")), setTimer();
+ },
+ },
+ ].flat();
+ let initialText = `import { workflow } from'@novu/framework';
+
+const commentWorkflow = workflow('comment-on-post', async (event) => {
+}, { payloadSchema: z.object({ postId: z.string() }) },
+);
+`.split(`
+`),
+ g,
+ currentState = "framework-client",
+ taskTimeout;
+ function setTimer(taskDelay = 3e3) {
+ clearTimeout(taskTimeout), (taskTimeout = setTimeout(executeStateTransition, taskDelay));
+ }
+ function executeStateTransition() {
+ currentState === "framework-client"
+ ? transitionToState(2, (currentState = "framework-event"))
+ : currentState === "framework-event"
+ ? transitionToState(2, (currentState = "framework-payload"))
+ : currentState === "framework-payload"
+ ? transitionToState(2, (currentState = "framework-step"))
+ : currentState === "framework-step"
+ ? transitionToState(2, (currentState = "framework-seen"))
+ : currentState === "framework-seen"
+ ? transitionToState(2, (currentState = "framework-trigger"))
+ : currentState === "framework-trigger" && transitionToState(2, (currentState = "framework-client")),
+ setTimer();
+ }
+ function handleStateTransition(event) {
+ const targetElement = event.target.parentElement.tagName === "SPAN" ? event.target.parentElement : event.target;
+ if (targetElement.id === "nv-node-framework-client") transitionToState(2, (currentState = "framework-client"));
+ else if (targetElement.id === "nv-node-framework-event") transitionToState(2, (currentState = "framework-event"));
+ else if (targetElement.id === "nv-node-framework-payload") transitionToState(2, (currentState = "framework-payload"));
+ else if (targetElement.id === "nv-node-framework-step") transitionToState(2, (currentState = "framework-step"));
+ else if (targetElement.id === "nv-node-framework-trigger") transitionToState(2, (currentState = "framework-trigger"));
+ else if (targetElement.id === "nv-node-framework-seen") transitionToState(2, (currentState = "framework-seen"));
+ else {
+ taskTimeout || setTimer(1e3);
+ return;
+ }
+ clearTimeout(taskTimeout), (taskTimeout = void 0);
+ }
+ setTimer(), de(() => clearTimeout(taskTimeout));
+ let animation = { timeout: void 0 };
+ function executeStateTransitionWithAnimation(A) {
+ transitionToState(3, ({ animation: animation, lines: initialText } = processAnimationStep(transitions, A, animation, initialText)), animation, transitionToState(5, initialText));
+ const nextTransition = transitions[A + 1];
+ nextTransition && transitionToState(3, (animation.timeout = setTimeout(() => executeStateTransitionWithAnimation(A + 1), nextTransition.time)), animation);
+ }
+ function ht() {
+ transitionToState(3, (animation = { timeout: void 0 })), executeStateTransitionWithAnimation(0);
+ }
+ i && ht(), de(() => clearTimeout(animation.timeout));
+ let ve = 0;
+ be.subscribe((A) => {
+ transitionToState(6, (ve = A));
+ });
+ let StepCompletionComponent = { line: 9, pos: 18, completion: "email", completions: stepCompletions, written: 0, index: 3 };
+ const _t = () => setTimer(1500),
+ gt = (A) => {
+ transitionToState(7, (StepCompletionComponent.completion = A.detail), StepCompletionComponent), transitionToState(4, (finalStateSnippet[6] = ` model: '${A.detail}',`), finalStateSnippet);
+ };
+ function wt(A) {
+ we[A ? "unshift" : "push"](() => {
+ (g = A), transitionToState(1, g);
+ });
+ }
+ return (
+ (o.$$set = (A) => {
+ "animated" in A && transitionToState(0, (i = A.animated));
+ }),
+ (o.$$.update = () => {
+ var A;
+ if (o.$$.dirty & 6 && g) {
+ const V = g.getElementsByClassName("hover");
+ for (const pe of V) pe.classList.remove("is-active");
+ if (currentState) {
+ const pe = document.getElementById("nv-node-" + currentState);
+ pe && pe.classList.add("is-active");
+ }
+ }
+ o.$$.dirty & 8 && transitionToState(8, (t = (A = animation.completionWindow) == null ? void 0 : A.completions.filter(({ text: V }) => V.startsWith(animation.completionWindow.completion.slice(0, animation.completionWindow.written)))));
+ }),
+ [i, g, currentState, animation, finalStateSnippet, initialText, ve, StepCompletionComponent, t, frameworkCompletionPopoverContent, stepCompletionPopoverContent, eventCompletionPopoverContent, triggerCompletionPopoverContent, stepCompletions, setTimer, handleStateTransition, _t, gt, wt]
+ );
+}
+class NodeSnippet extends Component {
+ constructor(e) {
+ super(), renderComponent(this, e, initializeEchoNode, Yt, B, { animated: 0 });
+ }
+}
+function Ze(o, e, n) {
+ const t = o.slice();
+ return (t[6] = e[n]), (t[8] = n), t;
+}
+function nn(o) {
+ let e,
+ n = o[6] + "",
+ t;
+ return {
+ c() {
+ (e = new oe(!1)), (t = D()), (e.a = t);
+ },
+ m(i, l) {
+ e.m(n, i, l), b(i, t, l);
+ },
+ p(i, l) {
+ l & 2 && n !== (n = i[6] + "") && e.p(n);
+ },
+ d(i) {
+ i && (k(t), e.d());
+ },
+ };
+}
+function ze(o) {
+ let e, n;
+ return (
+ (e = new ie({ props: { index: o[8], $$slots: { default: [nn] }, $$scope: { ctx: o } } })),
+ {
+ c() {
+ P(e.$$.fragment);
+ },
+ m(t, i) {
+ M(e, t, i), (n = !0);
+ },
+ p(t, i) {
+ const l = {};
+ i & 514 && (l.$$scope = { dirty: i, ctx: t }), e.$set(l);
+ },
+ i(t) {
+ n || (h(e.$$.fragment, t), (n = !0));
+ },
+ o(t) {
+ handleOutroTransition(e.$$.fragment, t), (n = !1);
+ },
+ d(t) {
+ cleanupComponents(e, t);
+ },
+ }
+ );
+}
+function on(o) {
+ let e, n, t;
+ return {
+ c() {
+ (e = x("div")), (e.textContent = ""), (n = W()), (t = x("div")), $(e, "class", "nv-completion-heading"), $(t, "class", "nv-completion-item");
+ // Remove the following timeout to render the Python popover
+ setTimeout(() => {
+ e.parentNode.style.display = "none";
+ }, 1);
+ },
+ m(i, l) {
+ b(i, e, l), b(i, n, l), b(i, t, l), (t.innerHTML = o[0]);
+ },
+ p(i, l) {
+ l & 1 && (t.innerHTML = i[0]);
+ },
+ d(i) {
+ i && (k(e), k(n), k(t));
+ },
+ };
+}
+function ln(o) {
+ let e,
+ n,
+ t,
+ i,
+ l = I(o[1]),
+ s = [];
+ for (let r = 0; r < l.length; r += 1) s[r] = ze(Ze(o, l, r));
+ const p = (r) =>
+ handleOutroTransition(s[r], 1, 1, () => {
+ s[r] = null;
+ });
+ return (
+ (t = new G({ props: { line: 17, pos: 30, $$slots: { default: [on] }, $$scope: { ctx: o } } })),
+ {
+ c() {
+ e = x("div");
+ for (let r = 0; r < s.length; r += 1) s[r].c();
+ (n = W()), P(t.$$.fragment);
+ },
+ m(r, f) {
+ b(r, e, f);
+ for (let a = 0; a < s.length; a += 1) s[a] && s[a].m(e, null);
+ E(e, n), M(t, e, null), (i = !0);
+ },
+ p(r, [f]) {
+ if (f & 2) {
+ l = I(r[1]);
+ let c;
+ for (c = 0; c < l.length; c += 1) {
+ const u = Ze(r, l, c);
+ s[c] ? (s[c].p(u, f), h(s[c], 1)) : ((s[c] = ze(u)), s[c].c(), h(s[c], 1), s[c].m(e, n));
+ }
+ for (H(), c = l.length; c < s.length; c += 1) p(c);
+ S();
+ }
+ const a = {};
+ f & 513 && (a.$$scope = { dirty: f, ctx: r }), t.$set(a);
+ },
+ i(r) {
+ if (!i) {
+ for (let f = 0; f < l.length; f += 1) h(s[f]);
+ h(t.$$.fragment, r), (i = !0);
+ }
+ },
+ o(r) {
+ s = s.filter(Boolean);
+ for (let f = 0; f < s.length; f += 1) handleOutroTransition(s[f]);
+ handleOutroTransition(t.$$.fragment, r), (i = !1);
+ },
+ d(r) {
+ r && k(e), K(s, r), cleanupComponents(t);
+ },
+ }
+ );
+}
+const De = `# Coming soon...`;
+function renderPythonSnippet(o, e, n) {
+ let t;
+ const i = `# Coming soon...`,
+ l = [];
+ for (let f = 0; f < De.length; f += 3) l.push(De.slice(f, f + 3));
+ let s = "",
+ p;
+ function r() {
+ n(0, (s = ""));
+ let f = 0;
+ function a() {
+ if (f < l.length) {
+ n(0, (s += l[f]));
+ const c = Math.random() * 200;
+ p = setTimeout(a, c);
+ }
+ f === l.length && (p = setTimeout(r, 5e3)), f++;
+ }
+ a();
+ }
+ return (
+ de(() => clearTimeout(p)),
+ r(),
+ n(
+ 1,
+ (t = i.split(`
+`))
+ ),
+ [s, t]
+ );
+}
+class PythonSnippet extends Component {
+ constructor(e) {
+ super();
+ renderComponent(this, e, renderPythonSnippet, ln, B, {});
+ }
+}
+function Fe(o, e, n) {
+ const t = o.slice();
+ return (t[6] = e[n]), t;
+}
+function Ke(o, e, n) {
+ const t = o.slice();
+ return (t[9] = e[n]), (t[11] = n), t;
+}
+function cn(o) {
+ let e,
+ n = o[9] + "",
+ t;
+ return {
+ c() {
+ (e = new oe(!1)), (t = D()), (e.a = t);
+ },
+ m(i, l) {
+ e.m(n, i, l), b(i, t, l);
+ },
+ p(i, l) {
+ l & 2 && n !== (n = i[9] + "") && e.p(n);
+ },
+ d(i) {
+ i && (k(t), e.d());
+ },
+ };
+}
+function Ue(o) {
+ let e, n;
+ return (
+ (e = new ie({ props: { index: o[11], $$slots: { default: [cn] }, $$scope: { ctx: o } } })),
+ {
+ c() {
+ P(e.$$.fragment);
+ },
+ m(t, i) {
+ M(e, t, i), (n = !0);
+ },
+ p(t, i) {
+ const l = {};
+ i & 4098 && (l.$$scope = { dirty: i, ctx: t }), e.$set(l);
+ },
+ i(t) {
+ n || (h(e.$$.fragment, t), (n = !0));
+ },
+ o(t) {
+ handleOutroTransition(e.$$.fragment, t), (n = !1);
+ },
+ d(t) {
+ cleanupComponents(e, t);
+ },
+ }
+ );
+}
+function Je(o) {
+ let e,
+ n = o[6].text + "";
+ return {
+ c() {
+ (e = x("div")), $(e, "class", "nv-completion-item");
+ },
+ m(t, i) {
+ b(t, e, i), (e.innerHTML = n);
+ },
+ p(t, i) {
+ i & 1 && n !== (n = t[6].text + "") && (e.innerHTML = n);
+ },
+ d(t) {
+ t && k(e);
+ },
+ };
+}
+function fn(o) {
+ let e,
+ n,
+ t,
+ i = I(o[0]),
+ l = [];
+ for (let s = 0; s < i.length; s += 1) l[s] = Je(Fe(o, i, s));
+ return {
+ c() {
+ (e = x("div")), (e.textContent = "Auto-Pagination"), (n = W());
+ for (let s = 0; s < l.length; s += 1) l[s].c();
+ (t = D()), $(e, "class", "nv-completion-heading");
+ // Remove the following timeout to render the Go popover
+ setTimeout(() => {
+ e.parentNode.style.display = "none";
+ }, 1);
+ },
+ m(s, p) {
+ b(s, e, p), b(s, n, p);
+ for (let r = 0; r < l.length; r += 1) l[r] && l[r].m(s, p);
+ b(s, t, p);
+ },
+ p(s, p) {
+ if (p & 1) {
+ i = I(s[0]);
+ let r;
+ for (r = 0; r < i.length; r += 1) {
+ const f = Fe(s, i, r);
+ l[r] ? l[r].p(f, p) : ((l[r] = Je(f)), l[r].c(), l[r].m(t.parentNode, t));
+ }
+ for (; r < l.length; r += 1) l[r].d(1);
+ l.length = i.length;
+ }
+ },
+ d(s) {
+ s && (k(e), k(n), k(t)), K(l, s);
+ },
+ };
+}
+function an(o) {
+ let e,
+ n,
+ t,
+ i,
+ l = I(o[1]),
+ s = [];
+ for (let r = 0; r < l.length; r += 1) s[r] = Ue(Ke(o, l, r));
+ const p = (r) =>
+ handleOutroTransition(s[r], 1, 1, () => {
+ s[r] = null;
+ });
+ return (
+ (t = new G({ props: { line: 13, pos: 40, $$slots: { default: [fn] }, $$scope: { ctx: o } } })),
+ {
+ c() {
+ e = x("div");
+ for (let r = 0; r < s.length; r += 1) s[r].c();
+ (n = W()), P(t.$$.fragment);
+ },
+ m(r, f) {
+ b(r, e, f);
+ for (let a = 0; a < s.length; a += 1) s[a] && s[a].m(e, null);
+ E(e, n), M(t, e, null), (i = !0);
+ },
+ p(r, [f]) {
+ if (f & 2) {
+ l = I(r[1]);
+ let c;
+ for (c = 0; c < l.length; c += 1) {
+ const u = Ke(r, l, c);
+ s[c] ? (s[c].p(u, f), h(s[c], 1)) : ((s[c] = Ue(u)), s[c].c(), h(s[c], 1), s[c].m(e, n));
+ }
+ for (H(), c = l.length; c < s.length; c += 1) p(c);
+ S();
+ }
+ const a = {};
+ f & 4097 && (a.$$scope = { dirty: f, ctx: r }), t.$set(a);
+ },
+ i(r) {
+ if (!i) {
+ for (let f = 0; f < l.length; f += 1) h(s[f]);
+ h(t.$$.fragment, r), (i = !0);
+ }
+ },
+ o(r) {
+ s = s.filter(Boolean);
+ for (let f = 0; f < s.length; f += 1) handleOutroTransition(s[f]);
+ handleOutroTransition(t.$$.fragment, r), (i = !1);
+ },
+ d(r) {
+ r && k(e), K(s, r), cleanupComponents(t);
+ },
+ }
+ );
+}
+function pn(o, e, n) {
+ let t;
+ const i = `// Coming soon...`,
+ l = [];
+ let s = [],
+ p;
+ function r() {
+ n(0, (s = []));
+ let f = 0;
+ function a() {
+ s.pop();
+ let c = 0;
+ function u() {
+ f === l.length ? (p = setTimeout(r, 5e3)) : c === 3 ? (s.push({ text: "Loading next page" }), n(0, s), (p = setTimeout(a, 1e3)), f--) : f < l.length && (s.push(l[f]), n(0, s), (p = setTimeout(u, 250))),
+ c++,
+ f++;
+ }
+ u();
+ }
+ a();
+ }
+ return (
+ de(() => clearTimeout(p)),
+ r(),
+ n(
+ 1,
+ (t = i.split(`
+`))
+ ),
+ [s, t]
+ );
+}
+class GoSnippet extends Component {
+ constructor(e) {
+ super(), renderComponent(this, e, pn, an, B, {});
+ }
+}
+function Ge(o, e, n) {
+ const t = o.slice();
+ return (t[2] = e[n]), (t[4] = n), t;
+}
+function mn(o) {
+ let e,
+ n = o[2] + "",
+ t;
+ return {
+ c() {
+ (e = new oe(!1)), (t = D()), (e.a = t);
+ },
+ m(i, l) {
+ e.m(n, i, l), b(i, t, l);
+ },
+ p(i, l) {
+ l & 1 && n !== (n = i[2] + "") && e.p(n);
+ },
+ d(i) {
+ i && (k(t), e.d());
+ },
+ };
+}
+function Qe(o) {
+ let e, n;
+ return (
+ (e = new ie({ props: { index: o[4], $$slots: { default: [mn] }, $$scope: { ctx: o } } })),
+ {
+ c() {
+ P(e.$$.fragment);
+ },
+ m(t, i) {
+ M(e, t, i), (n = !0);
+ },
+ p(t, i) {
+ const l = {};
+ i & 33 && (l.$$scope = { dirty: i, ctx: t }), e.$set(l);
+ },
+ i(t) {
+ n || (h(e.$$.fragment, t), (n = !0));
+ },
+ o(t) {
+ handleOutroTransition(e.$$.fragment, t), (n = !1);
+ },
+ d(t) {
+ cleanupComponents(e, t);
+ },
+ }
+ );
+}
+function dn(o) {
+ let e,
+ n,
+ t = I(o[0]),
+ i = [];
+ for (let s = 0; s < t.length; s += 1) i[s] = Qe(Ge(o, t, s));
+ const l = (s) =>
+ handleOutroTransition(i[s], 1, 1, () => {
+ i[s] = null;
+ });
+ return {
+ c() {
+ e = x("div");
+ for (let s = 0; s < i.length; s += 1) i[s].c();
+ Z(e, "position", "relative");
+ },
+ m(s, p) {
+ b(s, e, p);
+ for (let r = 0; r < i.length; r += 1) i[r] && i[r].m(e, null);
+ n = !0;
+ },
+ p(s, [p]) {
+ if (p & 1) {
+ t = I(s[0]);
+ let r;
+ for (r = 0; r < t.length; r += 1) {
+ const f = Ge(s, t, r);
+ i[r] ? (i[r].p(f, p), h(i[r], 1)) : ((i[r] = Qe(f)), i[r].c(), h(i[r], 1), i[r].m(e, null));
+ }
+ for (H(), r = t.length; r < i.length; r += 1) l(r);
+ S();
+ }
+ },
+ i(s) {
+ if (!n) {
+ for (let p = 0; p < t.length; p += 1) h(i[p]);
+ n = !0;
+ }
+ },
+ o(s) {
+ i = i.filter(Boolean);
+ for (let p = 0; p < i.length; p += 1) handleOutroTransition(i[p]);
+ n = !1;
+ },
+ d(s) {
+ s && k(e), K(i, s);
+ },
+ };
+}
+function hn(o, e, n) {
+ let t;
+ return (
+ n(
+ 0,
+ (t = `// Coming soon...`.split(`
+`))
+ ),
+ [t]
+ );
+}
+class JavaSnippet extends Component {
+ constructor(e) {
+ super(), renderComponent(this, e, hn, dn, B, {});
+ }
+}
+function Xe(o, e, n) {
+ const t = o.slice();
+ return (t[2] = e[n]), (t[4] = n), t;
+}
+function gn(o) {
+ let e,
+ n = o[2] + "",
+ t;
+ return {
+ c() {
+ (e = new oe(!1)), (t = D()), (e.a = t);
+ },
+ m(i, l) {
+ e.m(n, i, l), b(i, t, l);
+ },
+ p(i, l) {
+ l & 1 && n !== (n = i[2] + "") && e.p(n);
+ },
+ d(i) {
+ i && (k(t), e.d());
+ },
+ };
+}
+function Ye(o) {
+ let e, n;
+ return (
+ (e = new ie({ props: { index: o[4], $$slots: { default: [gn] }, $$scope: { ctx: o } } })),
+ {
+ c() {
+ P(e.$$.fragment);
+ },
+ m(t, i) {
+ M(e, t, i), (n = !0);
+ },
+ p(t, i) {
+ const l = {};
+ i & 33 && (l.$$scope = { dirty: i, ctx: t }), e.$set(l);
+ },
+ i(t) {
+ n || (h(e.$$.fragment, t), (n = !0));
+ },
+ o(t) {
+ handleOutroTransition(e.$$.fragment, t), (n = !1);
+ },
+ d(t) {
+ cleanupComponents(e, t);
+ },
+ }
+ );
+}
+function wn(o) {
+ let e,
+ n,
+ t = I(o[0]),
+ i = [];
+ for (let s = 0; s < t.length; s += 1) i[s] = Ye(Xe(o, t, s));
+ const l = (s) =>
+ handleOutroTransition(i[s], 1, 1, () => {
+ i[s] = null;
+ });
+ return {
+ c() {
+ e = x("div");
+ for (let s = 0; s < i.length; s += 1) i[s].c();
+ Z(e, "position", "relative");
+ },
+ m(s, p) {
+ b(s, e, p);
+ for (let r = 0; r < i.length; r += 1) i[r] && i[r].m(e, null);
+ n = !0;
+ },
+ p(s, [p]) {
+ if (p & 1) {
+ t = I(s[0]);
+ let r;
+ for (r = 0; r < t.length; r += 1) {
+ const f = Xe(s, t, r);
+ i[r] ? (i[r].p(f, p), h(i[r], 1)) : ((i[r] = Ye(f)), i[r].c(), h(i[r], 1), i[r].m(e, null));
+ }
+ for (H(), r = t.length; r < i.length; r += 1) l(r);
+ S();
+ }
+ },
+ i(s) {
+ if (!n) {
+ for (let p = 0; p < t.length; p += 1) h(i[p]);
+ n = !0;
+ }
+ },
+ o(s) {
+ i = i.filter(Boolean);
+ for (let p = 0; p < i.length; p += 1) handleOutroTransition(i[p]);
+ n = !1;
+ },
+ d(s) {
+ s && k(e), K(i, s);
+ },
+ };
+}
+function $n(o, e, n) {
+ let t;
+ return (
+ n(
+ 0,
+ (t = `// Coming soon...`.split(`
+ `))
+ ),
+ [t]
+ );
+}
+class KotlinSnippet extends Component {
+ constructor(e) {
+ super(), renderComponent(this, e, $n, wn, B, {});
+ }
+}
+function et(o, e, n) {
+ const t = o.slice();
+ return (t[5] = e[n][0]), (t[6] = e[n][1]), t;
+}
+function tt(o) {
+ let e, n, t;
+ function i() {
+ return o[3](o[6]);
+ }
+ return {
+ c() {
+ (e = x("button")), (e.textContent = `${o[6].name}`), $(e, "class", "nv-terminal-tab terminal-1dfudv"), z(e, "is-active", o[6].name === o[0].name);
+ },
+ m(l, s) {
+ b(l, e, s), n || ((t = ae(e, "click", i)), (n = !0));
+ },
+ p(l, s) {
+ (o = l), s & 5 && z(e, "is-active", o[6].name === o[0].name);
+ },
+ d(l) {
+ l && k(e), (n = !1), t();
+ },
+ };
+}
+function TerminalWrapperComponent(o) {
+ let e,
+ n,
+ t,
+ i,
+ l,
+ s,
+ p,
+ r,
+ f = I(Object.entries(o[2])),
+ a = [];
+ for (let _ = 0; _ < f.length; _ += 1) a[_] = tt(et(o, f, _));
+ var c = o[0].component;
+ function u(_, g) {
+ return { props: { animated: _[1] } };
+ }
+ return (
+ c && (l = me(c, u(o))),
+ {
+ c() {
+ (e = x("div")), (n = x("div"));
+ for (let _ = 0; _ < a.length; _ += 1) a[_].c();
+ (t = W()), (i = x("div")), l && P(l.$$.fragment), $(n, "class", "nv-terminal-tabs terminal-1dfudv"), $(i, "class", "nv-code-content terminal-1dfudv"), $(e, "id", "nv-terminal"), $(e, "class", "nv-code terminal-1dfudv");
+ },
+ m(_, g) {
+ b(_, e, g), E(e, n);
+ for (let m = 0; m < a.length; m += 1) a[m] && a[m].m(n, null);
+ E(e, t), E(e, i), l && M(l, i, null), (s = !0), p || ((r = ae(i, "scroll", o[4])), (p = !0));
+ },
+ p(_, [g]) {
+ if (g & 7) {
+ f = I(Object.entries(_[2]));
+ let m;
+ for (m = 0; m < f.length; m += 1) {
+ const L = et(_, f, m);
+ a[m] ? a[m].p(L, g) : ((a[m] = tt(L)), a[m].c(), a[m].m(n, null));
+ }
+ for (; m < a.length; m += 1) a[m].d(1);
+ a.length = f.length;
+ }
+ if (g & 1 && c !== (c = _[0].component)) {
+ if (l) {
+ H();
+ const m = l;
+ handleOutroTransition(m.$$.fragment, 1, 0, () => {
+ cleanupComponents(m, 1);
+ }),
+ S();
+ }
+ c ? ((l = me(c, u(_))), P(l.$$.fragment), h(l.$$.fragment, 1), M(l, i, null)) : (l = null);
+ } else if (c) {
+ const m = {};
+ g & 2 && (m.animated = _[1]), l.$set(m);
+ }
+ },
+ i(_) {
+ s || (l && h(l.$$.fragment, _), (s = !0));
+ },
+ o(_) {
+ l && handleOutroTransition(l.$$.fragment, _), (s = !1);
+ },
+ d(_) {
+ _ && k(e), K(a, _), l && cleanupComponents(l), (p = !1), r();
+ },
+ }
+ );
+}
+function TerminalComponent(o, e, n) {
+ const t = { node: { name: "Node", component: NodeSnippet }, python: { name: "Python", component: PythonSnippet }, go: { name: "Go", component: GoSnippet }, java: { name: "Java", component: JavaSnippet }, kotlin: { name: "Kotlin", component: KotlinSnippet } };
+ let i = t.node,
+ l = !0;
+ return [
+ i,
+ l,
+ t,
+ (r) => {
+ n(0, (i = r)), n(1, (l = !1));
+ },
+ (r) => be.set(r.target.scrollLeft),
+ ];
+}
+class Terminal extends Component {
+ constructor(e) {
+ super(), renderComponent(this, e, TerminalComponent, TerminalWrapperComponent, B, {});
+ }
+}
+
+// A noop JS template literal to syntax highlight `css`
+const css = (strings, ...values) => strings[0];
+
+/**
+ * Load the styles
+ */
+const frameworkTerminalCss = css`
+/* Terminal Wrapper Styles */
+
+.nv-snippet-window-wrapper.terminal-cqc4oo {
+ position: absolute;
+ font-size: .875rem;
+ font-family: var(--nv-monospace-font)
+}
+
+.nv-snippet-window-wrapper.is-noninteractable.terminal-cqc4oo {
+ opacity: 1;
+ -webkit-user-select: none;
+ user-select: none;
+ -moz-user-select: -moz-none;
+ transition: opacity .1s ease, filter .1s ease
+}
+
+.nv-snippet-window-wrapper.is-noninteractable.terminal-cqc4oo:hover {
+ opacity: .3;
+ filter: blur(2px)
+}
+
+.is-selectable.terminal-10kv6jm .nv-completion-item.terminal-10kv6jm:hover {
+ background: var(--nv-bg-light) !important
+}
+
+#nv-terminal {
+ position: relative;
+ width: 100%;
+ height: 600px;
+ max-width: 700px;
+ box-shadow: 0 4px 8px -2px #0f111a29, 0 10px 15px -4px #0f111a29, 0 30px 32px -15px #0f111a29, 0 60px 64px -25px #0f111a29
+}
+
+#nv-terminal.terminal-1dfudv>.nv-code-content.terminal-1dfudv .line {
+ min-width: 60ch
+}
+
+.nv-terminal-tabs.terminal-1dfudv.terminal-1dfudv {
+ display: flex;
+ border-radius: 8px 8px 0 0;
+ padding: 0 4px;
+ border-bottom: 1px solid #444
+}
+
+button.nv-terminal-tab.terminal-1dfudv.terminal-1dfudv {
+ position: relative;
+ font-family: inherit;
+ border: 0;
+ background: none;
+ color: #99a;
+ font-size: 1rem;
+ padding: .5rem .75rem
+}
+
+button.nv-terminal-tab.is-active.terminal-1dfudv.terminal-1dfudv {
+ color: #dde
+}
+
+button.nv-terminal-tab.is-active.terminal-1dfudv.terminal-1dfudv:after {
+ position: absolute;
+ top: 0;
+ bottom: -1px;
+ left: calc(.75rem - 2px);
+ right: calc(.75rem - 2px);
+ content: "";
+ border-bottom: 1px solid #ffffff7f
+}
+
+button.nv-terminal-tab.terminal-1dfudv.terminal-1dfudv:hover {
+ color: #dde
+}
+
+/* Terminal Code Snippet Styles */
+@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap');
+
+.nv-code {
+ display: flex;
+ flex-direction: column;
+ --nv-color: #f9fafc;
+ --nv-monospace-font: 'Source Code Pro', 'Monaco', 'Consolas', monospace;
+ --nv-bg: #27292e;
+ --nv-bg-light: #373a3f;
+ --nv-border-color: rgb(73, 76, 95);
+ width: 100%;
+ font-size: 0.875rem;
+ font-family: var(--nv-monospace-font);
+ line-height: 1.35;
+ color: var(--nv-color);
+ border: 1px solid #555;
+ background: var(--nv-bg);
+ border-radius: 8px;
+ box-shadow: 0px 1px 3px 1px rgba(17, 19, 33, 0.4);
+}
+
+.nv-code kw {
+ color: rgb(255 225 77);
+}
+
+.nv-code fn {
+ color: rgb(255 51 222);
+}
+
+.nv-code str {
+ color: rgb(0 213 255);
+}
+
+.nv-code id {
+ color: #ffcc4a;
+}
+
+.nv-code sp {
+ color: #6fedb7;
+}
+
+.nv-code const {
+ color: #b486fd;
+}
+
+.nv-code comment {
+ color: #8f9397;
+}
+
+.nv-code .nv-code-content {
+ padding: 0.75rem;
+ overflow: auto;
+ flex: 1;
+}
+
+.nv-code .line {
+ display: block;
+ font-family: var(--nv-monospace-font);
+ margin: 0;
+ white-space: pre;
+}
+
+.nv-code .line-number {
+ display: inline-block;
+ font-family: var(--nv-monospace-font);
+ width: 2ch;
+ text-align: right;
+ margin-right: 1ch;
+ opacity: 0.5;
+ user-select: none;
+}
+
+.nv-code .nv-completion-heading {
+ padding: 0.25rem 0.5rem;
+ border-radius: 5px;
+ background: var(--nv-bg-light);
+}
+
+.nv-code .nv-completion-items {
+ padding: 0.25rem 0;
+}
+
+.nv-code .nv-completion-item {
+ padding: 0.25rem 0.5rem;
+ white-space: pre-wrap;
+}
+
+.nv-code .nv-completion-item.is-active {
+ background-color: var(--nv-bg-light);
+}
+
+.nv-code .nv-completion-icon {
+ display: inline-block;
+ vertical-align: middle;
+ margin: -0.25rem 0;
+}
+
+.nv-code .nv-completion-description {
+ padding: 0.5rem 0.5rem;
+ border-top: 1px solid var(--nv-border-color);
+}
+
+.nv-code .nv-cursor {
+ font-family: var(--nv-monospace-font);
+ color: white;
+ position: absolute;
+ transform: translateX(-45%);
+ font-size: 0.875rem;
+ animation: nv-cursor-blinker 1s step-start infinite;
+}
+
+.nv-code .nv-cursor::before {
+ content: '│';
+}
+
+.nv-code span.hover {
+ background-color: var(--nv-bg-light);
+ outline: 1px solid #aac;
+}
+
+.nv-code span.hover.is-active {
+ background-color: var(--nv-bg);
+}
+
+.nv-code .nv-snippet-window {
+ font-size: 0.875rem;
+ font-family: var(--nv-monospace-font);
+ display: block;
+ background-color: var(--nv-bg);
+ border: 1px solid var(--nv-border-color);
+ border-radius: 5px;
+ min-width: 20ch;
+ max-width: 50ch;
+ whitespace: pre-wrap;
+ overflow-wrap: break-word;
+ box-shadow: 0px 1px 2px 1px rgba(17, 19, 33, 0.3);
+}
+
+.nv-code .nv-snippet-window.is-pop {
+ animation: nv-snippet-window-pop-in 0.3s forwards ease;
+}
+
+.nv-code .nv-snippet-window-wrapper hr {
+ box-sizing: content-box;
+ height: 0;
+ margin-top: 0.5rem;
+ margin-bottom: 0.5rem;
+ border-color: var(--nv-color);
+}
+
+@keyframes nv-snippet-window-pop-in {
+ 0% {
+ transform: scale(0);
+ opacity: 0;
+ }
+
+ 70% {
+ transform: scale(1.05);
+ opacity: 0.5;
+ }
+
+ 100% {
+ transform: scale(1);
+ opacity: 1;
+ }
+}
+
+@keyframes nv-cursor-blinker {
+ 50% {
+ opacity: 0;
+ }
+}
+`
+!function(id) {
+ if (!document.getElementById(id)) {
+ var link = document.createElement('style');
+ link.type = 'text/css';
+ link.innerHTML = frameworkTerminalCss;
+ link.id = id; // Set the ID for deduplication
+ document.head.appendChild(link); // Append to the head for CSS
+ }
+}("framework-terminal-css-loader");
+
+/*
+ * Load the `nv-framework-terminal` web component
+ */
+
+const CUSTOM_ELEMENTS_NAME = "nv-framework-terminal";
+const TERMINAL_ID = "nv-terminal";
+const TERMINAL_CLASSES = ["nv-code", "nv-terminal"];
+
+const loadTerminal = () => {
+ const nvTerminal = document.getElementById(TERMINAL_ID),
+ docFragment = document.createDocumentFragment();
+ new Terminal({ target: docFragment });
+ nvTerminal.replaceWith(docFragment);
+}
+
+customElements.define(CUSTOM_ELEMENTS_NAME, class extends HTMLElement {
+ connectedCallback() {
+ this.mount();
+ }
+
+ mount() {
+ this.id = TERMINAL_ID;
+ this.classList.add(...TERMINAL_CLASSES);
+ loadTerminal(); // Mount the terminal
+ }
+});
diff --git a/framework-terminal.md b/framework-terminal.md
new file mode 100644
index 00000000..83320b13
--- /dev/null
+++ b/framework-terminal.md
@@ -0,0 +1,52 @@
+# Framework Terminal Snippet Web Component
+
+The Framework Terminal Snippet can be loaded as a web component via JSDelivr CDN.
+
+The JSDelivr URLs below point to the latest commit in the main branch, ensuring changes to the snippet are automatically deployed to consuming websites.
+
+[See here for more options when testing using JSDelivr](https://www.jsdelivr.com/).
+
+## Usage
+
+The Framework terminal can be loaded via both JS and HTML.
+
+### Loading with Javascript (preferred)
+
+Use this method if you have access to custom JS.
+
+```javascript
+// Custom JS injection
+!(function (e) {
+ if (!document.getElementById(e)) {
+ var t = document.createElement("script");
+ (t.src = "https://cdn.jsdelivr.net/gh/novuhq/docs/framework-terminal.min.js"),
+ (t.type = "text/javascript"),
+ (t.crossOrigin = "anonymous"),
+ (t.id = e);
+ var n = document.getElementsByTagName("script")[0];
+ n ? n.parentNode.insertBefore(t, n) : document.body.appendChild(t);
+ }
+})("nv-framework-terminal-loader");
+```
+
+```html
+
+
+```
+
+### Loading with HTML
+
+Use this method if you only have access to modify the `` element.
+
+```html
+
+
+
+
+
+```
diff --git a/framework/chat-channel.mdx b/framework/chat-channel.mdx
new file mode 100644
index 00000000..cfb7a2ef
--- /dev/null
+++ b/framework/chat-channel.mdx
@@ -0,0 +1,17 @@
+---
+title: "Chat Channel"
+sidebarTitle: "Chat Channel"
+description: "Learn the process of configuring and using chat providers with Novu"
+---
+
+Novu brings chat notifications into your development workflow, giving you a unified way to manage messaging across platforms and apps. Whether you're working with tools like Slack or Microsoft Teams or apps like WhatsApp, Telegram, and Discord, Novu lets you integrate, manage, and scale chat notifications without unnecessary complexity.
+
+Learn more about the [Chat Channel](/integrations/providers/chat/overview).
+
+```typescript
+await step.chat('chat', async () => {
+ return {
+ body: 'A new post has been created',
+ };
+});
+```
\ No newline at end of file
diff --git a/framework/content/react-email.mdx b/framework/content/react-email.mdx
new file mode 100644
index 00000000..f8829e74
--- /dev/null
+++ b/framework/content/react-email.mdx
@@ -0,0 +1,63 @@
+---
+title: "React Email"
+---
+
+Integrating Novu Framework with [React.Email](https://react.email/) for your Next.js application can be done in three steps.
+
+ Don't have an existing Novu Next.js app yet? Visit our [Quickstart guide](/framework/quickstart/nextjs) to create one.
+
+
+
+
+ Install the required React.Email components.
+
+ ```bash
+ npm i @react-email/components react-email
+ ```
+
+
+
+ Create a new `sample-email.tsx` file for your email template.
+
+ ```tsx
+ import * as React from "react";
+ import { Button, Html } from "@react-email/components";
+
+ function Email(props) {
+ return (
+
+
+
+ );
+ }
+
+ export function renderEmail(inputs) {
+ return render();
+ }
+ ```
+
+
+
+ Define your workflow using the above template
+
+ ```tsx
+ import { renderEmail } from './sample-email.tsx';
+ import { workflow } from '@novu/framework';
+
+ workflow('new-signup', async ({ step, payload }) => {
+ await step.email('send-email', async (inputs) => {
+ return {
+ subject: `Welcome to React E-mail`,
+ body: renderEmail(inputs),
+ }
+ });
+ });
+ ```
+
+
+
diff --git a/framework/content/remix-react-email.mdx b/framework/content/remix-react-email.mdx
new file mode 100644
index 00000000..69fa1554
--- /dev/null
+++ b/framework/content/remix-react-email.mdx
@@ -0,0 +1,62 @@
+---
+title: "Remix & React Email"
+---
+
+Integrating Novu Framework with [React email](https://react.email/) for your Remix application can be done in three steps. If you don't have an app, you can [clone our Remix example](https://github.com/novuhq/novu-framework-remix-example).
+
+
+
+ Install the required React email components.
+
+ ```bash
+ npm i @react-email/components react-email
+ ```
+
+
+
+ Create an `emails` folder in the `app` directory of your Remix app.
+
+
+ Create a new `sample-email.tsx` file for your email template.
+
+ ```ts
+ import { Button, Html } from "@react-email/components";
+
+ function Email(props) {
+ return (
+
+
+
+ );
+ }
+
+ export function renderEmail(inputs) {
+ return render();
+ }
+ ```
+
+
+
+ Define your workflow using the above template
+
+ ```tsx
+ import { renderEmail } from './sample-email.tsx';
+ import { workflow } from '@novu/framework';
+
+ workflow('new-signup', async ({ step, payload }) => {
+ await step.email('send-email', async (inputs) => {
+ return {
+ subject: `Welcome to Remix and React E-mail`,
+ body: renderEmail(inputs),
+ }
+ });
+ });
+ ```
+
+
+
diff --git a/framework/content/svelte-email.mdx b/framework/content/svelte-email.mdx
new file mode 100644
index 00000000..d355ba3d
--- /dev/null
+++ b/framework/content/svelte-email.mdx
@@ -0,0 +1,133 @@
+---
+title: "Svelte Email"
+---
+
+Integrating Novu Framework with [Svelte email](https://react.email/) for your Svelte application can be done in three steps. If you don't have an app, you can [clone our Svelte example](https://github.com/novuhq/novu-svelte-email).
+
+
+
+ Install the required Svelte email components.
+
+ ```bash
+ npm i svelte-email
+ ```
+
+
+
+ Create an `emails` folder in the `src/lib` directory of your Svelte app.
+
+
+ Create a new `sample-email.svelte` file for your email template.
+
+ ```ts
+
+
+
+
+
+
+
+
+ {firstName}, welcome to svelte-email
+ A Svelte component library for building responsive emails
+
+
+
+ Happy coding!
+
+ Carsten Lebek
+
+
+
+ ```
+
+
+
+ Define your workflow using the above template
+
+ ```tsx
+ import WelcomeEmail from '$lib/emails/sample-email.svelte';
+ import { render } from 'svelte-email';
+ import { workflow } from '@novu/framework';
+
+ workflow('new-signup', async ({ step, payload }) => {
+ await step.email(
+ 'send-email',
+ async (inputs) => {
+ const html = render({
+ template: WelcomeEmail,
+ props: {
+ firstName: 'John',
+ },
+ });
+ return {
+ subject: `Welcome to Svelte E-mail`,
+ body: html,
+ }
+ });
+ }
+ );
+ ```
+
+
+
diff --git a/framework/content/vue-email.mdx b/framework/content/vue-email.mdx
new file mode 100644
index 00000000..456305bb
--- /dev/null
+++ b/framework/content/vue-email.mdx
@@ -0,0 +1,101 @@
+---
+title: "Vue Email"
+---
+
+You can integrate Novu Framework with [Vue Email](https://vuemail.net/) in a few simple steps. This guide will walk you through the process of creating a new email template using Vue Email and Nuxt.
+
+For a Quickstart Boilerplate project using Nuxt.js, and Vue Email, check out the [Vue Email Starter repository](https://github.com/novuhq/novu-framework-nuxt-example/)
+
+## Quickstart
+
+
+
+ Install the required Vue.Email components.
+
+ ```bash
+ npm install vue-email @vue-email/compiler @vue-email/nuxt
+ ```
+
+
+ Create a `templates` folder in the root of your Nuxt app
+
+
+ Update your `nuxt.config.ts` file to include the Vue Email module.
+
+ ```ts nuxt.config.ts
+ export default defineNuxtConfig({
+ devtools: { enabled: true },
+ components: true,
+
+ modules: [
+ '@vue-email/nuxt',
+ ],
+ })
+ ```
+
+
+ Create a new `sample-email.vue` file in the `templates` folder for your email template.
+
+ ```vue
+
+
+
+
+
+
+ Email Preview Text
+
+
+
+ Hello World!
+
+
+
+
+
+
+
+ ```
+
+
+ Define your workflow using the above template
+ ```ts
+ import { config } from '@vue-email/compiler';
+ import { workflow } from '@novu/framework';
+
+ const vueEmail = config('templates', {
+ verbose: false,
+ options: {},
+ });
+
+
+ workflow('new-signup', async ({step, payload}) => {
+ await step.email('send-email', async (inputs) => {
+ const template = await vueEmail.render('sample-email.vue', {
+ props: inputs
+ })
+
+ return {
+ subject: `Welcome to Vue E-mail`,
+ body: template.html
+ }
+ });
+ });
+ ```
+
+
+
+
+## Learn More
+
+To learn more, refer to [Vue Email documentation](https://vuemail.net/).
diff --git a/framework/controls.mdx b/framework/controls.mdx
new file mode 100644
index 00000000..0afbebef
--- /dev/null
+++ b/framework/controls.mdx
@@ -0,0 +1,191 @@
+---
+title: "Controls"
+---
+
+Controls are defined using [JSON Schema](framework/schema/json-schema) or [Zod](https://zod.dev), providing a strong run-time validation system for your workflows.
+
+This ensures that you as the developer and your non-technical peers are speaking the same language.
+Those responsible for styling and copy can edit with confidence, knowing their changes are tested in code.
+
+## Controls vs Payload
+
+**Control Schema** - For Non-Technical Peers and Developers. Managed in the Novu Dashboard UI, defined by developers and used by non-technical peers.
+
+**Payload Schema** - For Developers. Passed during the `novu.trigger` method, and controlled by the developer.
+
+## Common usecases
+
+- **Content** - Modify any static content: email subject, email body, push notification title, etc...
+- **Styling** - Modify the styling of the content: button color, background color, font size, etc...
+- **Behaviour** - Modify the behaviour of the content: show/hide a section, show/hide a button, etc...
+- **Order** - Modify the order of the content: the order of the email sections, the order of the buttons, etc...
+- **Actions** - Modify the behaviour of actions: digest duration, etc...
+- **Other** - Any other use case that should be controller without modifying code
+
+## Step Controls
+
+Step Control schema defines the control passed during the `step` method. These controls can be modified and persisted in the Novu Dashboard UI.
+The snippet below shows a configuration for the Step Control schema. If you don’t provide a schema, Typescript will infer the data type to `unknown`, reminding you of the best practice to specify your schema.
+
+
+
+ ```tsx Zod Schema
+ import { z } from 'zod';
+
+ workflow("new-signup", async ({ step, payload }) => {
+ await step.email(
+ "send-email",
+ async (controls) => {
+ return {
+ subject: controls.subject,
+ body: render(
+
+ ),
+ };
+ },
+ {
+ controlSchema: z.object({
+ hideBanner: z.boolean().default(false),
+ subject: z.string().default('Hi {{subscriber.firstName | capitalize}}'),
+ components: z.array(z.object({
+ type: z.enum(['header', 'cta-row', 'footer']),
+ content: z.string()
+ }))
+ })
+ }
+ );
+ });
+ ```
+
+ ```tsx Class-Validator Schema
+ class NewSignUpComponent {
+ @IsString()
+ subject: string;
+
+ @IsString()
+ content: string;
+ }
+ class NewSignUpControlSchema {
+ @IsBoolean()
+ hideBanner: Boolean;
+
+ @IsString()
+ @IsNotEmpty()
+ @IsOptional()
+ subject?: string;
+
+ // Allowing no code control over the component in the Dashboard UI
+ @Type(() => NewSignUpComponent)
+ @NestedValidation({ each: true })
+ @IsOptional()
+ components?: NewSignUpComponent[];
+ }
+
+ workflow("new-signup", async ({ step, payload }) => {
+ await step.email(
+ "send-email",
+ async (controls) => {
+ return {
+ subject: controls.subject,
+ body: render(
+
+ ),
+ };
+ },
+ {
+ // Learn about Class-Validator Schema here: https://github.com/typestack/class-validator
+ controlSchema: NewSignUpControlSchema,
+ }
+ );
+ });
+ ```
+
+ ```tsx JSON Schema
+ workflow("new-signup", async ({ step, payload }) => {
+ await step.email(
+ "send-email",
+ async (controls) => {
+ return {
+ subject: controls.subject,
+ body: render(
+
+ ),
+ };
+ },
+ {
+ // Learn about JSON Schema here: https://json-schema.org/specification
+ controlSchema: {
+ // Always `object`
+ type: "object",
+ // Specify the properties to validate. Supports deep nesting.
+ properties: {
+ hideBanner: { type: "boolean", default: false },
+ subject: { type: "string", default: 'Hi {{subscriber.firstName | capitalize}}' },
+ // Allowing no code control over the component in the Dashboard UI
+ components: {
+ type: "array",
+ items: {
+ type: "object",
+ },
+ properties: {
+ subject: { type: "string" },
+ content: { type: "string" },
+ }
+ },
+ },
+ // Specify the array of which properties are required.
+ required: ["hideBanner"],
+ // Used to enforce full type strictness, with no rogue properties.
+ additionalProperties: false,
+ // The `as const` is important to let Typescript know that this
+ // type won't change, enabling strong typing on `inputs` via type
+ // inference of the provided JSON Schema.
+ } as const,
+ }
+ );
+ });
+ ```
+
+
+For the full list of parameters, check out the [full SDK reference](/framework/typescript/steps/email).
+
+## Schema Validation & IDE IntelliSense
+
+You can use **Zod, Class-Validator or JSON Schema** based on your needs.
+
+- **[Zod](https://zod.dev/)** - A TypeScript-first schema declaration and validation library.
+- **[Class-Validator](https://github.com/typestack/class-validator)** - A TypeScript-first validation library using decorators for OOP-style applications.
+- **[JSON Schema](/framework/schema/json-schema)** - The most popular schema language for defining JSON data structures.
+
+If you only want local IDE IntelliSense, you are able to pass plain JS Classes, which will not provide any Schema Definition useable by Novu Platform.
+
+All provided **Zod** and **Class-Transformer** Schemas are compiled into **JSON Schema** which is passed to Novu. This ensures a consistent validation approach and UX by managing Payload and Control Data directly from the Platform.
+
+**Important:** There may be inconsistencies when using Class-Transformer exspecialy with nested schema objects. Please check out the guidelines on converting Class-Transformer classes to JSON Schema before using it here: [class-validator-jsonschema](https://www.npmjs.com/package/class-validator-jsonschema).
+
+## Using Variables
+
+To facilitate the use of variables in the control schema, enclose the variable name in double curly braces using the `{{variableName}}` syntax. For example, `{{subscriber.firstName | capitalize}}` will be dynamically replaced with the actual value of the subscriber's first name at runtime.
+You can use variables in any step control value, whether set by the developer or within the Novu Dashboard UI. To facilitate this, the Novu Dashboard UI offers auto-completion for variables. Simply start typing `{{` to view a list of all available variables.
+
+
+
+
+
+### Variable Options
+- **Subscriber Attributes**: Access all [subscriber attributes](/concepts/subscribers#subscriber-attributes). Example: `{{subscriber.firstName}}`
+- **Payload Variables**: Use all payload variables defined in the `payloadSchema`. Example: `{{payload.userId}}`
+- **Liquid Filters**: Apply [liquid filters](https://liquidjs.com/filters/overview.html) to format or manipulate variable values. Examples: `{{subscriber.firstName | append: ': ' | append: payload.status | capitalize}}` or
+`{{payload.invoiceDate | date: '%a, %b %d, %y'}}` will format the date as `Thu, Jan 01, 24`
+
+
+
diff --git a/framework/custom.mdx b/framework/custom.mdx
new file mode 100644
index 00000000..13a846fd
--- /dev/null
+++ b/framework/custom.mdx
@@ -0,0 +1,94 @@
+---
+title: "Custom Action"
+sidebarTitle: "Custom Action"
+description: "Used to execute any custom code as a step in the workflow."
+---
+
+A custom steps allows to execute any custom logic and persist in the durable execution context. The result of this step can be used in subsequent steps.
+
+## Common usecases
+
+- Making an API call to 3rd party service
+- Fetch data from a database to be used in subsequent steps
+- Execute a custom logic to transform data
+- Custom provider implementation
+
+## Custom Step Interface
+
+```tsx
+const stepResult = await step.custom(
+ "custom-step",
+ async () => {
+ return {
+ item_name: "A product name",
+ item_price: 100,
+ };
+ },
+ {
+ outputSchema: {
+ type: "object",
+ properties: {
+ item_name: { type: "string" },
+ item_price: { type: "number" },
+ },
+ required: ["item_name", "item_price"],
+ },
+ }
+);
+```
+
+### Output Schema Definition
+
+This JSON Schema definition is used to validate the output of the custom step. If the output does not match the schema, the workflow will fail.
+Novu Framework will infer the Typescript interface from the JSON Schema definition.
+
+### Return Value
+
+The Custom Step function should return a valid serializable object. The return value will be persisted in the durable execution context.
+
+## Using the Custom Step Result
+
+The result can only be used in the `resolver` of the step/providers/skip functions of subsequent steps.
+
+```typescript
+workflow("hello-world-workflow", async ({ payload }) => {
+ const task = await step.custom(
+ "fetch-db-data",
+ async () => {
+ const taskData = db.fetchTask(payload.task_id);
+ return {
+ task_id: taskData.id,
+ task_title: taskData.title,
+ complete: taskData.complete,
+ };
+ },
+ {
+ outputSchema: {
+ type: "object",
+ properties: {
+ task_title: { type: "string" },
+ task_id: { type: "string" },
+ complete: { type: "boolean" },
+ },
+ required: ["task_id", "complete"],
+ },
+ }
+ );
+
+ await step.email(
+ "send-email",
+ () => {
+ return {
+ subject: `Task reminder for ${task.task_title}`,
+ body: "Task is not yet complete. Please complete the task.",
+ };
+ },
+ {
+ // Only send the reminder E-mail if the task is not complete
+ skip: () => !task.complete,
+ }
+ );
+});
+```
+
+To read more about the full list of parameters, check out the [full SDK reference](/framework/typescript/steps/custom).
\ No newline at end of file
diff --git a/framework/delay.mdx b/framework/delay.mdx
new file mode 100644
index 00000000..ad88134b
--- /dev/null
+++ b/framework/delay.mdx
@@ -0,0 +1,78 @@
+---
+title: "Framework Delay Implementation"
+sidebarTitle: "Delay Action"
+description: "Implementation guide for delay steps in Novu Framework with code examples and best practices."
+---
+
+The delay action awaits a specified amount of time before moving on to trigger the following steps of the workflow.
+
+Learn more about the [Delay](/workflow/delay).
+
+## Common usecases
+
+- Waiting for X amount of time before sending the message
+- Wait for a short period of time before sending a push message in case the user seen the notification in the Inbox Component
+- Allow the user some time to cancel an action that generated a notification
+
+## Adding a delay step
+Delay steps can be inserted at any stage of your workflow execution, they can happen after or before any action. The workflow execution will be halted for the given amount of time and then resumed to the next step in the flow.
+
+The action can also be skipped using the skip parameter conditionally to allow more complex usecases of when to wait and when to send an email immediately.
+
+
+
+Here, we are delaying the execution of the next step by 1 day and skipping the delay step if the isCriticalMessage function returns true.
+
+```typescript
+await step.delay(
+ "delay",
+ () => {
+ return {
+ type: "regular"
+ unit: "days",
+ amount: 1,
+ };
+ },
+ {
+ skip: () => isCriticalMessage(),
+ }
+);
+```
+
+
+
+Here, we are delaying the execution of the in-app step by 30 minutes and sending the in-app notification only if the subscriber has `goalReminderInAppAllowed` set to `true` for subscriber. If during 30 minutes delay window, subscriber sets `goalReminderInAppAllowed` to `false`, the in-app step will be skipped.
+
+```typescript
+export const goalReminderInAppAfterDelay = workflow(
+ "goal-reminder-in-app-after-delay",
+ async ({ step, subscriber }) => {
+ await step.delay("delay-step", async () => {
+ return {
+ type: "regular",
+ amount: 30,
+ unit: "minutes",
+ };
+ });
+
+ await step.inApp(
+ "in-app-step",
+ async () => {
+ return {
+ subject: `Don't Forget Your Fitness Goals Today!`,
+ body: `Hey ${subscriber.firstName}, it's been a while since you logged your
+ last activity. Keep up the momentum and complete your workout to stay on
+ track with your goals!`,
+ };
+ },
+ {
+ skip: () => (subscriber as any).data?.goalReminderInAppAllowed === false,
+ }
+ );
+ }
+);
+```
+
+
+
+Changing the step content after triggering the workflow with delay step will not affect the existing pending delayed notification content.
\ No newline at end of file
diff --git a/framework/deployment/actions.mdx b/framework/deployment/actions.mdx
new file mode 100644
index 00000000..4486b471
--- /dev/null
+++ b/framework/deployment/actions.mdx
@@ -0,0 +1,38 @@
+---
+title: "GitHub Actions"
+---
+
+Learn how to deploy your Novu workflows with our built-in GitHub Action command:
+
+```yaml
+name: Deploy workflow State to Novu
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - main
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ steps:
+ # https://github.com/novuhq/actions-novu-sync
+ - name: Sync State to Novu
+ uses: novuhq/actions-novu-sync@v2
+ with:
+ # The secret key used to authenticate with Novu Cloud
+ # To get the secret key, go to https://web.novu.co/api-keys.
+ # Required.
+ secret-key: ${{ secrets.NOVU_SECRET_KEY }}
+
+ # The publicly available endpoint hosting the bridge application
+ # where notification entities (eg. workflows, topics) are defined.
+ # Required.
+ bridge-url: ${{ secrets.NOVU_BRIDGE_URL }}
+
+ # The Novu Cloud API URL to sync with.
+ # Optional.
+ # Defaults to https://api.novu.co
+ api-url: https://api.novu.co
+```
diff --git a/framework/deployment/cli.mdx b/framework/deployment/cli.mdx
new file mode 100644
index 00000000..6e6c99c5
--- /dev/null
+++ b/framework/deployment/cli.mdx
@@ -0,0 +1,34 @@
+---
+title: "CLI"
+---
+
+The Novu CLI provides a mechanism for you to synchronize your workflows into Novu Cloud so that non-technical Novu users can control the workflow and Step-level controls and enable your workflows to be triggered via Novu API.
+
+
+
+```bash
+npx novu@latest sync \
+ --bridge-url \
+ --secret-key \
+ --api-url https://api.novu.co
+```
+
+
+```bash
+npx novu@latest sync \
+ --bridge-url \
+ --secret-key \
+ --api-url https://eu.api.novu.co
+```
+
+
+
+- If your application api server is running at URL **https://api.domain.com** and **/api/novu** endpoint is serving Novu workflows created using Novu Framework, then ``in above command will be `https://api.domain.com/api/novu`
+
+- If your application is running in local machine with a local studio server, the tunnel URL can be used as ``. The tunnel URL follows the format of `https://.novu.sh/api/novu`, and example is `https://041e553c-0dbf-47e0-8ffa-c4536f390145.novu.sh/api/novu`. In this example `041e553c-0dbf-47e0-8ffa-c4536f390145` is the unique tunnel identifier which is generated for each Novu user when starting a tunnel via `npx novu@latest dev`. The tunnel identifier is persisted to your local machine to guarantee the same tunnel URL each time you invoke `npx novu@latest dev`.
+
+## Using vercel preview url
+
+In free tier, vercel preview urls for non production deployments are not publicly accessible. You will need to enable [Protection Bypass for Automation](https://vercel.com/docs/security/deployment-protection/methods-to-bypass-deployment-protection/protection-bypass-automation#protection-bypass-for-automation) from settings to make the preview url publicly accessible. Use vercel generated secret key in query params with bridge url to make the bridge url accessible to novu.
+
+Example: `https://my-app-preview-url.vercel.app/api/novu?x-vercel-protection-bypass=BYPASS_SECRET_KEY`
\ No newline at end of file
diff --git a/framework/deployment/production.mdx b/framework/deployment/production.mdx
new file mode 100644
index 00000000..5d6a67c4
--- /dev/null
+++ b/framework/deployment/production.mdx
@@ -0,0 +1,29 @@
+---
+title: "Production Deployment Guide"
+sidebarTitle: "Going to Production"
+description: "Learn how to deploy your Novu Framework application to production including networking, security, and HMAC verification setup."
+---
+
+## Networking
+
+Novu Cloud workers will need to be able to communicate with your [Bridge Endpoint](/concepts/endpoint). You will need to ensure that your firewall rules allow traffic from the internet.
+Due to the autoscaling nature of Novu Cloud, we don't have a set of IP Addresses that you can whitelist.
+
+## Security
+
+Novu Cloud workers are GDPR, SOC2 type II and ISO 27001 compliant. We take security very seriously and have implemented a number of security measures to ensure that your data is safe.
+Novu Framework has a builtin security mechanism that ensures that the requests are authentic from Novu Cloud using an HMAC signature.
+
+HMAC Verification is turned on by default for all "production" NODE_ENV environments
+
+
+ For `NODE_ENV=development` the HMAC validation is turned off, for the Studio to be able to reach your endpoint.
+
+
+The `Novu-Signature` header included in each signed event contains a timestamp and one or more signatures that we verify.
+The timestamp is prefixed by `t=`, and each signature is prefixed by a scheme.
+Schemes start with `v`, followed by an integer. Currently, the only valid live signature scheme is v1.
+
+{/* todo add an example of the x-novu-signature header here */}
+
+Handling the signature verification is done by the Novu Framework, so you don't need to perform any action.
diff --git a/framework/deployment/syncing.mdx b/framework/deployment/syncing.mdx
new file mode 100644
index 00000000..39b7bd1a
--- /dev/null
+++ b/framework/deployment/syncing.mdx
@@ -0,0 +1,43 @@
+---
+title: "Syncing your local changes to the Novu Cloud Dashboard"
+sidebarTitle: "Overview"
+---
+
+Novu operates in a multi environment setup, with the currently available environments:
+
+- **Local Studio** - Running against your local machine, this is where you can create, edit, and preview workflows.
+- **Development** - Acts as a Staging environment, where your non-technical peers can view and modify controls.
+- **Production** - For triggering workflows to your customers.
+
+## Sync changes to Novu Cloud
+
+Novu Framework operates in a GitOps model. This means that the source of truth for your workflows and configurations are located in your Git as Code.
+
+The general workflow for pushing changes to Novu Cloud is as follows:
+
+- Create a feature branch
+- Develop workflows locally in your bridge application
+- Sync changes to the Development environment to test e2e
+- Merge the feature branch to your `dev` branch
+ - This will trigger a CI/CD pipeline that will deploy the changes to the Development environment
+- Test the changes in the Development environment
+- Merge the `dev` branch to the `main` branch
+ - This will trigger a CI/CD pipeline that will deploy the changes to the Production environment
+
+## CI/CD Integrations
+
+Novu currently supports the following CI integrations:
+
+- **GitHub Actions** - [Direct Integration](/framework/deployment/actions)
+- **GitLab CI** - Using our [CLI command](/framework/deployment/cli)
+- **Jenkins** - Using our [CLI command](/framework/deployment/cli)
+- **CircleCI** - Using our [CLI command](/framework/deployment/cli)
+- **Bitbucket Pipelines** - Using our [CLI command](/framework/deployment/cli)
+- **Azure DevOps** - Using our [CLI command](/framework/deployment/cli)
+- **Travis CI** - Using our [CLI command](/framework/deployment/cli)
+- **Other** - For any other CI/CD tool, you can use our [CLI command](/framework/deployment/cli)
+
+
+ Direct integration with other CI/CD tools is on our roadmap. If you would like
+ to see a specific CI/CD tool integrated, please reach out to us.
+
diff --git a/framework/digest.mdx b/framework/digest.mdx
new file mode 100644
index 00000000..7cc05be4
--- /dev/null
+++ b/framework/digest.mdx
@@ -0,0 +1,55 @@
+---
+title: "Digest Action for Framework Based Workflows"
+sidebarTitle: "Digest action"
+---
+
+You can use the Digest Engine to collect multiple events to a single message. Learn more about the [Digest Engine](/workflow/digest).
+
+## Defining a digest step
+
+```typescript
+const digestResult = await step.digest("digest", async () => {
+ return {
+ unit: "days", // 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'months'
+ amount: 3, // the number of units to digest events for
+ };
+});
+```
+
+## Writing digest content
+
+In many cases, you will need to access all the digested events payload in order to show the user all or parts of the events included in this digest.
+
+**For example:** “John and 5 others liked your photo.”
+
+The digest function returns an array of triggers that have been digested.
+You can use this array to perform any necessary actions on the digested triggers.
+Like Sending and email, or updating a database.
+
+```typescript
+const { events } = await step.digest("digest-3-days", async () => {
+ return {
+ unit: "days", // 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'months'
+ amount: 3, // the number of units to digest events for
+ };
+});
+
+await step.email("send-email", async () => {
+ const eventCount = events.length;
+
+ return {
+ subject: "Digest Email",
+ body: `You have ${eventCount} new events`,
+ };
+});
+```
+
+ Step controls: At the moment, it is not possible to use digest information in step controls. You can only use it from the code, by creating a custom component for handling digested data.
+
+The digest step returns an object with events array. Each event in the array has the following properties:
+
+- **id** - The job id of the digested event
+- **time** - The time when the event was triggered
+- **payload** - The original payload passed to the event
+
+ Changing the step content after triggering the workflow with digest step will not affect the existing digested events.
diff --git a/framework/email-channel.mdx b/framework/email-channel.mdx
new file mode 100644
index 00000000..53a9f750
--- /dev/null
+++ b/framework/email-channel.mdx
@@ -0,0 +1,19 @@
+---
+title: "Email Channel"
+sidebarTitle: "Email Channel"
+description: "Learn how to configure the Email channel"
+---
+
+The Email Channel is a critical component for delivering notifications reliably. Whether it’s a password reset, an onboarding email, or an alert about account activity, email remains a trusted medium for reaching users.
+Novu simplifies this process, allowing you to focus on implementation rather than infrastructure.
+
+Learn more about the [Email Channel](/integrations/providers/email/overview).
+
+```typescript
+await step.email('email', async () => {
+ return {
+ subject: 'You received a message',
+ body: 'A new post has been created',
+ };
+});
+```
\ No newline at end of file
diff --git a/framework/endpoint.mdx b/framework/endpoint.mdx
new file mode 100644
index 00000000..938f7bfc
--- /dev/null
+++ b/framework/endpoint.mdx
@@ -0,0 +1,84 @@
+---
+title: "Bridge Endpoint"
+---
+
+
+Novu Framework requires a **single** `HTTP` endpoint (`/api/novu` or similar) to be exposed by your application. This endpoint is used to receive events from our **Worker Engine**.
+
+You can view the Bridge Endpoint as a webhook endpoint that Novu will call when it needs to retrieve contextual information for a given subscriber and notification.
+
+Using the `npx novu init` command creates a Bridge application for you with a Bridge Endpoint ready to go.
+
+
+## The `serve` function
+
+We offer framework specific wrappers in form of an exported `serve` function that abstracts away:
+
+- Parsing the incoming request for `GET`, `POST`, `PUT` and `OPTIONS` requests
+- HMAC header authentication
+- Framework specific response and error handling
+
+Currently, we offer `serve` functions for the following frameworks:
+
+- [Next.js](/framework/quickstart/nextjs)
+- [Express.js](/framework/quickstart/express)
+- [Nuxt](/framework/quickstart/nuxt)
+- [h3](/framework/quickstart/h3)
+- [Remix](/framework/quickstart/remix)
+- [Sveltekit](/framework/quickstart/svelte)
+
+## Writing a custom `serve` function
+
+If we currently don't support your framework, you can write a custom `serve` function like the following example:
+
+```ts
+import { type Request, type Response } from "express";
+import { NovuRequestHandler, ServeHandlerOptions } from "@novu/framework";
+
+export const serve = (options: ServeHandlerOptions): any => {
+ const requestHandler = new NovuRequestHandler({
+ frameworkName: "express",
+ ...options,
+ handler: (incomingRequest: Request, response: Response) => ({
+ method: () => incomingRequest.method,
+ headers: (key) => {
+ const header = incomingRequest.headers[key];
+ return Array.isArray(header) ? header[0] : header;
+ },
+ queryString: (key) => {
+ const qs = incomingRequest.query[key];
+ return Array.isArray(qs) ? qs[0] : qs;
+ },
+ body: () => incomingRequest.body,
+ url: () =>
+ new URL(
+ incomingRequest.url,
+ `https://${incomingRequest.headers.get("host") || ""}`
+ ),
+ transformResponse: ({ body, headers, status }) => {
+ Object.entries(headers).forEach(([headerName, headerValue]) => {
+ response.setHeader(headerName, headerValue);
+ });
+
+ return response.status(status).send(body);
+ },
+ }),
+ });
+
+ return requestHandler.createHandler();
+};
+```
+
+
+
+ The tunnel url is the url that is generated by the Studio when you run the `npx novu@latest dev` command. It is used to test your notifications by triggering them from the Studio UI. For local development and testing, you can use the tunnel url as bridge url. For production, you should use deployed application url with bridge endpoint as the bridge url.
+
+
+
+ Yes, the bridge url must be publicly accessible. We recommend having https enabled for the bridge url.
+
+
+
+ Yes, you can use any path you want. However, you need to make sure that the endpoint is used in the bridge url. Bridge url is made of the base url and the endpoint path. If your path is /custom-path/novu and deployed application url is https://my-app.com, then the bridge url will be https://my-app.com/custom-path/novu.
+
+
diff --git a/framework/in-app-channel.mdx b/framework/in-app-channel.mdx
new file mode 100644
index 00000000..10160b60
--- /dev/null
+++ b/framework/in-app-channel.mdx
@@ -0,0 +1,41 @@
+---
+title: "In-App Channel"
+sidebarTitle: "In-App Channel"
+description: "Learn how to configure the In-App channel"
+---
+
+Novu extends beyond traditional notification channels like email, SMS, and push by providing a robust framework for in-app notifications. With Novu, you can build reliable, stateful systems that integrate seamlessly into your applications.
+
+Learn more about the [In-App Channel](/integrations/providers/in-app/overview).
+
+```typescript
+await step.inApp('inbox', async () => {
+ return {
+ subject: 'Welcome to Acme!',
+ body: 'We are excited to have you on board.',
+ avatar: 'https://acme.com/avatar.png',
+ redirect: {
+ url: 'https://acme.com/welcome',
+ target: '_blank',
+ },
+ primaryAction: {
+ label: 'Get Started',
+ redirect: {
+ url: 'https://acme.com/get-started',
+ target: '_self',
+ }
+ },
+ secondaryAction: {
+ label: 'Learn More',
+ redirect: {
+ url: 'https://acme.com/learn-more',
+ target: '_self',
+ }
+ },
+ data: {
+ customData: 'customValue',
+ text: payload.text,
+ },
+ };
+});
+```
\ No newline at end of file
diff --git a/framework/introduction.mdx b/framework/introduction.mdx
new file mode 100644
index 00000000..fcac0095
--- /dev/null
+++ b/framework/introduction.mdx
@@ -0,0 +1,278 @@
+---
+title: "Introduction to Novu Framework"
+sidebarTitle: "Overview"
+description: "Discover how the Novu Framework empowers you to build, customize, and manage advanced notification workflows with a mix of code and no-code capabilities."
+---
+
+The Novu framework allows you to build and manage advanced notification workflows with code, and expose no-code controls for non-technical users to modify.
+
+Workflows are the building blocks of your customer notification experience, they will define the what, when, how and where of your notifications.
+
+## Building blocks
+
+Each Novu workflow is composed of 3 main components:
+
+- **Trigger** - The event that will start the workflow.
+- **Channel steps** - The delivery method of the notification with the content.
+- **Action steps** - Actions that will happen before and after a given channel step is executed.
+
+Let's take a look at a simple example of a workflow that sends an email after one day:
+
+```typescript
+import { workflow } from "@novu/framework";
+
+workflow("sample-workflow", async (step) => {
+ await step.delay("delay", async () => {
+ return {
+ unit: "days",
+ amount: 1,
+ };
+ });
+
+ await step.email("email-step", async () => {
+ return {
+ subject: "Welcome to Novu",
+ body: "Hello, welcome to Novu!",
+ };
+ });
+});
+```
+
+### Trigger
+
+The trigger is the event that will start the workflow. In our current example the `sample-workflow` identifier will be used as our trigger id.
+Workflow identifiers should be unique to your application and should be descriptive of the workflow's purpose.
+
+### Channel steps
+
+Channel Steps are the delivery methods of the notification. In our example, we have an email Channel Step that will send an email with the subject `Welcome to Novu` and the body `Hello, welcome to Novu!`.
+Novu's durable workflow execution engine will select the relevant delivery provider configured for this channel and send the notification with the specified content.
+
+Novu supports a variety of common notification channels out-of-the-box, including **email**, **SMS**, **push**, **inbox**, and **chat**.
+
+To read more about the full list of parameters, check out the [full SDK reference](/framework/typescript/steps/overview).
+
+### Action steps
+
+Action Steps are purpose built functions that help you manage the flow of your workflow. In our example, we have a delay Acction Step that will pause the workflow for one day before sending the email.
+
+You can also use Action Steps to perform other tasks such as fetching data from an external API, updating a database, or sending a notification to another channel.
+
+Novu supported the following Action Steps: **delay**, **custom** and **digest**.
+
+
+## Create a workflow
+
+Here's a bare-bones example of a workflow to send a notification in response to a trigger:
+
+```tsx
+import { workflow } from "@novu/framework";
+
+const myWorkflow = workflow(
+ "new-signup",
+ async ({ step, payload }) => {
+ // Send a welcome email
+ await step.email("send-email", async () => {
+ return {
+ subject: `Welcome to Acme, ${payload.name}`,
+ body: "We look forward to helping you achieve mission.",
+ };
+ });
+ // JSON Schema or compatible library like Zod for validation and type-safety
+ },
+ { payloadSchema: z.object({ name: z.string() }) }
+);
+```
+We'll build on top of this basic code block in the following examples below.
+
+## Just-in-time data fetching
+
+You can add any custom logic needed into your steps. For example, you might want to fetch more information about your new user from a database during the workflow execution. You can achieve this with the following changes:
+
+```tsx
+const myWorkflow = workflow(
+ "new-signup",
+ async ({ step, payload }) => {
+ await step.email("send-email", async () => {
+ // Fetch the user from your database
+ const user = await db.getUser(payload.userId);
+ return {
+ subject: `Welcome to Acme ${user.productTier} tier, ${user.name}.`,
+ // 'Welcome to Acme Business tier, John Doe.'
+ body: "We look forward to helping you achieve mission.",
+ };
+ });
+ },
+ { payloadSchema: z.object({ userId: z.string() }) }
+);
+```
+
+We call this **just-in-time** notification data fetching. It allows you pull in data from the relevant sources during the workflow execution, removing the need to store all of your subscriber data in Novu.
+
+## Multi-step workflow
+
+What if you want to send another update to the same user in one week? But you don't want to send the follow-up if the user opted out. We can add more steps to the workflow to achieve this.
+
+```tsx
+const myWorkflow = workflow(
+ "new-signup",
+ async ({ step, payload }) => {
+ await step.email("send-email", async () => {
+ const user = await db.getUser(payload.userId);
+ return {
+ subject: `Welcome to Acme ${user.productTier} tier, ${user.name}.`,
+ body: "We look forward to helping you achieve mission.",
+ };
+ });
+
+ // Wait for 1 week before continuing. After 1 week, Novu will continue
+ // executing the workflow from here.
+ await step.delay("onboarding-follow-up", async () => ({
+ amount: 1,
+ unit: "weeks",
+ }));
+
+ // 1 week passed, let's follow up with an in-app notification.
+ await step.inApp(
+ "onboarding-follow-up",
+ async (controls) => {
+ const user = await db.getUser(payload.userId);
+ // The `feedbackUrl` can be updated in Novu Web without changing code.
+ // This helps you to create re-usable workflow snippets.
+ return {
+ body: `Hey ${user.name}! How do you like the product?
+
+ Let us know here
+ if you have any questions.`,
+ };
+ },
+ {
+ // Don't follow up if the Subscriber opted out.
+ skip: () => !payload.shouldFollowUp,
+ // Add validation to ensure `feedbackUrl` provided in Web is a `uri`
+ controlSchema: {
+ type: "object",
+ properties: {
+ feedbackUrl: {
+ type: "string",
+ format: "uri",
+ default: "https://acme.com/feedback",
+ },
+ },
+ required: ["feedbackUrl"],
+ additionalProperties: false,
+ } as const,
+ }
+ );
+ },
+ {
+ payloadSchema: {
+ type: "object",
+ properties: {
+ userId: { type: "string" },
+ shouldFollowUp: { type: "boolean", default: true },
+ },
+ required: ["userId", "shouldFollowUp"],
+ additionalProperties: false,
+ } as const,
+ }
+);
+```
+
+With this simple workflow, we:
+
+- Sent a new signup email
+- Waited 1 week
+- Sent a follow-up notification in-app
+
+That's the flexibility that Novu Framework offers.
+
+Read the section on [Controls](/framework/controls) next to learn how to expose Novu's no-code editing capabilities to your peers.
+
+## Payload schema
+
+Payload schema defines the payload passed during the `novu.trigger` method. This is useful for ensuring that the payload is correctly formatted and that the data is valid.
+
+```tsx
+workflow(
+ "comment-on-post",
+ async ({ step, payload }) => {
+ await step.email("send-email", async () => {
+ return {
+ subject: `You have a new comment from: ${payload.author_name}.`,
+ body: render(),
+ };
+ });
+ },
+ {
+ payloadSchema: {
+ // Always `object`
+ type: "object",
+ // Specify the properties to validate. Supports deep nesting.
+ properties: {
+ post_id: { type: "number" },
+ author_name: { type: "string" },
+ comment: { type: "string", maxLength: 200 },
+ },
+ // Specify the array of which properties are required.
+ required: ["post_id", "comment"],
+ // Used to enforce full type strictness, with no rogue properties.
+ additionalProperties: false,
+ // The `as const` is important to let Typescript know that this
+ // type won't change, enabling strong typing on `inputs` via type
+ // inference of the provided JSON Schema.
+ } as const,
+ }
+);
+```
+
+## Tags
+
+Tags are used to categorize the workflows. They also allow you to filter and group notifications for your [Inbox](/inbox/overview) tabs.
+
+```tsx
+workflow(
+ "acme-login-alert",
+ async ({ step, payload }) => {
+ await step.inApp('inbox', async () => {
+ return {
+ subject: 'New Login Detected',
+ body: 'We noticed a login from a new device or location. If this wasn't you, change your password immediately.',
+ };
+ });
+ },
+ {
+ tags: ['security'],
+ }
+);
+
+workflow(
+ "acme-password-change",
+ async ({ step, payload }) => {
+ await step.inApp('inbox', async () => {
+ return {
+ subject: 'Password Changed',
+ body: 'Your password was successfully updated. If you didn't request this, contact support right away.',
+ };
+ });
+ },
+ {
+ tags: ['security'],
+ }
+);
+```
+
+### Passing a payload
+
+Here is an example of the validated payload during trigger:
+
+```tsx
+novu.trigger("comment-on-post", {
+ to: "subscriber_id",
+ payload: {
+ post_id: 1234,
+ author_name: "John Doe",
+ comment: "Looks good!",
+ },
+});
+```
\ No newline at end of file
diff --git a/framework/overview.mdx b/framework/overview.mdx
new file mode 100644
index 00000000..d7190730
--- /dev/null
+++ b/framework/overview.mdx
@@ -0,0 +1,46 @@
+---
+title: "Novu Framework Overview"
+sidebarTitle: "Introduction"
+description: "Learn how to extend Novu's capabilities by building custom notification workflows with code using the Novu Framework."
+---
+
+import { FrameworkTerminal } from "/snippets/framework-terminal.mdx";
+import { ExpressjsIcon } from "/snippets/icons/expressjs.mdx";
+import { H3Icon } from "/snippets/icons/h3.mdx";
+import { LambdaIcon } from "/snippets/icons/lambda.mdx";
+import { NestjsIcon } from "/snippets/icons/nestjs.mdx";
+import { NextjsIcon } from "/snippets/icons/nextjs.mdx";
+import { NuxtIcon } from "/snippets/icons/nuxt.mdx";
+import { RemixIcon } from "/snippets/icons/remix.mdx";
+import { SvelteIcon } from "/snippets/icons/svelte.mdx";
+
+Novu Framework enables you to run a part of the core Novu workflow engine from within your network boundary. This also opens up a powerful new capability: **you can create Novu workflows entirely in code**. This is important because:
+- You can inject custom code that does nearly anything you can imagine as part of a Novu workflow
+- Your code-based workflow lives alongside your application code in source control
+- You can hydrate notification content using local data-sources, reducing what you need to sync outside of your IT boundary
+
+### What it is and how it works
+
+Novu Framework is an application and SDK that you run locally, and communicates natively with the Novu Cloud Worker Engine via the Novu API.
+
+Novu Framework requires a single HTTP webhook-like endpoint (/api/novu or similar) to be exposed by your application. This endpoint is called a **Bridge Endpoint** and is used to receive events from our Worker Engine through an encrypted client-initiated tunnel.
+
+When enabled, Novu Cloud will call the Bridge Endpoint when it needs to retrieve contextual information for a given subscriber and notification.
+
+Use the `npx novu init` command to create a sample Bridge application with a built-in Bridge Endpoint.
+
+
+
+## Quickstart
+To get started with Novu Framework, pick your preferred technology from the list below:
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
diff --git a/framework/push-channel.mdx b/framework/push-channel.mdx
new file mode 100644
index 00000000..8ba6cac7
--- /dev/null
+++ b/framework/push-channel.mdx
@@ -0,0 +1,19 @@
+---
+title: "Push Channel"
+sidebarTitle: "Push Channel"
+description: "Learn how to configure the Push channel"
+---
+
+Push notifications are a powerful way to deliver real-time updates, reminders, and personalized messages to your users across mobile and web platforms.
+Whether it's a promotional alert, a system notification, or a critical update, push notifications are key to enhancing engagement and retention.
+
+Learn more about the [Push Channel](/integrations/providers/push/overview).
+
+```typescript
+await step.push('push', async () => {
+ return {
+ subject: 'You received a message',
+ body: 'A new post has been created',
+ };
+});
+```
\ No newline at end of file
diff --git a/framework/quickstart/express.mdx b/framework/quickstart/express.mdx
new file mode 100644
index 00000000..0cc571a0
--- /dev/null
+++ b/framework/quickstart/express.mdx
@@ -0,0 +1,53 @@
+---
+title: "How to send notifications with Express.js and Novu Framework"
+sidebarTitle: "Express.js"
+---
+
+import LocalStudio from "/snippets/quickstart/start-studio.mdx";
+import DeployApp from "/snippets/quickstart/deploy.mdx";
+import TestStep from "/snippets/quickstart/test.mdx";
+import PackagesStep from "/snippets/quickstart/packages.mdx";
+import SecretStep from "/snippets/quickstart/secret.mdx";
+import NextStepsStep from "/snippets/quickstart/next-steps.mdx";
+import WorkflowStep from "/snippets/quickstart/workflow.mdx";
+
+In this guide, we will add a Novu [Bridge Endpoint](/concepts/endpoint) to a Express.js application and send our first test workflow.
+
+
+
+
+
+
+
+ ```typescript app/server/api/novu.ts
+ import { serve } from "@novu/framework/express";
+ import { testWorkflow } from "../novu/workflows";
+
+ app.use(express.json()); // Required for Novu POST requests
+ app.use( "/api/novu", serve({ workflows: [testWorkflow] }) );
+ ```
+
+
+
+
+
+ Add a `novu` folder in your app folder as such ```novu/workflows.ts``` that will contain your workflow definitions.
+
+
+
+
+
+ Start your Express server with the Novu Endpoint configured.
+
+ If your Express application is running on other than `4000` port, restart the `npx novu dev` command with the port:
+
+ ```tsx
+ npx novu@latest dev --port
+ ```
+
+
+
+
+
+
+
diff --git a/framework/quickstart/h3.mdx b/framework/quickstart/h3.mdx
new file mode 100644
index 00000000..971d0330
--- /dev/null
+++ b/framework/quickstart/h3.mdx
@@ -0,0 +1,58 @@
+---
+title: "How to send notifications with H3 and Novu Framework"
+sidebarTitle: "H3"
+---
+
+import LocalStudio from "/snippets/quickstart/start-studio.mdx";
+import DeployApp from "/snippets/quickstart/deploy.mdx";
+import TestStep from "/snippets/quickstart/test.mdx";
+import PackagesStep from "/snippets/quickstart/packages.mdx";
+import SecretStep from "/snippets/quickstart/secret.mdx";
+import NextStepsStep from "/snippets/quickstart/next-steps.mdx";
+import WorkflowStep from "/snippets/quickstart/workflow.mdx";
+
+In this guide, we will add a Novu [Bridge Endpoint](/concepts/endpoint) to a H3 application and send our first test workflow.
+
+
+
+
+
+
+ ```typescript app/server/api/novu.ts
+ import { createApp, eventHandler, toNodeListener } from "h3";
+ import { serve } from "@novu/framework/h3";
+ import { createServer } from "node:http";
+ import { testWorkflow } from "./novu/workflows";
+
+ const app = createApp();
+
+ app.use("/api/novu", eventHandler(serve({ workflows: [testWorkflow] }) ));
+
+ createServer(toNodeListener(app)).listen(4000);
+ ```
+
+
+
+
+
+ Add a `novu` folder in your app folder as such ```novu/workflows.ts``` that will contain your workflow definitions.
+
+
+
+
+
+
+ Start your H3 server with the Novu Endpoint configured.
+
+ If your H3 application is running on other than `4000` port, restart the `npx novu dev` command with the port:
+
+ ```tsx
+ npx novu@latest dev --port
+ ```
+
+
+
+
+
+
+
diff --git a/framework/quickstart/lambda.mdx b/framework/quickstart/lambda.mdx
new file mode 100644
index 00000000..6bb61a8a
--- /dev/null
+++ b/framework/quickstart/lambda.mdx
@@ -0,0 +1,55 @@
+---
+title: "How to send notifications with AWS Lambda and Novu Framework"
+sidebarTitle: "AWS Lambda"
+---
+
+import LocalStudio from "/snippets/quickstart/start-studio.mdx";
+import DeployApp from "/snippets/quickstart/deploy.mdx";
+import TestStep from "/snippets/quickstart/test.mdx";
+import PackagesStep from "/snippets/quickstart/packages.mdx";
+import SecretStep from "/snippets/quickstart/secret.mdx";
+import NextStepsStep from "/snippets/quickstart/next-steps.mdx";
+import WorkflowStep from "/snippets/quickstart/workflow.mdx";
+
+In this guide, we will add a Novu [Bridge Endpoint](/concepts/endpoint) to a AWS Lambda application and send our first test workflow.
+
+
+
+
+
+
+ ```typescript src/functions/api/novu.ts
+ import { serve } from "@novu/framework/lambda";
+ import { workflow } from "@novu/framework";
+ import { testWorkflow } from "../novu/workflows";
+
+ module.exports.novu = serve({
+ workflows: [testWorkflow],
+ });
+ ```
+
+
+
+
+
+ Add a `novu` folder in your app folder as such `novu/workflows.ts` that will contain your workflow definitions.
+
+
+
+
+
+
+ Start your AWS Lambda server with the Novu Endpoint configured.
+
+ If your Local Lambda application is running on other than `4000` port, restart the `npx novu dev` command with the port:
+
+ ```tsx
+ npx novu@latest dev --port
+ ```
+
+
+
+
+
+
+
diff --git a/framework/quickstart/nestjs.mdx b/framework/quickstart/nestjs.mdx
new file mode 100644
index 00000000..49218e33
--- /dev/null
+++ b/framework/quickstart/nestjs.mdx
@@ -0,0 +1,148 @@
+---
+title: "How to send notifications with NestJS and Novu Framework"
+sidebarTitle: "NestJS"
+---
+
+import LocalStudio from "/snippets/quickstart/start-studio.mdx";
+import DeployApp from "/snippets/quickstart/deploy.mdx";
+import TestStep from "/snippets/quickstart/test.mdx";
+import PackagesStep from "/snippets/quickstart/packages.mdx";
+import SecretStep from "/snippets/quickstart/secret.mdx";
+import NextStepsStep from "/snippets/quickstart/next-steps.mdx";
+import WorkflowStep from "/snippets/quickstart/workflow.mdx";
+
+In this guide, we will add a Novu [Bridge Endpoint](/concepts/endpoint) to a NestJS application and send our first test workflow.
+
+
+
+
+
+ The `NovuModule` is a NestJS module that registers the Novu Endpoint in your application.
+
+ The following example does not support NestJS dependency injection. If you need to `@Injectable` dependencies in your workflow definition, see [Advanced Usage](#advanced-usage-dependency-injection).
+
+
+ ```typescript src/app.module.ts
+ import { Module } from '@nestjs/common';
+ import { NovuModule } from '@novu/framework/nest';
+ import { testWorkflow } from './novu/workflows';
+
+ @Module({
+ imports: [
+ NovuModule.register({
+ apiPath: '/api/novu',
+ workflows: [testWorkflow],
+ }),
+ ],
+ })
+ export class AppModule {}
+ ```
+
+
+
+
+
+ Add a `novu` folder in your `src` folder as such ```src/novu/workflows.ts``` that will contain your workflow definitions.
+
+
+
+
+
+ Start your NestJS application with the Novu Endpoint configured.
+
+ If your NestJS application is running on other than `4000` port, restart the `npx novu dev` command with the port:
+
+ ```tsx
+ npx novu@latest dev --port
+ ```
+
+
+
+
+
+
+
+
+## Advanced Usage (Dependency Injection)
+
+If you need to inject dependencies into your workflow definition, you can use the `registerAsync` method.
+
+Add the `NovuModule` using the `registerAsync` method to your `AppModule`.
+
+```typescript src/app.module.ts
+import { Module } from '@nestjs/common';
+import { NovuModule } from '@novu/framework/nest';
+import { NotificationService } from './notification.service';
+import { UserService } from './user.service';
+
+@Module({
+ imports: [
+ NovuModule.registerAsync({
+ imports: [AppModule],
+ useFactory: (notificationService: NotificationService) => ({
+ apiPath: '/api/novu',
+ workflows: [notificationService.welcomeWorkflow()],
+ }),
+ inject: [NotificationService],
+ }),
+ ],
+ providers: [NotificationService, UserService],
+ exports: [NotificationService],
+})
+export class AppModule {}
+```
+
+For example, you might need to inject a service that fetches the user's name from a database. This is useful when you need to fetch data in realtime during the execution of your workflow.
+
+An example `UserService` is available below with hardcoded values, but in a real-world application you might use a database or an external API to fetch the user's name.
+
+```typescript src/user.service.ts
+import { Injectable } from '@nestjs/common';
+
+@Injectable()
+export class UserService {
+ getUser(id: string) {
+ return {
+ name: 'John Doe',
+ email: `john.doe.${id}@example.com`,
+ };
+ }
+}
+```
+
+Finally, configure your `NotificationService` to use the injected `UserService`.
+
+```typescript src/notification.service.ts
+import { Injectable } from '@nestjs/common';
+import { workflow } from '@novu/framework';
+import { z } from 'zod';
+import { UserService } from './user.service';
+
+@Injectable()
+export class NotificationService {
+ constructor(private readonly userService: UserService) {}
+
+ public welcomeWorkflow() {
+ return workflow(
+ 'welcome-email',
+ async ({ step, payload }) => {
+ await step.email('send-email', async () => {
+ const user = this.userService.getUser(payload.userId);
+
+ return {
+ subject: `Hello, ${user.name}`,
+ body: `We are glad you are here!`,
+ };
+ });
+ },
+ {
+ payloadSchema: z.object({
+ userId: z.string(),
+ }),
+ }
+ );
+ }
+}
+```
+
+A full example NestJS application demonstrating dependency injection is available [here](https://github.com/novuhq/novu/tree/next/playground/nestjs).
diff --git a/framework/quickstart/nextjs.mdx b/framework/quickstart/nextjs.mdx
new file mode 100644
index 00000000..23e30d96
--- /dev/null
+++ b/framework/quickstart/nextjs.mdx
@@ -0,0 +1,155 @@
+---
+title: "How to send notifications with Next.js and Novu Framework"
+sidebarTitle: "Next.js"
+---
+
+import LocalStudio from "/snippets/quickstart/start-studio.mdx";
+import DeployApp from "/snippets/quickstart/deploy.mdx";
+import TestStep from "/snippets/quickstart/test.mdx";
+import NextStepsStep from "/snippets/quickstart/next-steps.mdx";
+
+In this guide, we will add a Novu [Bridge Endpoint](/concepts/endpoint) to a Next.js application and send our first test workflow.
+
+
+
+
+ This link can be copied right from the onboarding guide on the Novu Studio or can always be copied from the [API Keys](https://dashboard-v2.novu.co/api-keys) page on the Novu Dashboard.
+
+ ```bash
+ npx novu init --secret-key=
+ ```
+
+ The sample application will create an `.env` file containing the `NOVU_SECRET_KEY` environment variable required
+ for securing your endpoint. And a sample workflow demonstrating the flexibility of Novu using Step Controls.
+
+
+
+ **Install required packages**
+
+ ```bash
+ npm install @novu/framework @react-email/components react-email zod zod-to-json-schema
+ ```
+
+ This will install
+
+ - **`@novu/framework`** SDK Package
+ - **React Email** (Recommended) - For writing your email templates with React
+ - **Zod** (Recommended) - For end-to-end type safety for your Payload and Step Controls
+
+ **Add a Novu API Endpoint**
+
+
+
+ ```typescript App Router (app/api/novu/route.ts)
+ import { serve } from "@novu/framework/next";
+ import { myWorkflow } from "../../novu/workflows";
+
+ export const { GET, POST, OPTIONS } = serve({ workflows: [myWorkflow] });
+ ```
+
+ ```typescript Pages Router (pages/api/novu.ts)
+ import { serve } from '@novu/framework/next';
+ import { testWorkflow } from '../../novu/workflows';
+
+ export default serve({ workflows: [testWorkflow] });
+ ```
+
+
+ **Add a Novu Secret Key Environment Variable**
+
+ Add `NOVU_SECRET_KEY` environment variable to your `.env`
+
+ ```env
+ NOVU_SECRET_KEY=
+ ```
+
+ **Create your workflow definition**
+
+ Add a `novu` folder that will contain your workflow definitions
+
+ ```tsx app/novu/workflows.ts
+ import { workflow } from '@novu/framework';
+ import { renderEmail } from './emails/test-email';
+ import { z } from 'zod';
+
+ export const testWorkflow = workflow('test-workflow', async ({ step, payload }) => {
+ await step.email('send-email', async (controls) => {
+ return {
+ subject: controls.subject,
+ body: renderEmail(payload.userName),
+ };
+ },
+ {
+ controlSchema: z.object({
+ subject: z.string().default('A Successful Test on Novu from {{userName}}'),
+ }),
+ });
+ }, {
+ payloadSchema: z.object({
+ userName: z.string().default('John Doe'),
+ }),
+ });
+ ```
+
+ **Create your React Email Template (Optional)**
+
+ Add a new email template
+
+ ```typescript app/novu/emails/test-email.tsx
+ import {
+ Body,
+ Container,
+ Head,
+ Html,
+ render,
+ } from '@react-email/components';
+ import * as React from "react";
+
+ interface TestEmailProps {
+ name: string
+ }
+
+ export const TestEmailTemplate = ({ name }: TestEmailProps) => {
+ return (
+
+
+
+
+ Hello {name} welcome to your first React E-mail template!
+
+
+
+ );
+ };
+
+ export default TestEmailTemplate;
+
+ export function renderEmail(name: string) {
+ return render();
+ }
+ ```
+
+
+
+
+ To start your boilerplate Next.js server with the Novu Endpoint configured, run the following command:
+
+ ```tsx
+ cd my-novu-app && npm run dev
+ ```
+
+ The sample application will start on [`https://localhost:4000`](https://localhost:4000) and your novu endpoint will be exposed at `/api/novu` served by the Next.js API.
+
+ If your Next.js application is running on other than `4000` port, restart the `novu dev` command with the port:
+
+ ```tsx
+ npx novu@latest dev --port
+ ```
+
+
+
+
+
+
+
+
diff --git a/framework/quickstart/nuxt.mdx b/framework/quickstart/nuxt.mdx
new file mode 100644
index 00000000..3ebffc41
--- /dev/null
+++ b/framework/quickstart/nuxt.mdx
@@ -0,0 +1,55 @@
+---
+title: "How to send notifications with Nuxt and Novu Framework"
+sidebarTitle: "Nuxt"
+---
+
+import LocalStudio from "/snippets/quickstart/start-studio.mdx";
+import DeployApp from "/snippets/quickstart/deploy.mdx";
+import TestStep from "/snippets/quickstart/test.mdx";
+import PackagesStep from "/snippets/quickstart/packages.mdx";
+import SecretStep from "/snippets/quickstart/secret.mdx";
+import NextStepsStep from "/snippets/quickstart/next-steps.mdx";
+import WorkflowStep from "/snippets/quickstart/workflow.mdx";
+
+In this guide, we will add a Novu [Bridge Endpoint](/concepts/endpoint) to a Nuxt application and send our first test workflow.
+
+
+
+
+
+
+ ```typescript app/server/api/novu.ts
+ import { serve } from '@novu/framework/nuxt';
+ import { testWorkflow } from "../novu/workflows";
+
+ export default defineEventHandler(serve({ workflows: [myWorkflow] }));
+ ```
+
+
+
+
+
+ Add a `novu` folder in your app folder as such ```app/server/api/novu.ts``` that will contain your workflow definitions.
+
+
+
+
+
+ Start your Nuxt application with the Novu Endpoint configured.
+
+ ```bash
+ cd my-novu-app && npm run dev
+ ```
+
+ If your Nuxt application is running on other than `4000` port, restart the `npx novu dev` command with the port:
+
+ ```bash
+ npx novu@latest dev --port
+ ```
+
+
+
+
+
+
+
diff --git a/framework/quickstart/remix.mdx b/framework/quickstart/remix.mdx
new file mode 100644
index 00000000..767be0a0
--- /dev/null
+++ b/framework/quickstart/remix.mdx
@@ -0,0 +1,62 @@
+---
+title: "How to send notifications with Remix and Novu Framework"
+sidebarTitle: "Remix"
+---
+
+import LocalStudio from "/snippets/quickstart/start-studio.mdx";
+import DeployApp from "/snippets/quickstart/deploy.mdx";
+import TestStep from "/snippets/quickstart/test.mdx";
+import PackagesStep from "/snippets/quickstart/packages.mdx";
+import SecretStep from "/snippets/quickstart/secret.mdx";
+import WorkflowCode from "/snippets/quickstart/workflow.mdx";
+import NextStepsStep from "/snippets/quickstart/next-steps.mdx";
+
+In this guide, we will add a Novu [Bridge Endpoint](/concepts/endpoint) to a Remix application and send our first test workflow.
+
+
+
+
+
+
+ This guide is based on Remix Offical [Quick Start](https://remix.run/docs/en/main/start/quickstart).
+
+
+ ```typescript app/routes/api.novu.ts
+ import { serve } from "@novu/framework/remix";
+ import { testWorkflow } from "../novu/workflows";
+
+ const handler = serve({
+ workflows: [testWorkflow]
+ });
+
+ export { handler as action, handler as loader };
+ ```
+
+
+
+
+
+ Add a `novu` folder in your app folder as such ```app/novu/workflows.ts``` that will contain your workflow definitions.
+
+
+
+
+
+ To start your Remix server with the Novu Endpoint configured, run the following command:
+
+ ```bash
+ cd my-novu-app && npm run dev
+ ```
+
+ Remix application default port is 5173. For that to work, restart Novu Studio and point it to the right port:
+
+ ```bash
+ npx novu@latest dev --port
+ ```
+
+
+
+
+
+
+
diff --git a/framework/quickstart/svelte.mdx b/framework/quickstart/svelte.mdx
new file mode 100644
index 00000000..6283f635
--- /dev/null
+++ b/framework/quickstart/svelte.mdx
@@ -0,0 +1,55 @@
+---
+title: "How to send notifications with Svelte and Novu Framework"
+sidebarTitle: "Svelte"
+---
+
+import LocalStudio from "/snippets/quickstart/start-studio.mdx";
+import DeployApp from "/snippets/quickstart/deploy.mdx";
+import TestStep from "/snippets/quickstart/test.mdx";
+import PackagesStep from "/snippets/quickstart/packages.mdx";
+import SecretStep from "/snippets/quickstart/secret.mdx";
+import NextStepsStep from "/snippets/quickstart/next-steps.mdx";
+import WorkflowCode from "/snippets/quickstart/workflow.mdx";
+
+In this guide, we will add a Novu [Bridge Endpoint](/concepts/endpoint) to a Svelte application and send our first test workflow.
+
+
+
+
+
+
+
+ ```typescript src/routes/api/novu/+server.ts
+ import { testWorkflow } from '$lib/novu/workflows';
+ import { serve } from '@novu/framework/sveltekit';
+
+ export const { GET, POST, OPTIONS } = serve({ workflows: [testWorkflow] });
+ ```
+
+
+
+
+ Add a `novu` folder in your lib folder as such ```src/lib/novu/workflows.ts``` that will contain your workflow definitions.
+
+
+
+
+
+ To start your Svelte server with the Novu Endpoint configured, run the following command:
+
+ ```tsx
+ cd my-novu-app && npm run dev
+ ```
+
+ Svelte application default port is 5173. For that to work, restart Novu Studio and point it to the right port:
+
+ ```tsx
+ npx novu@latest dev --port
+ ```
+
+
+
+
+
+
+
diff --git a/framework/schema/class-validator.mdx b/framework/schema/class-validator.mdx
new file mode 100644
index 00000000..136d7af3
--- /dev/null
+++ b/framework/schema/class-validator.mdx
@@ -0,0 +1,79 @@
+---
+title: "Integrate Class Validator with your notification workflows"
+sidebarTitle: "Class Validator"
+---
+
+Novu Framework allows you to use [Class Validator](https://www.npmjs.com/package/class-validator) to define the [Control](/framework/controls) and [Payload](/concepts/payload) schemas for your workflows.
+
+## Add Class Validator to your project
+
+
+
+ ```bash
+ npm install class-validator class-validator-jsonschema reflect-metadata
+ ```
+
+ Novu requires the `class-validator-jsonschema` package to generate JSON schemas from your DTOs. You may also need the `reflect-metadata` package.
+
+
+ After installation, the Class Validator DTOs can be used interchangeably with the `controlSchema` and `payloadSchema` options in your workflow definitions.
+
+ ```tsx
+ import { workflow } from '@novu/framework';
+ import { IsString, IsBoolean, IsNotEmpty, IsOptional, Type, NestedValidation } from "class-validator";
+
+ class TestComponent {
+ @IsString()
+ subject: string;
+
+ @IsString()
+ content: string;
+ }
+
+ class TestControlSchema {
+ @IsBoolean()
+ hideBanner: Boolean;
+
+ @IsString()
+ @IsNotEmpty()
+ @IsOptional()
+ subject?: string;
+
+ // Allowing no code control over the component in the Dashboard UI
+ @Type(() => NewSignUpComponent)
+ @NestedValidation({ each: true })
+ @IsOptional()
+ components?: NewSignUpComponent[];
+ }
+
+ class TestPayloadSchema {
+ @IsString()
+ username: string;
+ }
+
+ export const testWorkflow = workflow('test-workflow', async ({ step, payload }) => {
+ await step.email('send-email', async (controls) => {
+ return {
+ subject: controls.subject,
+ body: 'Hello, World!',
+ };
+ },
+ {
+ controlSchema: TestControlSchema,
+ });
+ }, {
+ payloadSchema: TestPayloadSchema,
+ });
+ ```
+
+
+
+
+## Controls and Payload UI
+
+When you define a `controlSchema` for a step, Novu will automatically generate a UI for the controls in the workflow editor.
+
+- **Form Input Title** - Will be derived from the key of the Class Validator schema. Unfortunately Class Validator does not support custom titles at this point.
+- **Form Input Type** - Will be derived from the Class Validator schema type, with support for `string`, `number`, `boolean`, and `enum` and `array` types.
+- **Default Value** - Unfortunately Class Validator does not support default values at this point.
+- **Validation** - Will be derived from the Class Validator schema validation decorators, including `@Min`, `@Max`, `@IsEmail`, `@IsUrl` and etc...
diff --git a/framework/schema/json-schema.mdx b/framework/schema/json-schema.mdx
new file mode 100644
index 00000000..083b7135
--- /dev/null
+++ b/framework/schema/json-schema.mdx
@@ -0,0 +1,352 @@
+---
+title: "JSON Schema"
+---
+
+JSON Schema can be used to define the [workflow payload](/framework/controls) and [step inputs](/framework/typescript/steps/overview).
+It provides a strongly-typed way to define the structure of the data that is expected by the workflow or Step.
+And also as a contract for changing the workflow behaviour using the Platform User Interface.
+
+Learn more about JSON schema at [json-schema.org](https://json-schema.org/).
+
+## Examples
+
+### Simple
+
+```json
+{
+ "type": "object",
+ "required": ["firstName", "lastName"],
+ "properties": {
+ "firstName": {
+ "type": "string",
+ "title": "First name",
+ "default": "Chuck"
+ },
+ "lastName": {
+ "type": "string",
+ "title": "Last name"
+ },
+ "age": {
+ "type": "integer",
+ "title": "Age"
+ }
+ }
+}
+```
+
+### Nested array structure
+
+```json
+{
+ "type": "object",
+ "required": ["title"],
+ "properties": {
+ "title": {
+ "type": "string",
+ "title": "Task list title"
+ },
+ "tasks": {
+ "type": "array",
+ "title": "Tasks",
+ "items": {
+ "type": "object",
+ "required": ["title"],
+ "properties": {
+ "title": {
+ "type": "string",
+ "title": "Title",
+ "description": "A sample title"
+ },
+ "details": {
+ "type": "string",
+ "title": "Task details",
+ "description": "Enter the task details"
+ },
+ "done": {
+ "type": "boolean",
+ "title": "Done?",
+ "default": false
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+### Reference and reuse blocks
+
+```json
+{
+ "definitions": {
+ "address": {
+ "type": "object",
+ "properties": {
+ "street_address": {
+ "type": "string"
+ },
+ "city": {
+ "type": "string"
+ },
+ "state": {
+ "type": "string"
+ }
+ },
+ "required": ["street_address", "city", "state"]
+ },
+ "node": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "children": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/node"
+ }
+ }
+ }
+ }
+ },
+ "type": "object",
+ "properties": {
+ "billing_address": {
+ "title": "Billing address",
+ "$ref": "#/definitions/address"
+ },
+ "shipping_address": {
+ "title": "Shipping address",
+ "$ref": "#/definitions/address"
+ },
+ "tree": {
+ "title": "Recursive references",
+ "$ref": "#/definitions/node"
+ }
+ }
+}
+```
+
+### Any of schemas
+
+```json
+{
+ "type": "object",
+ "properties": {
+ "age": {
+ "type": "integer",
+ "title": "Age"
+ },
+ "items": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "anyOf": [
+ {
+ "properties": {
+ "foo": {
+ "type": "string"
+ }
+ }
+ },
+ {
+ "properties": {
+ "bar": {
+ "type": "string"
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "anyOf": [
+ {
+ "title": "First method of identification",
+ "properties": {
+ "firstName": {
+ "type": "string",
+ "title": "First name",
+ "default": "Chuck"
+ },
+ "lastName": {
+ "type": "string",
+ "title": "Last name"
+ }
+ }
+ },
+ {
+ "title": "Second method of identification",
+ "properties": {
+ "idCode": {
+ "type": "string",
+ "title": "ID code"
+ }
+ }
+ }
+ ]
+}
+```
+
+### One of schema
+
+```json
+{
+ "type": "object",
+ "oneOf": [
+ {
+ "properties": {
+ "lorem": {
+ "type": "string"
+ }
+ },
+ "required": ["lorem"]
+ },
+ {
+ "properties": {
+ "ipsum": {
+ "type": "string"
+ }
+ },
+ "required": ["ipsum"]
+ }
+ ]
+}
+```
+
+### If then else
+
+```json
+{
+ "type": "object",
+ "properties": {
+ "animal": {
+ "enum": ["Cat", "Fish"]
+ }
+ },
+ "allOf": [
+ {
+ "if": {
+ "properties": {
+ "animal": {
+ "const": "Cat"
+ }
+ }
+ },
+ "then": {
+ "properties": {
+ "food": {
+ "type": "string",
+ "enum": ["meat", "grass", "fish"]
+ }
+ },
+ "required": ["food"]
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "animal": {
+ "const": "Fish"
+ }
+ }
+ },
+ "then": {
+ "properties": {
+ "food": {
+ "type": "string",
+ "enum": ["insect", "worms"]
+ },
+ "water": {
+ "type": "string",
+ "enum": ["lake", "sea"]
+ }
+ },
+ "required": ["food", "water"]
+ }
+ },
+ {
+ "required": ["animal"]
+ }
+ ]
+}
+```
+
+### Enum objects
+
+```json
+{
+ "definitions": {
+ "locations": {
+ "enumNames": ["New York", "Amsterdam", "Hong Kong"],
+ "enum": [
+ {
+ "name": "New York",
+ "lat": 40,
+ "lon": 74
+ },
+ {
+ "name": "Amsterdam",
+ "lat": 52,
+ "lon": 5
+ },
+ {
+ "name": "Hong Kong",
+ "lat": 22,
+ "lon": 114
+ }
+ ]
+ }
+ },
+ "type": "object",
+ "properties": {
+ "location": {
+ "title": "Location",
+ "$ref": "#/definitions/locations"
+ },
+ "locationRadio": {
+ "title": "Location Radio",
+ "$ref": "#/definitions/locations"
+ },
+ "multiSelect": {
+ "title": "Locations",
+ "type": "array",
+ "uniqueItems": true,
+ "items": {
+ "$ref": "#/definitions/locations"
+ }
+ },
+ "checkboxes": {
+ "title": "Locations Checkboxes",
+ "type": "array",
+ "uniqueItems": true,
+ "items": {
+ "$ref": "#/definitions/locations"
+ }
+ }
+ }
+}
+```
+
+### Regex validation
+
+The following example matches a simple North American telephone number with an optional area code:
+
+```json
+{
+ "type": "object",
+ "properties": {
+ "phone": {
+ "type": "string",
+ "pattern": "^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$"
+ }
+ }
+}
+```
+
+## Other resources
+
+- [Examples](https://json-schema.org/learn/miscellaneous-examples)
+- [React JSON schema](https://rjsf-team.github.io/react-jsonschema-form/)
+- [JSON schema validator](https://www.jsonschemavalidator.net/)
+- [JSON schema lint](https://jsonschemalint.com/)
diff --git a/framework/schema/zod.mdx b/framework/schema/zod.mdx
new file mode 100644
index 00000000..25bb2246
--- /dev/null
+++ b/framework/schema/zod.mdx
@@ -0,0 +1,54 @@
+---
+title: "Integrate Zod with your notification workflows"
+sidebarTitle: "Zod"
+---
+
+Novu Framework allows you to use [Zod](https://zod.dev/) to define the [Control](/framework/controls) and [Payload](/concepts/payload) schemas for your workflows.
+
+## Add Zod to your project
+
+
+
+ ```bash
+ npm install zod zod-to-json-schema
+ ```
+
+ Novu requires the `zod-to-json-schema` package to generate JSON schemas from your Zod definitions.
+
+
+ After installation, the Zod schemas can be used interchangeably with the `controlSchema` and `payloadSchema` options in your workflow definitions.
+
+ ```tsx
+ import { workflow } from '@novu/framework';
+ import { z } from 'zod';
+
+ export const testWorkflow = workflow('test-workflow', async ({ step, payload }) => {
+ await step.email('send-email', async (controls) => {
+ return {
+ subject: controls.subject,
+ body: 'Hello, World!',
+ };
+ },
+ {
+ controlSchema: z.object({
+ subject: z.string().default('A test subject'),
+ }),
+ });
+ }, {
+ payloadSchema: z.object({
+ userName: z.string(),
+ }),
+ });
+ ```
+
+
+
+
+## Controls and Payload UI
+
+When you define a `controlSchema` for a step, Novu will automatically generate a UI for the controls in the workflow editor.
+
+- **Form Input Title** - Will be derived from the key of the Zod schema. Unfortunately Zod does not support custom titles at this point.
+- **Form Input Type** - Will be derived from the Zod schema type, with support for `string`, `number`, `boolean`, and `enum` and `array` types.
+- **Default Value** - Will be derived from the Zod schema default value.
+- **Validation** - Will be derived from the Zod schema validation rules, including `min`, `max`, `email`, `url`, `regex` and etc...
diff --git a/framework/sms-channel.mdx b/framework/sms-channel.mdx
new file mode 100644
index 00000000..6874b465
--- /dev/null
+++ b/framework/sms-channel.mdx
@@ -0,0 +1,17 @@
+---
+title: "SMS Channel"
+sidebarTitle: "SMS Channel"
+description: "Learn how to configure the SMS channel"
+---
+
+Novu makes SMS notifications simple, scalable, and reliable, enabling seamless integration with your communication stack. Whether you're sending OTPs, updates, or transactional messages, Novu ensures your SMS notifications are delivered efficiently and effectively.
+
+Learn more about the [SMS Channel](/integrations/providers/sms/overview).
+
+```typescript
+await step.sms('sms', async () => {
+ return {
+ body: 'A new post has been created',
+ };
+});
+```
\ No newline at end of file
diff --git a/framework/studio.mdx b/framework/studio.mdx
new file mode 100644
index 00000000..b43aa933
--- /dev/null
+++ b/framework/studio.mdx
@@ -0,0 +1,78 @@
+---
+title: "Local Studio"
+---
+
+The Local Studio is a companion app to the Novu Framework SDK. Its goal is to provide a local environment that lives near your code.
+
+To launch the Local Studio locally you can run the following command in your terminal:
+
+```bash
+npx novu@latest dev
+```
+Learn how to use the `novu` CLI package and the available [CLI flags](#novu-cli-flags) to use for customization
+
+The Dev Studio will be started by default on port 2022, and accessible via: http://localhost:2022
+
+After successfully connecting the Studio to your local [Bridge Endpoint](/concepts/endpoint), you will be able to preview in real time any workflows and content defined in your code. This is ideal for quick prototyping, debugging, styling, and adjusting your workflows before syncing them to Novu Cloud.
+
+## Control and Payload forms
+
+You can quickly modify the Step Controls and workflow Payload to preview your workflow’s different states. This is helpful to quickly debug how the email will behave in case of a missing control, or iterate more complex content structures.
+
+## Syncing State
+
+Syncing state to your Production or Development environment in Novu, is recommended to do via your CI pipeline. However, a sync can be made using the Local Studio for quick experimentation.
+
+Click on the Sync button at the top right corner of the navigation bar. This will open the Sync State modal.
+
+## Tunnel URL
+
+By default the Novu CLI will automatically create a tunnel URL connected to your local computer. This tunnel will proxy any workflow engine requests on our cloud to your local machine.
+
+## Connect Studio to your local server
+
+By default, the Studio will connect to the Novu [Bridge Endpoint](/concepts/endpoint) running on your local machine at `http://localhost:4000/api/novu` if your server is running on a different port or the workflows are served from a different endpoint path you can use the following optional parameters to connect:
+
+```bash
+npx novu@latest dev --port --route
+```
+
+- **YOUR_SERVER_PORT** - This accepts the port number where your server is running. Defaults to 4000.
+- **YOUR_NOVU_ROUTE_PATH** - This is the mounted path of the framework `serve` function. Defaults to `/api/novu`.
+
+## Novu CLI flags
+
+The Novu CLI command `npx novu@latest dev` supports a number of flags:
+
+| Flag | Long form usage example | Description | Default value |
+| ---- | ----------------------- | ----------------------------- | --------------------------- |
+| -p | --port `` | Bridge application port | 4000 |
+| -r | --route `` | Bridge application route | /api/novu |
+| -o | --origin `` | Bridge application origin | http://localhost |
+| -d | --dashboard-url `` | Novu Cloud dashboard URL | https://dashboard-v2.novu.co |
+| -sp | --studio-port `` | Local Studio server port | 2022 |
+| -t | --tunnel `` | Self hosted tunnel url | null |
+| -H | --headless | Run bridge in headless mode | false |
+
+Example: If bridge application is running on port `3002` and Novu account is in `EU` region.
+
+```bash
+npx novu@latest dev --port 3002 --dashboard-url https://eu.dashboard.novu.co
+```
+
+## FAQ
+
+
+
+ It is possible to run the Studio without generating the default tunnel by passing the `--tunnel` flag with the URL of your application.
+
+ ```bash
+ npx novu@latest dev -t http://custom-tunnel-url.ngrok.app
+ ```
+
+
+ While the preview will work, you won't be able to test your notifications by the triggering them from the Studio UI.
+
+
+
+
diff --git a/framework/tags.mdx b/framework/tags.mdx
new file mode 100644
index 00000000..a90c4822
--- /dev/null
+++ b/framework/tags.mdx
@@ -0,0 +1,58 @@
+---
+title: "Framework Tags Implementation"
+sidebarTitle: "Tags"
+description: "Learn how to implement and manage notification tags programmatically using the Novu Framework SDK."
+---
+
+**Tags** act like labels or categories that help you organize and manage notifications in your app. By grouping notifications under specific tags, you can better control how they're filtered, displayed, or managed by both your app and your users.
+
+[Learn more about why and how to use tags](/workflow/tags)
+
+## How to Add Tags to Notifications
+
+Adding tags to a notification is simple and can be done directly in the workflow configuration.
+Here's an example:
+
+```javascript
+workflow(
+ "acme-login-alert",
+ async ({ step, payload }) => {
+ await step.inApp('inbox', async () => {
+ return {
+ subject: 'New Login Detected',
+ body: 'We noticed a login from a new device or location. If this wasn't you, change your password immediately.',
+ };
+ });
+ },
+ {
+ tags: ['security'],
+ }
+);
+
+workflow(
+ "acme-password-change",
+ async ({ step, payload }) => {
+ await step.inApp('inbox', async () => {
+ return {
+ subject: 'Password Changed',
+ body: 'Your password was successfully updated. If you didn't request this, contact support right away.',
+ };
+ });
+ },
+ {
+ tags: ['security'],
+ }
+);
+```
+
+Let's break it down:
+In the above workflows, whenever someone logs in from a new device, or changes their password, the notification is tagged as security.
+
+**Benefits:**
+
+- **Filtered Views**: Users can quickly locate security-related notifications in their inbox.
+- **Custom Preferences**: Users who only want security alerts can opt in or out of that tag category.
+
+
+
+
diff --git a/framework/typescript/client.mdx b/framework/typescript/client.mdx
new file mode 100644
index 00000000..21d3ac11
--- /dev/null
+++ b/framework/typescript/client.mdx
@@ -0,0 +1,58 @@
+---
+title: "Client"
+---
+
+The `Client` is an optional Class you can pass to the `serve` function to override some global settings.
+By default, we will inject a new instance of the `Client` class in your `serve` method with the following defaults:
+
+## Client Interface
+
+
+ Your Novu Secret Key, used to sign the HMAC header to guarantee the
+ authenticity of our requests.
+
+
+
+ This bypasses the HMAC signature verification, required for local development
+ and testing against [Local Studio](/framework/studio).
+
+
+## Environment Variables
+
+Unless specified in the `Client` constructor the `Client` class will look for the following environment variables:
+
+- `NOVU_SECRET_KEY` - Your Novu Secret Key
+- `NOVU_API_URL` - Defaults to `https://api.novu.co`. For EU customers, this should be set to `https://eu.api.novu.co`.
+
+## Development Environment
+
+When your service is running in development mode `process.env.NODE_ENV=development`, the following rules will auto apply:
+
+- `strictAuthentication` will be set to `false`.
+
+## Code Example
+
+```typescript
+import { Client as NovuFrameworkClient } from "@novu/framework";
+import { serve } from "@novu/framework/next";
+import { passwordResetWorkflow } from "./workflows";
+
+export const { GET, POST, OPTIONS } = serve({
+ client: new NovuFrameworkClient({
+ secretKey: process.env.NOVU_SECRET_KEY,
+ strictAuthentication: false,
+ }),
+ workflows: [
+ /* all workflows */
+ passwordResetWorkflow
+ ],
+});
+```
diff --git a/framework/typescript/overview.mdx b/framework/typescript/overview.mdx
new file mode 100644
index 00000000..58d4d376
--- /dev/null
+++ b/framework/typescript/overview.mdx
@@ -0,0 +1,59 @@
+---
+title: "Novu Framework TypeScript SDK"
+description: "Learn how to use Novu's TypeScript SDK to build type-safe notification workflows with advanced features like payload validation and step controls."
+---
+
+import { FrameworkTerminal } from "/snippets/framework-terminal.mdx";
+
+Although you can trigger Novu workflows from any programming language using our Rest API SDKs.
+
+We believe that the best way to build your notification strategy is to treat your templates and workflows as your Notification Design System.
+Building reusable components to be consumed and embedded by your non-technical peers in any combination.
+
+Typescript SDKs enable the creation of stunning channel content like E-mails using modern technologies like React/Vue/etc...
+Treating your emails as a front-end concern opens up a world of possibilities to reuse design tokens, components, and even entire templates across your applications for consistent branding and a cohesive user experience.
+
+Novu Framework was built and optimized with extreme focus on Developer Experience.
+Our `@novu/framework` SDK is written in Typescript, and we recommend using Typescript for your own projects as well.
+
+
+
+## Type-safe workflow payloads
+
+When defining a [workflow payload](/concepts/payload) schema, our SDK will automatically infer it to a Typescript interface.
+
+```typescript
+import { workflow } from "@novu/framework";
+
+const myWorkflow = workflow(
+ "new-signup",
+ async ({ step, payload }) => {
+ await step.email("send-email", () => {
+ return {
+ subject: "Hello World",
+ // The payload object here is type-safe
+ body: `Hi ${payload.name}, welcome to our platform!`,
+ };
+ });
+ },
+ {
+ payloadSchema: { properties: { name: { type: "string" } } },
+ }
+);
+```
+
+## Type safe steps
+
+Similarly, when defining a [step](/framework/typescript/steps/overview) schema, our SDK will automatically infer it to a Typescript interface.
+
+## Step controls
+
+Build and define type safe controls to expose no-code editing capabilities to your teammates.
+
+## Explore the SDK
+
+- [Client](/sdks/framework/typescript/client)
+- [Workflow](/sdks/framework/typescript/workflow)
+- [Steps](/sdks/framework/typescript/steps)
+
+The `@novu/framework` SDK is compatible with Node.js version 20.0.0 and above.
diff --git a/framework/typescript/steps/chat.mdx b/framework/typescript/steps/chat.mdx
new file mode 100644
index 00000000..51c9dbbb
--- /dev/null
+++ b/framework/typescript/steps/chat.mdx
@@ -0,0 +1,32 @@
+---
+title: "Chat Channel Step"
+sidebarTitle: "Chat"
+icon: "comments"
+---
+
+The `chat` step allows you to send a message to a chat channel. This is useful when you need to send a message to a chat channel, such as Slack, Discord, or Microsoft Teams.
+
+### Chat Credentials
+
+In order to send a chat message, you need to set the specific chat provider credentials for the subscriber.
+You can learn more about how to achieve this for each provider in the [Provider Documentation](/integrations/providers/chat/overview).
+
+## Chat Output
+
+
+ ```typescript Chat
+ await step.chat('chat', async () => {
+ return {
+ body: 'A new post has been created',
+ };
+ });
+ ```
+
+
+
+ The message to be sent to the chat channel.
+
+
+## Chat Result
+
+The `chat` step does not return any result object.
diff --git a/framework/typescript/steps/custom.mdx b/framework/typescript/steps/custom.mdx
new file mode 100644
index 00000000..9dc52ff0
--- /dev/null
+++ b/framework/typescript/steps/custom.mdx
@@ -0,0 +1,44 @@
+---
+title: "Custom Action Step"
+sidebarTitle: "Custom"
+description: "Used to execute any custom code as a step in the workflow."
+icon: "bolt"
+---
+
+The `custom` step allows you to execute any custom code as a step in the workflow and persist the result to the workflow state. This is useful when you need to ensure a step is executed only once, or when you need to execute a step that is not supported by the SDK.
+
+The `custom` step is typically used when you need to retrieve data from your own system, such as user data, then use that data for content of subsequent channel step content. By using a `custom` step to persist data once, you can avoid making repeated calls to your system and improve the performance of your workflow whilst reducing load on your system.
+
+
+ ```typescript Custom Step
+ await step.custom('custom', async () => {
+ return {
+ hello: 'world',
+ }
+ }, {
+ outputSchema: z.object({
+ hello: z.string(),
+ }),
+ });
+ ```
+
+
+
+ ```typescript Custom Step Result
+ const { hello } = await step.custom('custom', resolver);
+ ```
+
+
+## Custom Step Outputs
+
+
+ The custom step is a unique step that allows you to specify the output of the
+ step, the output is strongly typed using the `outputSchema`.
+
+
+## Custom Step Result
+
+
+ The custom step returns any object that you responded with in the step
+ resolver function.
+
diff --git a/framework/typescript/steps/delay.mdx b/framework/typescript/steps/delay.mdx
new file mode 100644
index 00000000..079519a7
--- /dev/null
+++ b/framework/typescript/steps/delay.mdx
@@ -0,0 +1,46 @@
+---
+title: "Delay Step TypeScript API"
+sidebarTitle: "Delay"
+description: "TypeScript API reference for implementing delay steps in Novu Framework workflows."
+icon: "watch"
+---
+
+The `delay` step allows you to pause the execution of a workflow for a specified amount of time. This is useful when you need to wait for a certain amount of time before executing the next step in the workflow.
+
+
+ ```typescript Delay Output
+ await step.delay('delay', () => {
+ return {
+ unit: 'days',
+ amount: 1,
+ };
+ });
+ ```
+
+
+
+ ```typescript Delay Result
+ const { duration } = await step.delay('delay-1-week', resolver);
+ ```
+
+
+## Step Outputs
+
+
+ This combined with the unit field determines the amount of time to digest
+ events for.
+
+
+
+ The measurement unit for the amount field.
+
+
+## Step Result
+
+
+ The duration of the delay in milliseconds.
+
diff --git a/framework/typescript/steps/digest.mdx b/framework/typescript/steps/digest.mdx
new file mode 100644
index 00000000..d95ef335
--- /dev/null
+++ b/framework/typescript/steps/digest.mdx
@@ -0,0 +1,150 @@
+---
+title: "Digest Action Step"
+sidebarTitle: "Digest"
+icon: "boxes-stacked"
+---
+
+The `digest` step allows you to group events by a specified key and digest them together. This is useful when you need to group events by a specific key, such as a user ID, and then digest them together.
+
+The result of the `digest` step is an array of events that have been digested together, allowing you to create a single notification for each group of events.
+
+
+ ```typescript Regular
+ await step.digest('digest', async (controls) => {
+ return {
+ amount: 1,
+ unit: 'hours'
+ }
+ });
+ ```
+ ```typescript Scheduled
+ await step.digest('digest', async (controls) => {
+ return {
+ cron: '0 0 * * *',
+ digestKey: 'postId',
+ }
+ });
+ ```
+ ```typescript Look-back Window
+ await step.digest('digest', async (controls) => {
+ return {
+ amount: 1,
+ unit: 'hours',
+ lookBackWindow: {
+ amount: 10,
+ unit: 'minutes',
+ }
+ }
+ });
+ ```
+
+ ```typescript Digest Key
+ await step.digest('digest', async (controls) => {
+ return {
+ amount: 1,
+ unit: 'hours',
+ digestKey: 'postId',
+ }
+ });
+ ```
+
+
+
+
+ ```typescript Digest Result
+ const { events } = await step.digest('digest', resolver);
+
+ const {
+ id,
+ time,
+ payload
+ } = events[0];
+ ```
+
+
+
+## Digest Step Output
+
+### Regular Digest
+
+
+ This combined with the unit field determines the amount of time to digest
+ events for.
+
+
+
+ The measurement unit for the amount field.
+
+
+
+ This key is used to group events for the digest engine, if not provided all events will be digested together on the subscriber level.
+ When provided, events will be grouped by the `subscriberId` and the `digestKey`.
+
+ The digest key must match the `payloadSchema` key provided on the workflow.
+
+
+
+
+ If a `lookbackWindow` will be specified, the digest engine will check if any event was triggered within the past `lookbackWindow` period.
+ If so, it will create a digest. Otherwise, it will send a message immediately and skip the digest creation. Learn more about the Look-Back strategy [here](/workflow/digest#look-back-strategy).
+
+
+
+ An example of a parameter field
+
+
+
+ An example of a parameter field
+
+
+
+
+
+### Scheduled Digest
+
+
+ The [cron expression](https://crontab.guru/) to schedule the digest on a repeating basis.
+
+ `@novu/framework` SDK exports a utility enum called `CronExpression` to help you build cron expressions.
+
+ ```typescript
+ import { CronExpression } from '@novu/framework';
+
+ await step.digest('digest', async (controls) => {
+ return {
+ cron: CronExpression.EVERY_DAY_AT_1AM
+ }
+ });
+ ```
+
+
+
+
+ This key is used to group events for the digest engine, if not provided all
+ events will be digested together on the subscriber level. When provided,
+ events will be grouped by the `subscriberId` and the `digestKey`.
+
+
+## Digest Step Result
+
+
+ An array of triggers that have been digested.
+
+
+
+ The transactionId of the digested event
+
+
+ The time when the event was triggered
+
+
+
+ The original payload passed to the digested event
+
+
+
+
diff --git a/framework/typescript/steps/email.mdx b/framework/typescript/steps/email.mdx
new file mode 100644
index 00000000..6e2afe70
--- /dev/null
+++ b/framework/typescript/steps/email.mdx
@@ -0,0 +1,32 @@
+---
+title: "Email Channel Step"
+sidebarTitle: "Email"
+icon: "envelope"
+---
+
+The `email` step allows you to send an email to a user. This is useful when you need to send an email to a user, such as a welcome email, password reset email, or any other email you need to send.
+
+
+ ```typescript Email
+ await step.email('email', async () => {
+ return {
+ subject: 'You received a message',
+ body: 'A new post has been created',
+ };
+ });
+ ```
+
+
+## Email Step Output
+
+
+ The subject of the email.
+
+
+
+ The HTML body of the email.
+
+
+## Email Step Result
+
+The `email` step does not return any result object.
diff --git a/framework/typescript/steps/inApp.mdx b/framework/typescript/steps/inApp.mdx
new file mode 100644
index 00000000..84042081
--- /dev/null
+++ b/framework/typescript/steps/inApp.mdx
@@ -0,0 +1,163 @@
+---
+title: 'In-App Channel Step'
+sidebarTitle: 'In-App'
+icon: 'inbox'
+---
+
+The `inApp` step allows you to send a message to your user's `` for your web or a mobile app.
+
+
+ ```typescript Inbox
+ await step.inApp('inbox', async () => {
+ return {
+ subject: 'Welcome to Acme!',
+ body: 'We are excited to have you on board.',
+ avatar: 'https://acme.com/avatar.png',
+ redirect: {
+ url: 'https://acme.com/welcome',
+ target: '_blank',
+ },
+ primaryAction: {
+ label: 'Get Started',
+ redirect: {
+ url: 'https://acme.com/get-started',
+ target: '_self',
+ }
+ },
+ secondaryAction: {
+ label: 'Learn More',
+ redirect: {
+ url: 'https://acme.com/learn-more',
+ target: '_self',
+ }
+ },
+ data: {
+ customData: 'customValue',
+ text: payload.text,
+ },
+ };
+ });
+ ```
+
+
+
+ ```typescript Inbox
+ const {
+ seen,
+ read,
+ lastSeenDate,
+ lastReadDate,
+ } = await step.inApp('inbox', resolver);
+ ```
+
+
+## In-App Step Output
+
+
+ The body of the inbox notification. The main content of the notification.
+
+
+ The subject of the inbox notification. This property communicates the subject
+ of the notification to the user.
+
+
+ The avatar shown in the inbox notification. When specified, overrides the
+ avatar of the actor initiating the notification.
+
+
+ The redirect object is used to define the URL to visit when interacting with
+ the notification. This property can be omitted in place of an
+ `onNotificationClick`
+ [handler](/inbox/react/components#handle-notification-click) implemented in
+ the `` component.
+
+
+ The URL to visit when clicking on the notification.
+
+
+ The target attribute specifies where the new window or tab will be opened.
+ This property is optional and defaults to `_blank`. The supported values
+ are: `_self, _blank, _parent, _top, _unfencedTop`.
+
+
+
+
+ Define a primary action to be shown in the inbox notification. The primary
+ action will appear with an accent color.
+
+
+ The label of the action.
+
+
+ The redirect object is used to define the URL to visit when interacting
+ with the notification action buttons. This property can be omitted in
+ place of an `onPrimaryActionClick`
+ [handler](/inbox/react/components#handle-notification-button-clicks)
+ implemented in the `` component.
+
+
+ The URL to visit when clicking on the notification action button.
+
+
+ The target attribute specifies where the new window or tab will be
+ opened. This property is optional and defaults to `_blank`. The
+ supported values are: `_self, _blank, _parent, _top, _unfencedTop`.
+
+
+
+
+
+
+ Define a secondary action to be shown in the inbox notification. The secondary
+ action will appear with a muted color.
+
+
+ The label of the action.
+
+
+ The redirect object is used to define the URL to visit when interacting
+ with the notification action buttons. This property can be omitted in
+ place of an `onSecondaryActionClick`
+ [handler](/inbox/react/components#handle-notification-button-clicks)
+ implemented in the `` component.
+
+
+ The URL to visit when clicking on the notification action button.
+
+
+ The target attribute specifies where the new window or tab will be
+ opened. This property is optional and defaults to `_blank`. The
+ supported values are: `_self, _blank, _parent, _top, _unfencedTop`.
+
+
+
+
+
+
+ Custom data to be sent with the notification. This data can be used to
+ [customize the notification item
+ rendered](/inbox/react/components#custom-notification-item) in the ``.
+
+
+## In-App Step Result
+
+
+ A flag indicating if the notification has been seen. This property is useful
+ when conditionally delivering notifications in subsequent steps via the `skip`
+ function. A notification is marked as seen when the user views the
+ notification.
+
+
+ A flag indicating if the notification has been read. A notification is marked
+ as read when the user confirms the notification.
+
+
+ The date the notification was last seen. This corresponds to the date the
+ `seen` property was set to `true`, or `null` if the notification has not been
+ seen.
+
+
+ The date the notification was last read. This corresponds to the date the
+ `read` property was set to `true`, or `null` if the notification has not been
+ read.
+
diff --git a/framework/typescript/steps/overview.mdx b/framework/typescript/steps/overview.mdx
new file mode 100644
index 00000000..bf427f05
--- /dev/null
+++ b/framework/typescript/steps/overview.mdx
@@ -0,0 +1,162 @@
+---
+title: "Step interface"
+---
+
+
+ ```typescript Control Schema
+ await step.email('stepId', resolver, {
+ controlSchema: z.object({
+ subject: z.string(),
+ components: z.array(z.object({
+ type: z.enum(['text', 'button']),
+ content: z.string(),
+ })),
+ }),
+ });
+ ```
+ ```typescript Skip
+ await step.email('skipped-step', async () => ({
+ subject: 'Hello, world!',
+ body: 'My email message',
+ }), {
+ skip: async (controls) => true,
+ });
+ ```
+ ```typescript Disable Output Sanitization
+ await step.inApp(
+ 'without-sanitization',
+ async () => ({
+ body: 'My in-app message',
+ data: {
+ link: '/pipeline/123?active=true&env=prod',
+ },
+ }),
+ {
+ // Prevent the `&` character in `data.link` from
+ // being converted to `&`
+ disableOutputSanitization: true,
+ }
+ );
+ ```
+ ```typescript Provider Overrides
+ await step.email('provider-override', resolver, {
+ providers: {
+ slack: ({ controls, outputs }) => {
+ return {
+ text: 'A new post has been created',
+ blocks: [{
+ type: 'section',
+ text: {
+ type: 'mrkdwn',
+ text: 'A new post has been created',
+ },
+ }],
+ };
+ }
+ }
+ });
+ ```
+ ```typescript Provider Passthrough
+ await step.email('provider-passthrough', resolver, {
+ providers: {
+ sendgrid: ({ controls, outputs }) => {
+ return {
+ _passthrough: {
+ body: {
+ ip_pool_name: 'my-ip-pool',
+ },
+ headers: {
+ 'Authorization': 'Bearer my-api-key',
+ },
+ query: {
+ 'queryParam': 'queryValue',
+ },
+ }
+ };
+ }
+ }
+ });
+ ```
+
+
+## Channel Steps Interface
+
+All channels follow the same shared interface:
+
+
+ This is the unique identifier for the step in the workflow context. It is used
+ to reference and display the step in the dashboard interface.
+
+
+
+ This is an async function that returns the content of the step which called
+ `Outputs`. Each channel has its own output schema.
+
+
+
+ Additional step configuration.
+
+
+## Options Object
+
+This is an optional configuration object that defines: [Controls Schema](/framework/controls), [Provider Overrides](#provider-overrides), skip and other configurations.
+
+
+ A function that returns a boolean value to skip the step. This is helpful when
+ you want to use previous step results or other custom logic to skip the step
+ from executing.
+
+
+
+ This defined the UI Controls exposed in the dashboard for the step. They can
+ be nested and of any JSON Schema supported structure.
+
+
+
+ This object used to access and override the underlying deliver providers SDKs. This is useful when you want to customize the content of the notification with properties that are unique to the provider.
+
+
+
+ A flag to disable output sanitization for the step. This is useful when you want to return unescaped HTML content to the provider or the `` component.
+
+
+### Providers Overrides Object
+
+This object used to access and override the underlying deliver providers SDKs. This is useful when you want to customize the content of the notification with properties that are unique to the provider.
+
+An example of this is the `slack` provider, which allows you to customize the content of the notification with Slack `blocks` to create a rich notification experience.
+
+```typescript
+type ProvidersOverride = {
+ [key: ProviderEnum]: ProviderCallback;
+};
+
+type ProviderCallback = (
+ params: ProviderOverridesParams
+) => ProviderOverrideOutput | Promise;
+
+type ProviderOverridesParams = {
+ controls: StepControls;
+ output: StepOutput;
+};
+
+type ProviderOverrideOutput = {
+ // A map of the properties used by the Provider.
+ // These properties are strongly typed and validated
+ // against the underlying provider SDK.
+ [key in KnownProviderKey]: KnownProviderValue;
+ // The passthrough object is used to pass through
+ // the original request to the provider.
+ // These properties are not validated.
+ _passthrough?: {
+ body: Record;
+ headers: Record;
+ query: Record;
+ };
+};
+```
+
+The `_passthrough` object and the known provider values are deeply merged prior to sending the request to the provider, with the `_passthrough` object taking precedence.
diff --git a/framework/typescript/steps/push.mdx b/framework/typescript/steps/push.mdx
new file mode 100644
index 00000000..3db5ac7c
--- /dev/null
+++ b/framework/typescript/steps/push.mdx
@@ -0,0 +1,37 @@
+---
+title: "Push Channel Step"
+sidebarTitle: "Push"
+icon: "mobile"
+---
+
+The `push` step allows you to send a message to a push channel. This is useful when you need to send a message to a push-enabled channel, such as a mobile app or web app.
+
+
+ ```typescript Push
+ await step.push('push', async () => {
+ return {
+ subject: 'You received a message',
+ body: 'A new post has been created',
+ };
+ });
+ ```
+
+
+### Push Credentials
+
+In order to send a push notification to a subscriber, you need to set the specific push provider credentials for the subscriber.
+You can learn more about how to achieve this for each provider in the [Provider Documentation](/integrations/providers/push/overview).
+
+## Push Output
+
+
+ The subject of the notification.
+
+
+
+ The message to be sent.
+
+
+## Push Result
+
+The `push` step does not return any result object.
diff --git a/framework/typescript/steps/sms.mdx b/framework/typescript/steps/sms.mdx
new file mode 100644
index 00000000..a7b29aee
--- /dev/null
+++ b/framework/typescript/steps/sms.mdx
@@ -0,0 +1,27 @@
+---
+title: "SMS Channel Step"
+sidebarTitle: "SMS"
+icon: "message-sms"
+---
+
+The `sms` step allows you to send a message to a SMS channel. This is useful when you need to send an SMS message directly to a users device over a mobile network.
+
+## SMS Output
+
+
+ ```typescript SMS
+ await step.sms('sms', async () => {
+ return {
+ body: 'A new post has been created',
+ };
+ });
+ ```
+
+
+
+ The message to be sent.
+
+
+## SMS Result
+
+The `sms` step does not return any result object.
diff --git a/framework/typescript/workflow.mdx b/framework/typescript/workflow.mdx
new file mode 100644
index 00000000..0021cb3f
--- /dev/null
+++ b/framework/typescript/workflow.mdx
@@ -0,0 +1,310 @@
+---
+title: "Workflow Interface"
+sidebarTitle: "Workflow"
+---
+
+
+ ```typescript Define a workflow
+ import { workflow } from "@novu/framework";
+
+ workflow("my-workflow", async ({ step, payload, subscriber }) => {
+ await step.inApp("send-in-app", async () => {
+ return {
+ body: "Hello there",
+ };
+ });
+ }, {
+ payloadSchema: z.object({
+ body: z.string(),
+ }),
+ name: "My Workflow",
+ description: "This is a workflow",
+ tags: ["business", "critical"],
+ preferences: {
+ channels: {
+ inApp: { enabled: true }
+ },
+ },
+ });
+ ```
+
+
+```typescript
+import { workflow } from '@novu/framework';
+
+workflow(
+ workflowId: string,
+ handler: WorkflowHandler,
+ options?: WorkflowOptions
+): WorkflowInstance;
+```
+
+
+ This id should be unique within your organization.
+
+
+
+ The definition function of the workflow.
+
+
+
+ An optional options object for workflow level configurations
+
+
+
+ The schema to validate the event payload against, can be used to provide default values.
+
+
+ The name of the workflow. This is used to display a human-friendly name for the workflow in the Dashboard and `` component. If no value is specified, the `workflowId` will be used as the name.
+
+
+ The description of the workflow. This is used to provide a brief overview of the workflow in the Dashboard.
+
+
+ The tags assigned to the workflow. Tags can be used to filter workflows in the dashboard, and can be used by channels such as Inbox to sort Notifications into different categories for a better user experience.
+
+
+ The preferences for the workflow. Read more about [Workflow Channel Preferences](/concepts/preferences#workflow-channel-preferences).
+
+
+
+
+ A flag specifying if notification delivery is enabled for the workflow.
+ If `true`, notification delivery is enabled by default for all channels.
+ This setting can be overridden by the channel preferences.
+
+
+ A flag specifying if the preferences are read-only.
+ If `true`, the preferences cannot be changed by the subscriber.
+
+
+
+
+ The preferences for the workflow. Read more about [Workflow Channel Preferences](/concepts/preferences).
+
+
+
+
+ A flag specifying if notification delivery is enabled for the in-app channel.
+ If `true`, notification delivery is enabled by default.
+
+
+
+
+
+
+ A flag specifying if notification delivery is enabled for the email channel.
+ If `true`, notification delivery is enabled by default.
+
+
+
+
+
+
+ A flag specifying if notification delivery is enabled for the SMS channel.
+ If `true`, notification delivery is enabled by default.
+
+
+
+
+
+
+ A flag specifying if notification delivery is enabled for the chat channel.
+ If `true`, notification delivery is enabled by default.
+
+
+
+
+
+
+ A flag specifying if notification delivery is enabled for the push channel.
+ If `true`, notification delivery is enabled by default.
+
+
+
+
+
+
+
+
+
+
+
+## Workflow Context
+
+This context is passed by the workflow engine to provide contextual information about current workflow execution.
+
+
+
+
+ The id of the subscriber, as passed during `/events/trigger` request.
+
+
+ Nullable, the first name of the subscriber.
+
+
+ Nullable, the last name of the subscriber.
+
+
+
+
+
+ The payload of the event that triggered the workflow, will be validated
+ against the `payloadSchema` if provided.
+
+
+
+ The object that contains all the step functions, read more at [Step
+ Functions](/framework/typescript/steps/overview).
+
+
+## Workflow Channel Preferences
+
+With Workflow channel preferences, you can control the default delivery preference for a channel and whether a subscriber can change it. Novu will show the subscriber preferences in `` component. Subscribers can enable and disable any active channel in the workflow.
+
+In the `all` object, you can specify default preferences for all channels. The `enabled` field on the `all` object is used as fallback value if a channel is not specified explicitly in `channels`.
+
+The `readOnly` field controls whether subscribers can change the delivery preference for a channel. Critical workflows are defined with `{ readOnly: true }`.
+
+In the `channels` object, you can specify In-App, SMS, Email, Chat, and Push channel preferences. Each channel takes an object with an optional `enabled` flag that controls whether a notification delivery channel is enabled or disabled by default for subscribers.
+
+#### Default values
+
+By default, `enabled` is `true` for all channels. The `readOnly` flag is `false`.
+
+
+These preferences can also be controlled from the Novu Dashboard per workflow. To do so, click on the cog icon at the top right of your screen, and then select the "Preferences" tab.
+
+
+
+
+```typescript
+const newWorkflow = workflow(
+ 'default-preferences',
+ async ({ step }) => {
+ await step.inApp('send-in-app', () => ({
+ body: 'Hello there',
+ }));
+ },
+ {
+ preferences: {
+ {
+ all: { enabled: true, readOnly: false },
+ channels: {
+ inApp: { enabled: true },
+ email: { enabled: true },
+ sms: { enabled: true },
+ chat: { enabled: true },
+ push: { enabled: true },
+ },
+ }
+ },
+ }
+);
+```
+
+
+```typescript
+const newWorkflow = workflow(
+ 'only-in-app-channel',
+ async ({ step }) => {
+ await step.inApp('send-in-app', () => ({
+ body: 'Hello there',
+ }));
+ },
+ {
+ preferences: {
+ {
+ all: { enabled: false },
+ channels: {
+ inApp: { enabled: true },
+ },
+ }
+ },
+ }
+);
+```
+
+
+```typescript
+const newWorkflow = workflow(
+ 'all-enabled-editable',
+ async ({ step }) => {
+ await step.inApp('send-in-app', () => ({
+ body: 'Hello there',
+ }));
+ },
+ {
+ preferences: {
+ {
+ all: { enabled: true },
+ }
+ },
+ }
+);
+```
+
+
+```typescript
+const newWorkflow = workflow(
+ 'all-enabled-read-only',
+ async ({ step }) => {
+ await step.inApp('send-in-app', () => ({
+ body: 'Hello there',
+ }));
+ },
+ {
+ preferences: {
+ {
+ all: { enabled: true, readOnly: true },
+ }
+ },
+ }
+);
+```
+
+
+```typescript
+const newWorkflow = workflow(
+ 'all-disabled-editable',
+ async ({ step }) => {
+ await step.inApp('send-in-app', () => ({
+ body: 'Hello there',
+ }));
+ },
+ {
+ preferences: {
+ {
+ all: { enabled: false, readOnly: false },
+ }
+ },
+ }
+);
+```
+
+
+```typescript
+const newWorkflow = workflow(
+ 'in-app-disabled-editable',
+ async ({ step }) => {
+ await step.inApp('send-in-app', () => ({
+ body: 'Hello there',
+ }));
+ },
+ {
+ preferences: {
+ {
+ channels: {
+ inApp: { enabled: false },
+ },
+ }
+ },
+ }
+);
+```
+
+
diff --git a/getting-started/how-novu-works.mdx b/getting-started/how-novu-works.mdx
new file mode 100644
index 00000000..1c498ca9
--- /dev/null
+++ b/getting-started/how-novu-works.mdx
@@ -0,0 +1,573 @@
+---
+title: "What is Novu?"
+---
+
+Novu is an open-source notification infrastructure platform that greatly reduces the effort and complexity required to implement notifications your users will love, all without the need to build your own notification system.
+
+We designed Novu with both developers and product teams in mind: it’s easy for developers to implement quickly, and simple for less-technical users to interact with and maintain content with a powerful intuitive dashboard.
+
+Novu functions as an abstraction layer between your application and end users, and manages all aspects of notification workflow logic and delivery provider management.
+
+
+
+---
+
+**How does it work?**
+
+Managing notifications across multiple channels is painful. Each notification delivery provider has their own SDK, authentication methods, and API quirks. This complexity dramatically increases as you add additional logic such as delays, digests/batches, or multi-tiered notification approaches.
+
+
+**Without Novu**
+- Learn and maintain different codebases for email, SMS, push, and chat providers
+- Handle varying API response formats and error patterns
+- Keep track of multiple API keys and credentials
+- Build abstraction layers to standardize notification logic
+- Deal with provider-specific rate limits and quotas
+- Maintain separate content formats and hydration methods for each channel and notification type
+
+
+## API & SDKs
+
+Novu eliminates these headaches with a unified API.
+
+Instead of juggling multiple providers' APIs and SDKs, you get a single, consistent interface to control all your notification flows.
+
+
+**With Novu**
+- Trigger notifications across any channel
+- Manage subscriber profiles and preferences
+- Power real-time notification feeds with our [full-stack UI Components libraries](/inbox/overview)
+- Handle notification templates and workflows
+
+And much more.
+
+**This is a robust, highly customizable infrastructure layer that sits between your application and the user's devices and channels.**
+
+
+There are two ways to integrate Novu with your application or website:
+
+
+
+ Use our [server-side SDKs](/sdks/overview) for seamless integration
+
+
+ Make direct [HTTP requests](/api-reference/overview) using your environment's API Secret Key
+
+
+
+Every request you make is automatically routed to the correct [environment](/concepts/environments), ensuring clear separation between **development** and **production** environments.
+
+---
+
+## Workflows
+
+Workflows are the core building blocks of Novu's notification system. They enable you to design sophisticated messaging sequences that can span multiple communication channels like in-app, email, SMS, chat, and push.
+
+Using intuitive logical operators and [action steps](/concepts/workflows#action-step), you can create dynamic notification paths that automatically adapt based on your [end user](/additional-resources/glossary#subscribers) preferences and behavior.
+
+
+**Every notification in Novu originates from a workflow trigger, making workflows the central orchestration layer for your entire messaging infrastructure.**
+
+
+Whether you need to send a simple welcome email or implement a complex multi-step notification sequence with digests, delays, fallbacks, and conditional logic, workflows provide the foundation for managing these communications effectively.
+
+
+
+In the code snippets below, you can see how a workflow trigger API request looks.
+
+
+
+```bash cURL
+curl -X POST https://api.novu.co/v1/events/trigger \
+ -H "Authorization: ApiKey " \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "",
+ "to": {
+ "subscriberId": "",
+ "email": "john@doemail.com",
+ "firstName": "John",
+ "lastName": "Doe",
+ "phone": "+1234567890",
+ "avatar": "AVATAR_URL",
+ },
+ "payload": {
+ "name": "Hello World",
+ "organization": {
+ "logo": "https://happycorp.com/logo.png"
+ }
+ }
+ }'
+```
+
+```javascript Node.js
+import { Novu } from "@novu/node";
+
+const novu = new Novu("");
+
+await novu.trigger("", {
+ to: {
+ subscriberId: "",
+ email: "john@doemail.com",
+ firstName: "John",
+ lastName: "Doe",
+ phone: "+1234567890"
+ avatar: "AVATAR_URL",
+ },
+ payload: {
+ name: "Hello World",
+ organization: {
+ logo: "https://happycorp.com/logo.png",
+ },
+ },
+});
+```
+
+```python Python
+from novu.api import EventApi
+
+url = "https://api.novu.co"
+api_key = ""
+
+novu = EventApi(url, api_key).trigger(
+ name="", # This is the Workflow ID. It can be found on the workflow page.
+ recipients="", # The subscriber ID can be gotten from the dashboard.
+ payload={}, # Your custom Novu payload goes here
+)
+```
+
+```java Java
+import co.novu.common.base.Novu;
+import co.novu.api.common.SubscriberRequest;
+import co.novu.api.events.requests.TriggerEventRequest;
+import co.novu.api.events.responses.TriggerEventResponse;
+
+public class Main {
+ public static void main(String[] args) {
+ String apiKey = "";
+
+ Novu novu = new Novu(apiKey);
+
+ SubscriberRequest subscriberRequest = new SubscriberRequest();
+ subscriberRequest.setEmail("");
+ subscriberRequest.setFirstName("");
+ subscriberRequest.setLastName("");
+ subscriberRequest.setPhone("");
+ subscriberRequest.setAvatar("");
+ subscriberRequest.setSubscriberId("");
+
+ TriggerEventRequest triggerEventRequest = new TriggerEventRequest();
+ triggerEventRequest.setName("");
+ triggerEventRequest.setTo(subscriberRequest);
+ triggerEventRequest.setPayload(Collections.singletonMap("", ""));
+
+ TriggerEventResponse response = novu.triggerEvent(triggerEventRequest);
+ }
+}
+```
+
+```php PHP
+use Novu\SDK\Novu;
+
+$novu = new Novu();
+
+$novu->triggerEvent([
+ 'name' => '',
+ 'payload' => ['customVariables' => 'Hello'],
+ 'to' => [
+ 'subscriberId' => '',
+ 'phone' => '07983882186'
+ ]
+])->toArray();
+```
+
+```ruby Ruby
+require 'novu'
+
+client = Novu::Client.new('')
+
+payload = {
+ 'name' => '',
+ 'payload' => { # optional
+ 'first-name' => 'Adam' # optional
+ },
+ 'to' => {
+ 'subscriberId' => ''
+ },
+ 'transactionId' => '89kjfke9893' #optional
+}
+
+client.trigger_event(payload)
+```
+
+```go Go
+package main
+
+import (
+ "context"
+ "fmt"
+ novu "github.com/novuhq/go-novu/lib"
+ "log"
+)
+
+func main() {
+ subscriberID := ""
+ apiKey := ""
+ eventId := ""
+
+ ctx := context.Background()
+ to := map[string]interface{}{
+ "lastName": "Doe",
+ "firstName": "John",
+ "subscriberId": subscriberID,
+ "email": "john@doemail.com",
+ }
+
+ payload := map[string]interface{}{
+ "name": "Hello World",
+ "organization": map[string]interface{}{
+ "logo": "https://happycorp.com/logo.png",
+ },
+ }
+
+ data := novu.ITriggerPayloadOptions{To: to, Payload: payload}
+ novuClient := novu.NewAPIClient(apiKey, &novu.Config{})
+
+ resp, err := novuClient.EventApi.Trigger(ctx, eventId, data)
+
+ if err != nil {
+ log.Fatal("novu error", err.Error())
+ return
+ }
+}
+```
+
+```net Net
+using Novu.DTO;
+using Novu.Models;
+using Novu;
+
+var novuConfiguration = new NovuClientConfiguration
+{
+ /**
+ * Defaults to https://api.novu.co/v1
+ */
+ Url = "https://novu-api.my-domain.com/v1",
+ ApiKey = "12345",
+};
+
+var novu = new NovuClient(novuConfiguration);
+
+public class OnboardEventPayload
+{
+ [JsonProperty("username")]
+ public string Username { get; set; }
+
+ [JsonProperty("welcomeMessage")]
+ public string WelcomeMessage { get; set; }
+}
+
+var onboardingMessage = new OnboardEventPayload
+{
+ Username = "jdoe",
+ WelcomeMessage = "Welcome to novu-dotnet"
+};
+
+var payload = new EventTriggerDataDto()
+{
+ EventName = "onboarding",
+ To = { SubscriberId = "subscriberId" },
+ Payload = onboardingMessage
+};
+
+var trigger = await novu.Event.Trigger(payload);
+```
+
+
+
+---
+
+To trigger a notification workflow process in Novu, you must specify the intended subscriber(s).
+**This is a fundamental concept in Novu because it determines the routing of your notifications.**
+
+## Subscribers
+
+Subscribers are in most cases users in your application who receive notifications through various channels.
+
+The subscriber is identified through one of two methods:
+
+- A `subscriberId` - this uniquely identifies a specific user in your system (similar to how each user has a unique user_id in a database)
+- A `topicKey` - this represents a channel or category that multiple subscribers can opt into (like a subscription group)
+
+
+
+**When you trigger workflows to subscribers, Novu maintains a cache of their notification-related data, including:**
+
+- Required contact information (email, phone number)
+- Profile details (avatar URL)
+- Platform-specific tokens (push notifications, webhooks)
+- Custom properties (plan type, user role, timezone)
+
+
+Below is a detailed breakdown of the subscriber object:
+
+```json
+{
+ // Core Identifiers
+ "subscriberId": "UNIQUE_USER_IDENTIFIER_IN_YOUR_SYSTEM",
+ "_id": "NOVU_GENERATED_SUBSCRIBER_ID",
+ "_organizationId": "NOVU_GENERATED_ORG_ID",
+ "_environmentId": "NOVU_GENERATED_ENV_ID",
+
+ // Basic Information
+ "firstName": "John",
+ "lastName": "Doe",
+ "email": "john.doe@org.com",
+ "phone": "+98712345670",
+ "avatar": "AVATAR_URL",
+ "locale": "en_US",
+ "avatar": "AVATAR_URL",
+
+ // Custom Data
+ "data": {
+ "custom_key_1": "custom_value_1",
+ "custom_key_2": "custom_value_2"
+ },
+
+ // Communication Channels
+ "channels": [
+ {
+ // Firebase Cloud Messaging configuration
+ "credentials": {
+ "deviceTokens": ["token1", "token2"]
+ },
+ "_integrationId": "NOVU_GENERATED_INTEGRATION_ID",
+ "providerId": "fcm"
+ },
+ {
+ // Discord configuration
+ "credentials": {
+ "webhookUrl": "URL"
+ },
+ "_integrationId": "NOVU_GENERATED_INTEGRATION_ID",
+ "providerId": "discord"
+ }
+ ],
+
+ // System Fields
+ "deleted": false,
+ "createdAt": "2022-10-13T17:40:53.231Z",
+ "updatedAt": "2022-10-13T17:41:53.238Z",
+ "__v": 0,
+ "isOnline": false,
+ "lastOnlineAt": "2022-10-13T17:41:53.238Z",
+ "id": "NOVU_GENERATED_SUBSCRIBER_ID"
+}
+```
+
+---
+
+## Channels
+
+To deliver notifications to subscribers, you need to configure a [channel](/additional-resources/glossary#channels).
+
+A channel in Novu is a configured delivery method for sending notifications to subscribers.
+Each channel connects to a specific delivery [provider](/additional-resources/glossary#providers) that handles the actual notification delivery.
+
+**Sending notifications with Novu:**
+
+
+
+ Novu allows you to set up multiple notification delivery providers and integrate them into your notification workflows.
+
+ Each [channel](/workflow/channel-steps) corresponds to a configured provider that you can use to deliver notifications in production.
+
+
+ Use these channels individually or combine them in workflows for multi-channel notification delivery.
+
+
+ Make an API call or use a `cURL` command to trigger your workflow to a `subscriberId`.
+
+
+
+
+For example, you might configure an email channel using SendGrid as the provider and an SMS channel using Twilio. These channels can be used independently or together in workflows to achieve multi-channel delivery.
+
+
+Here's an overview of our supported channels and providers:
+
+
+
+ Send emails through leading providers including [Amazon SES](https://aws.amazon.com/ses/), [Mailersend](https://www.mailersend.com/), [Mailgun](https://www.mailgun.com/), [Mailjet](https://www.mailjet.com/), [Mailtrap](https://mailtrap.io/), [Mandrill](https://mandrillapp.com/), [Postmark](https://postmarkapp.com/), [Resend](https://resend.com/), [Sendgrid](https://sendgrid.com/), [SMTP](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol), and [Sparkpost](https://www.sparkpost.com/).
+
+ [Learn more about email channel →](/integrations/providers/email/overview)
+
+
+
+ Deliver SMS messages through trusted providers including [Africa's Talking](https://africastalking.com/), [Amazon SNS](https://aws.amazon.com/sns/), [Mailersend](https://www.mailersend.com/), [MessageBird](https://messagebird.com/), [Plivo](https://www.plivo.com/), [Sinch](https://www.sinch.com/), [Telnyx](https://telnyx.com/), [Twilio](https://www.twilio.com/), and [Vonage](https://www.vonage.com/).
+
+ [Learn more about SMS channel →](/integrations/providers/sms/overview)
+
+
+
+ Send push notifications across major platforms including [Apple Push Notification Service](https://developer.apple.com/notifications/) for iOS, [Expo](https://expo.dev/) for React Native, and [Firebase Cloud Messaging](https://firebase.google.com/products/cloud-messaging) for Android.
+
+ [Learn more about Push channel →](/integrations/providers/push/overview)
+
+
+
+ Integrate with popular messaging platforms including [Discord](https://discord.com/), [Microsoft Teams](https://www.microsoft.com/teams), [Slack](https://slack.com/), [Telegram](https://telegram.org/), and [WhatsApp](https://www.whatsapp.com/).
+
+ [Learn more about Chat channel →](/integrations/providers/chat/overview)
+
+
+
+ Build native notification experiences with our real-time notification feed API, ready-to-use UI components, and custom integration options for seamless implementation in your application.
+
+ [Learn more about In-app channel →](/integrations/providers/in-app/overview)
+
+
+
+---
+
+## Templates
+
+Novu allows you to design notification templates using an intuitive combination of a template editor and the Liquid templating language to create dynamic, customizable notification templates.
+
+**This unified approach solves two common challenges:**
+
+
+
+ Consolidate templating languages into a single system, removing the complexity of managing different syntaxes for different providers.
+
+ A single templating language provides consistency across your communication channels without the need for constant adjustments.
+
+ Novu’s built-in engine works seamlessly across email, SMS, push, chat, and in-app notifications.
+
+ Define variables like `{{subscriber.firstName}}` or `{{payload.orderTotal}}` once, and let Novu automatically handle the rest.
+
+
+ Manage notification content templates in a dedicated control center, where teams can:
+
+ - Edit templates using an intuitive notification template editor
+ - Test notifications with actual subscriber data to ensure accuracy
+ - Switch between channels (email, SMS, push) while keeping content and hydration consistent
+ - Set up translation variations for different languages
+ - Allow non-technical resources to adjust and update content without ever breaking workflow logic
+
+
+
+---
+
+## Actions
+
+Adding action steps (or functions) to your workflows is a powerful way to create more complex and unique notification experiences for your users.
+
+Each workflow can combine multiple action steps to model complex logic that creates better notification experiences. You can combine the following action steps with any number of channel steps to create personalized notifications for your users:
+
+ ### Digest
+ Digest actions combine multiple notifications into a single, consolidated message. This helps prevent notification fatigue and provides better context for your users.
+
+ - Group related notifications within a specified time window
+ - Customize aggregation logic and formatting
+
+ [Learn more about Digest action →](/workflow/digest)
+
+ ### Delay
+ Delay actions introduce controlled waiting periods between steps in your workflow. This enables better timing and sequencing of notifications.
+
+ - Set precise delay durations (seconds to months)
+ - Support for cron-style scheduling
+
+ [Learn more about Delay action →](/workflow/delay)
+
+ ### Custom
+ Custom actions allow you to integrate specialized business logic directly into your notification workflows while maintaining workflow durability and state management.
+
+ - Execute arbitrary code within the workflow context
+ - Can serve as a trigger for other workflows
+ - Pass data from your application's database into the workflow
+
+ [Learn more about Custom action →](/workflow/custom)
+
+---
+
+## Step Conditions
+
+In addition to combining channel steps and action steps to create complex workflows, you can augment these steps with additional logic based on the subscriber, inputs from your application, or the status of previous workflow steps. These are called step conditions.
+
+ **Coming Soon...**
+
+Step conditions are powerful rules that determine when a workflow executes channel and action steps.
+
+They evaluate trigger data, subscriber properties, and previous step outcomes to create personalized user experiences.
+
+**Step conditions enable you to:**
+
+- Skip sending an email if a subscriber has already viewed an in-app message
+- Display in-app notifications only to users on specific plans (e.g., `subscriber.data.plan === "pro"`)
+- Control timing by executing delay steps based on trigger notification payload values (e.g., `if delay === true`)
+
+Step conditions automatically respect subscriber communication preferences, including:
+
+- Preferred notification channels (email, SMS, in-app, etc.)
+- Preferred times to receive communications
+- Frequency and volume settings
+
+---
+
+## Preferences
+
+Empower the end users and subscribers to decide what, when, and how they want to receive notifications.
+
+In Novu, each workflow run is executed on behalf of a subscriber, and each subscriber can specify their preferences to receive notifications across a number of different criteria:
+- channel types
+- individual workflows
+- workflow categories
+
+
+
+Novu automatically enforces user communication [preferences](/concepts/preferences) during workflow execution, while giving developers full flexibility to customize how these preferences are displayed and which options are available to users.
+
+---
+
+## Next Steps
+
+### Build something
+
+If you want to start by adding Novu to your existing system, you can check out our quick start guide to implement your first workflow.
+
+
+ Learn how to integrate Novu with your backend codebase.
+
+
+### Keep learning
+
+While the workflow engine is at the heart of Novu, our goal is to build a complete notification system for our customers.
+
+**Here is an overview of some of the more-advanced concepts and features Novu has to offer:**
+
+
+
+ Explore our full-stack UI components libraries for building in-app notifications.
+
+
+ Extend your workflows past the UI to accomplish any use case imaginable.
+
+
+ Use our SDK to integrate Novu with your backend codebase.
+
+
+ Manage multiple tenants within an organization.
+
+
+ Manage localized versions of your notification templates.
+
+
+ Use our local studio to design and test your notification templates.
+
+
+ Manage multiple environments based on your application's deployment stages.
+
+
+
diff --git a/getting-started/introduction.mdx b/getting-started/introduction.mdx
new file mode 100644
index 00000000..e49866bd
--- /dev/null
+++ b/getting-started/introduction.mdx
@@ -0,0 +1,59 @@
+---
+title: "What is Novu?"
+description: "Learn more about what Novu does and how it helps power your product notifications."
+---
+
+Novu is an open-source notification infrastructure designed to help you implement notifications your users will love, without the effort of building and maintaining your own in-house notifications system.
+
+Novu is designed with both developers and product teams in mind: it’s easy for developers to implement quickly, and simple for less-technical users to maintain with our intuitive dashboard.
+
+You also can reffer to it as a **Communication Infrastructure Layer (CIL)**, which is a layer that sits between your application and the user's devices and channels.
+
+
+
+At Novu, we’re redefining how you engage users with notifications.
+Our platform offers the most flexible and customizable set of tools to deliver modern, seamless, and highly personalized notification experiences.
+
+Whether you're sending alerts, updates, or tailored messages, Novu enables you to design notification flows that adapt to your users' preferences, across any channel, with ease.
+
+We can break down the Novu platform into the following blocks:
+
+
+
+
+ The Novu API allows you to seamlessly integrate a comprehensive notification infrastructure into your product. It's the only RESTful API you'll need to manage notifications across all channels.
+
+
+
+ In Novu, notifications are sent through workflows. Each workflow serves as a container for the logic and templates associated with a specific type of notification in your system.
+
+
+
+ Subscribers typically refer to users in your application. As workflows are triggered for subscribers, Novu stores the necessary data to send notifications across various platforms.
+
+
+
+ Channels in Novu represent the specific providers you've configured to send notifications, such as email, SMS, or push.
+
+
+
+ Workflows can include multiple action steps, enabling complex logic to create richer and more personalized notification experiences.
+
+
+
+ Each workflow run is executed on behalf of a subscriber, and each subscriber can specify their preferences to receive notifications across a number of different criteria: channel types, individual workflows, and workflow categories.
+
+
+
+ Novu offers a collection of full-stack UI components that are customizable and can be easily embedded into your application.
+
+
+
+ The Dashboard provides an interface for managing workflows, notification templates, integrations, debugging, and more.
+
+
+
+ The Novu Framework is the underlying layer that allows you to build and manage advanced notification workflows with code, while providing no-code options for non-technical users to customize.
+
+
+
\ No newline at end of file
diff --git a/getting-started/media-assets/add-steps.png b/getting-started/media-assets/add-steps.png
new file mode 100644
index 00000000..a83f063a
Binary files /dev/null and b/getting-started/media-assets/add-steps.png differ
diff --git a/getting-started/media-assets/application-identifier.png b/getting-started/media-assets/application-identifier.png
new file mode 100644
index 00000000..f9f36508
Binary files /dev/null and b/getting-started/media-assets/application-identifier.png differ
diff --git a/getting-started/media-assets/how-novu-works.png b/getting-started/media-assets/how-novu-works.png
new file mode 100644
index 00000000..eea61143
Binary files /dev/null and b/getting-started/media-assets/how-novu-works.png differ
diff --git a/getting-started/media-assets/in-app-template-example.png b/getting-started/media-assets/in-app-template-example.png
new file mode 100644
index 00000000..d9f921f4
Binary files /dev/null and b/getting-started/media-assets/in-app-template-example.png differ
diff --git a/getting-started/media-assets/notification-template.png b/getting-started/media-assets/notification-template.png
new file mode 100644
index 00000000..4d78fddf
Binary files /dev/null and b/getting-started/media-assets/notification-template.png differ
diff --git a/getting-started/media-assets/novu-workflow.png b/getting-started/media-assets/novu-workflow.png
new file mode 100644
index 00000000..ba6e5b0d
Binary files /dev/null and b/getting-started/media-assets/novu-workflow.png differ
diff --git a/getting-started/media-assets/preferences.png b/getting-started/media-assets/preferences.png
new file mode 100644
index 00000000..37781771
Binary files /dev/null and b/getting-started/media-assets/preferences.png differ
diff --git a/getting-started/media-assets/preview-notification.png b/getting-started/media-assets/preview-notification.png
new file mode 100644
index 00000000..c57a7f6f
Binary files /dev/null and b/getting-started/media-assets/preview-notification.png differ
diff --git a/getting-started/media-assets/switch-to-production.png b/getting-started/media-assets/switch-to-production.png
new file mode 100644
index 00000000..96909df9
Binary files /dev/null and b/getting-started/media-assets/switch-to-production.png differ
diff --git a/getting-started/media-assets/sync-workflow-success.png b/getting-started/media-assets/sync-workflow-success.png
new file mode 100644
index 00000000..715f267e
Binary files /dev/null and b/getting-started/media-assets/sync-workflow-success.png differ
diff --git a/getting-started/media-assets/sync-workflow.png b/getting-started/media-assets/sync-workflow.png
new file mode 100644
index 00000000..8cbf5b48
Binary files /dev/null and b/getting-started/media-assets/sync-workflow.png differ
diff --git a/getting-started/media-assets/test-workflow-inbox.png b/getting-started/media-assets/test-workflow-inbox.png
new file mode 100644
index 00000000..a2797885
Binary files /dev/null and b/getting-started/media-assets/test-workflow-inbox.png differ
diff --git a/getting-started/media-assets/test-workflow.png b/getting-started/media-assets/test-workflow.png
new file mode 100644
index 00000000..2b72a8d8
Binary files /dev/null and b/getting-started/media-assets/test-workflow.png differ
diff --git a/getting-started/media-assets/trigger-snippet.png b/getting-started/media-assets/trigger-snippet.png
new file mode 100644
index 00000000..9a861c58
Binary files /dev/null and b/getting-started/media-assets/trigger-snippet.png differ
diff --git a/getting-started/media-assets/trigger-tab.png b/getting-started/media-assets/trigger-tab.png
new file mode 100644
index 00000000..5f2f612d
Binary files /dev/null and b/getting-started/media-assets/trigger-tab.png differ
diff --git a/getting-started/novu-for/developers.mdx b/getting-started/novu-for/developers.mdx
new file mode 100644
index 00000000..cb750a83
--- /dev/null
+++ b/getting-started/novu-for/developers.mdx
@@ -0,0 +1,27 @@
+---
+title: "Novu for Developers"
+sidebarTitle: "Developers"
+description: "Novu is a powerful notifications platform designed for developers and engineers to build, manage, and deliver scalable, extensible, and captivating multi-channel notification experiences while empowering product teams with intuitive tools and prebuilt components."
+---
+
+## Who is Novu for?
+
+How you use and get started with Novu depends on your role. While it’s initially implemented by engineering and development teams, Novu unifies everyone in an organization that authors, creates, sends, manages, and measures results from notifications being sent to end users. Novu empowers engineers to deliver notification platforms for product teams.
+
+### Novu for developers and engineers
+
+Novu empowers developers and engineering teams to quickly deliver a fully extensible notifications platform for product teams to create captivating notification experiences.
+
+We provide the following proven stack for developers to simply integrate notifications into their products:
+
+- **[Code-first Notification Framework](/sdks/framework/typescript/overview)** Opinionated, yet flexible, Framework for building and managing notification workflows.
+- **[JSON Schema Based](/api-reference/overview)** Controls to craft a no-code visual editor to enable non-technical team members to modify content and behaviour.
+- **[Prebuilt, customisable UI components](/inbox/introduction)** for in-app user notification feeds and preference experiences.
+- **[Integration with multiple delivery providers](/integrations/providers/introduction)**, allowing you to continue using your preferred vendors with Novu.
+- **[Scalable, reliable Novu Cloud SaaS infrastructure](https://dashboard.novu.co)** developed from scratch to meet the demands of high-volume notification delivery and storage (think hundreds of millions of notifications).
+- **Observability** for delving into the lifecycle of a notification's success or failure. Eliminate guesswork of how, when, and why a user receives a notification.
+- **Comprehensive documentation**, implementation guides, recipes and illustrative examples.
+- **Compliance and security** for safely managing your data.
+- **Open source** provides transparency you can trust, cultivates community contributions for fast improvement, and enables you to deploy and self-host a Novu instance into any environment of your choosing.
+
+Notification content can be written in a variety of common content tooling, including [React](/integrations/react-email), [Vue-email](/integrations/vue-email), MJML, and more. Content can also be customized and hydrated using any datasource.
\ No newline at end of file
diff --git a/getting-started/novu-for/product.mdx b/getting-started/novu-for/product.mdx
new file mode 100644
index 00000000..6b5a179d
--- /dev/null
+++ b/getting-started/novu-for/product.mdx
@@ -0,0 +1,45 @@
+---
+title: "No-Code tools to manage your notifications"
+sidebarTitle: "Product Teams"
+description: "Change notification messaging, verbiage, and cadence without requiring engineering's help by using the Novu Dashboard UI."
+---
+
+Novu Framework was designed to bridge the gap between developers and non-developers in the team.
+
+- **Developers** - Define and abstract away complex logic, data manipulation, hydration, html, and etc...
+- **Non-Developers** - Use the Novu Dashboard UI to control the content and behavior of the notifications using [controls](/framework/controls).
+
+## How to modify controls?
+
+Controls are modified directly in the [Novu Dashboard UI](https://dashboard-v2.novu.co), under the 'Step controls' tab of the **Workflow Editor** page.
+Upon saving the changes, the new Control values will be used in the notification sent to your subscribers.
+
+## Dynamic payload data
+
+Dynamic data passed as part of the trigger payload can be easily used inside of the Controls. For example, you can pass a `user_name` as part of the payload and use it in the controls as `{{payload.user_name}}` to personalize the notification.
+
+## Common usecases
+
+### Change notification content
+
+A control can be as broad as `content` or as specific as `2fa_code_title_color`. This allows you to change the content of the notification without needing to touch the code.
+
+### Create new email designs
+
+Novu's built-in email editor enables rich content creation without ever touching the code.
+
+### Change digest or delay frequency
+
+Controls can be used for controlling any aspect of the step configuration, for example:
+
+- Delay amount
+- Digest length
+- Digest type (daily, weekly, monthly)
+- etc...
+
+Read more about [digest strategies and how to prevent notification fatigue.](https://novu.co/blog/digest-notifications-best-practices-example/)
+
+### Control structure and layout
+
+Controls can be used to show/hide or rearrange the layout of the notification.
+For example, you can have an input to show/hide a button, or change the position of a specific email section.
\ No newline at end of file
diff --git a/getting-started/quickstart.mdx b/getting-started/quickstart.mdx
new file mode 100644
index 00000000..29cf29a8
--- /dev/null
+++ b/getting-started/quickstart.mdx
@@ -0,0 +1,359 @@
+---
+title: "Quickstart"
+description: "Learn how to quickly get started with Novu."
+mode: "full"
+---
+
+Before you can start integrating Novu into your application, you need to create a Novu account and set up a new workflow in the Novu Dashboard. This guide walks you through those steps.
+
+
+
+
+ [Create a new account](https://dashboard-v2.novu.co/signup) on Novu Cloud or [sign in](https://dashboard-v2.novu.co/login) if you already have an account.
+
+
+
+ Our SDKs support [most major programming languages](/sdks/overview), making it easy to get started. Is your preferred language missing? [Reach out to us!](https://novu.co/contact)
+ ```bash
+ npm install @novu/node
+ ```
+
+
+
+
+ To start, set the `NOVU_API_KEY` environment variable in your application.
+ You can find your public and secret API keys in the **Developer** section of the Novu dashboard. Since this step is for backend integration, be sure to use the **secret key**.
+
+ For security, always define your API key as an environment variable and avoid committing it to source control.
+ ```bash
+ NOVU_API_KEY= "example_key_abcdefg1234567890"
+ ```
+
+
+
+ Navigate through the toggles to create your first custom workflow.
+
+
+
+ Create a new workflow by clicking on the **Create Workflow** button in the workflows page.
+ - Give your workflow a name (e.g. "User Onboarding").
+ - **Workflow identifier** will be generated automatically (e.g. `user-onboarding`).
+ - Add **tags** (optional) : Tags are a great way to classify and filter workflows for you, and your users.
+ - Add a **description** (optional) : Add a description to help you and your team understand the purpose of the workflow.
+
+
+
+
+ Every notification workflow starts with at least one [**channel step**](/workflow/channel-steps). To add a step, click the **Plus** button (**+**) in the Workflow Editor.
+
+
+
+
+
+ In the editor, you can customize your workflow with various steps, including **Action steps** (e.g., [Digest](/workflow/digest) and [Delay](/workflow/delay)) and **[delivery channels](/workflow/channel-steps)**. Select a delivery channel to begin crafting your workflow.
+
+ Workflows can include multiple steps, with each step sending a notification to the user. Steps execute in the order they are added.
+
+
+
+ **In-app** notifications are delivered directly inside your application via an Inbox or custom integration.
+
+ [Learn more about the In-App channel step](integrations/providers/in-app/overview)
+
+
+ **Email** is ideal for sending promotional or transactional messages.
+
+ [Learn more about the Email channel step](integrations/providers/email/overview)
+
+
+ **SMS** excels at delivering OTPs, alerts, and two-factor authentication codes.
+
+ [Learn more about the SMS channel step](integrations/providers/sms/overview)
+
+
+ **Web Push** and **Mobile Push** notifications are perfect for time-sensitive updates.
+
+ [Learn more about the Push channel step](integrations/providers/push/overview)
+
+
+ **Chat** notifications integrate with platforms like **Slack**, **Microsoft Teams**, **WhatsApp**, **Telegram**, and **Discord**.
+
+ [Learn more about the Chat channel step](integrations/providers/chat/overview)
+
+
+
+ **Delay** steps allow you to pause for a specified time before proceeding to the next step.
+
+ [Learn more about the Delay action step](workflow/delay)
+
+
+ **Digest** steps group notifications and send them later based on defined logic.
+
+ [Learn more about the Digest action step](workflow/digest)
+
+
+ **Custom** steps enable you to add tailored logic to your workflow using the **[Novu Framework](/framework/overview)**.
+
+ [Learn more about the Custom action step](/workflow/custom)
+
+
+
+
+
+
+ Once you’ve added a channel step, you can customize the notification content.
+
+ Click **"Configure Channel Step Template"** in the channel's edit view to open the [Notification Template Editor](/workflow/template-editor) and start editing.
+
+
+
+
+
+ Each channel has its own template editor, allowing you to customize the notification content for each channel based on the channel's capabilities.
+
+ In this example, we're using the **In-App** channel step, so we're using the **In-App** template editor.
+
+
+ **Here is an example of an In-App notification template:**
+
+ **Title**
+ ```json
+ You’ve Got a New Task Assigned!
+ ```
+ **Body**
+ ```json
+ Hey {{subscriber.firstName}}, you've been assigned to a new task: {{payload.task_id.title}}
+ The deadline is {{payload.task_id.dueDate}}. Click below to view the details and get started.
+ ```
+ **Button Text**
+ ```json
+ View Task
+ ```
+ **Redirect URL**
+ ```json
+ {{payload.task_id.url}}
+ ```
+
+
+
+
+
+ Preview your notification by clicking the **Preview** button in the **Configure Template** section of the workflow editor.
+
+
+
+
+
+
+
+ You can test your workflow in the editor by clicking the **Test Workflow** button in the workflow editor.
+
+
+
+
+
+
+ [**In-app**](/integrations/providers/in-app/overview) channel is the only channel that doesn't require any credentials to be configured in the [**Subscriber** object](/concepts/subscribers).
+
+ If you have [Email](/integrations/providers/email/overview), [SMS](/integrations/providers/sms/overview), [Push](/integrations/providers/push/overview) or [Chat](/integrations/providers/chat/overview) channels steps in your workflow, you need to ensure that you have the necessary credentials configured in the **Subscriber** object.
+
+ - [Learn more about the Subscriber object](/concepts/subscribers)
+ - [Learn more about how to add credentials to the Subscriber object](/api-reference/subscribers/update-subscriber-credentials)
+
+
+ If you have the **In-app** channel step in your workflow, you can test your workflow in the editor with the default `subscriberId`, you will see the notification in the **Inbox** section of the Novu dashboard.
+
+
+
+
+
+
+
+ Before leaving the Workflow Editor and heading back to your backend, navigate to the **`Trigger`** tab.
+
+ Here, you can grab a sample payload to use when calling Novu's API.
+
+
+
+
+
+ Based on the workflow we created, the sample payload will look like this:
+
+ ```json
+ {
+ "task_id": {
+ "title": "{{payload.task_id.title}}",
+ "dueDate": "{{payload.task_id.dueDate}}",
+ "url": "{{payload.task_id.url}}"
+ }
+ }
+ ```
+
+
+ **Note:** The payload is optional, and can be used to pass additional information to the workflow.
+
+
+ At the bottom of the page, you can grab the workflow trigger snippet and add it to your backend.
+
+
+
+
+
+ Now we're ready to trigger our workflow via the Novu API.
+
+ [Learn more about how to build workflows](/workflow/how-to/build-a-workflow)
+
+
+
+
+
+
+
+
+ Each workflow is created with a unique identifier that can be used to trigger the workflow, visit the Trigger page to view the trigger instructions.
+
+
+ **Each trigger consists of 3 main parts:**
+ - **Workflow identifier**: The unique identifier of the workflow to trigger.
+ - **Subscriber ID**: The unique identifier of the subscriber to trigger the workflow for, this can be a user id, email or any unique identifier.
+ - **Payload**: The payload to pass to the workflow, this is optional and can be used to pass additional information to the workflow.
+
+
+ In this example, we're using the [**Node.js SDK**](/sdks/nodejs) to trigger the workflow. But the same can be done with any of the [SDKs](/sdks/overview).
+
+ Add the trigger snippet to your backend and call the trigger function with the **Workflow identifier**, **Subscriber ID** and **Payload**.
+
+
+ ```javascript Node.js
+ import { Novu } from '@novu/node';
+
+ const novu = new Novu(process.env['NOVU_SECRET_KEY']);
+
+ novu.trigger('test', { // Workflow Identifier
+ to: {
+ subscriberId: '625f3fe55a55980017dd63fd' // Subscriber ID
+ },
+ payload: { // Payload
+ task_id: {
+ title: '{{payload.task_id.title}}',
+ dueDate: '{{payload.task_id.dueDate}}',
+ url: '{{payload.task_id.url}}'
+ }
+ }
+ });
+ ```
+
+ ```bash Curl
+ curl -X POST 'https://api.novu.co/v1/events/trigger' \
+ -H 'Authorization: ApiKey NOVU_SECRET_KEY' \
+ -H 'Content-Type: application/json' \
+ -d '{
+ "name": "test",
+ "to": {
+ "subscriberId": "625f3fe55a55980017dd63fd"
+ },
+ "payload": {
+ "task_id": {
+ "title": "{{payload.task_id.title}}",
+ "dueDate": "{{payload.task_id.dueDate}}",
+ "url": "{{payload.task_id.url}}"
+ }
+ }
+ }'
+ ```
+
+
+**Execute the trigger call from your backend.**
+
+
+ If you have the **In-app** channel step in your workflow, you should see the notification in the **Inbox** section of the Novu dashboard.
+
+
+
+
+
+ Novu users logically separated environments to control the roll-out of your notifications. When you're happy with the way your workflows work and look, you just need to promote them to production to start sending notifications to your real users.
+
+
+
+ 1. Navigate to the **Workflows** page, click on the 3 dots button on the right side of the workflow you want to promote and click the **Sync to Production** button.
+
+
+
+ You should see a success message and the workflow will be synced to production.
+
+
+
+
+
+
+ At the top of the page, you should see a dropdown to switch from the Development to the production environment.
+
+
+
+
+ Once you have switched to the Production environment, you should see the workflows you have synced to production.
+
+
+ You are also able to test your workflows in the Production environment by clicking the **Test Workflow** button in the workflow editor.
+
+
+
+
+ Be sure to update your application’s environment variables to point to your Novu Production environment API **Secret Keys**.
+
+
+ You can find your **Secret Keys** in the **Developer** section of the Novu dashboard.
+
+
+
+
+
+ If you are using the **in-app** channel, you need to update your **Application Identifier** to point to your Novu Production environment **Application Identifier**.
+
+
+ You can find your **Application Identifier** in the **Developers** section of the Novu dashboard.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ If you have any questions or run into issues as you build with our product, we hope you’ll let us know! Email us at support@novu.co, or join our [Discord community](https://discord.gg/novu) and we’ll be more than happy to assist you.
+
+
+
+
+**Now that you triggered your first workflow, learn more about the advanced features and concepts of Novu.**
+
+
+
+ Explore our full-stack UI components libraries for building in-app notifications.
+
+
+ Extend Novu Workflows with code, and accomplish literally any advanced use case imaginable.
+
+
+ For more advanced use cases, use our SDK to integrate Novu with your backend codebase.
+
+
+ Manage multiple tenants within an organization.
+
+
+ Manage localized versions of your notification templates.
+
+
+ Use our local studio with Novu Framework to design and test your notification templates.
+
+
+ Manage multiple environments based on your application's deployment stages.
+
+
+
diff --git a/google-tag-manager.js b/google-tag-manager.js
new file mode 100644
index 00000000..f0a0e6a6
--- /dev/null
+++ b/google-tag-manager.js
@@ -0,0 +1,10 @@
+(function (w, d, s, l, i) {
+ w[l] = w[l] || [];
+ w[l].push({ "gtm.start": new Date().getTime(), event: "gtm.js" });
+ var f = d.getElementsByTagName(s)[0],
+ j = d.createElement(s),
+ dl = l != "dataLayer" ? "&l=" + l : "";
+ j.async = true;
+ j.src = "https://www.googletagmanager.com/gtm.js?id=" + i + dl;
+ f.parentNode.insertBefore(j, f);
+})(window, document, "script", "dataLayer", "GTM-KXMC4XP2");
diff --git a/help/account-management.mdx b/help/account-management.mdx
new file mode 100644
index 00000000..3cb019f5
--- /dev/null
+++ b/help/account-management.mdx
@@ -0,0 +1,37 @@
+---
+title: "Account Management"
+sidebarTitle: "Account Management"
+icon: "user"
+---
+
+import ContactSupportSnippet from "/snippets/contact-support.mdx";
+
+### Creating an account
+
+Novu supports [US](https://web.novu.co/auth/signup?utm_campaign=docs-gs-help-account-mgmt) and [EU](https://eu.web.novu.co/auth/signup?utm_campaign=docs-gs-help-account-mgmt) regions based on where you want your data to be stored. In both the regions, a new account can be created using Github, Google, or an Email and Password. We support [SSO](/account/sso) for certain pricing tiers. If you are looking for other ways to signup on Novu, feel free to reach out us at support@novu.co.
+
+### Resetting password
+If you have created an account with Novu before, you can reset your password by clicking on `Forgot Your Password?` option on the sign-in page.
+
+### Is US and EU account data synced?
+US and EU accounts are completely independent. If you have created an account in the US, you will not be able to login with the same credentials in the EU and vice versa.
+
+### Changing the email associated to my account
+If you want to change your account email, please reach out to us at support@novu.co.
+
+### What happens if I change my Github account email?
+If you created your Novu account using Github, and you change your email in Github, it will not be reflected in Novu. When you login to Novu using the new Github email, a new account will be created. Please email support@novu.co to update your email address.
+
+### How do I create a new Organization?
+A new organization can be created by writing something in the dropdown in the left bottom corner of the dashboard. As soon you start writing something, an add option will apear. This dropdown can also be used to switch between Organizations. One organization's data is not visible to other organization members.
+
+### How to change organization name?
+We are working on account management feature currently so this option is not available in UI as of now. If you want to change your current organization name, [rename organization api](/api-reference/organizations/rename-organization) can be used. if you face any issue in using api or need any help any changing the organization name, please reach out to us at support@novu.co.
+
+### How to upload multiple image assets similar to logo?
+Currently, Novu supports uploading only one logo image per organization. If you want to use other images in workflow step content then a valid absolute url of image can be used with `` tag.
+
+### What will happen once free business trial ends?
+Novu starts free business trial for 14 days for new organizations. After 14 days, if you have not upgraded to a paid plan, your organization will be automatically downgraded to free tier. If you have added payment details, you will be charged as per the plan you have selected. If you have any billing queries, please reach out to us at sales@novu.co.
+
+
\ No newline at end of file
diff --git a/help/channels/chat.mdx b/help/channels/chat.mdx
new file mode 100644
index 00000000..2e16defb
--- /dev/null
+++ b/help/channels/chat.mdx
@@ -0,0 +1,13 @@
+---
+title: "Chat"
+sidebarTitle: "Chat"
+icon: "comment"
+---
+
+import ContactSupportSnippet from "/snippets/contact-support.mdx";
+
+### Can I send a message to a specific user on Slack?
+
+While installing the Slack app, you will be asked the User or Channel to which the message needs to be sent. On selecting a user, a webhookUrl for that user will be generated and Novu will use that webhookUrl to send a message to that specific user.
+
+
diff --git a/help/channels/email.mdx b/help/channels/email.mdx
new file mode 100644
index 00000000..1117dfac
--- /dev/null
+++ b/help/channels/email.mdx
@@ -0,0 +1,45 @@
+---
+title: "Email"
+sidebarTitle: "Email"
+icon: "envelope"
+---
+
+import ContactSupportSnippet from "/snippets/contact-support.mdx";
+
+### How to override default values of Email?
+
+All values (from, to, subject, body, etc.) can be overridden by using the `overrides` option while triggering the workflow. Please see our [email overrides documentation](/integrations/providers/email/overview) for more details.
+
+### Sending HTML as the value of a variable
+
+HTML content as a value of step variable can be sent by replacing the double curly braces `{{}}` variable with triple curly braces `{{{}}}` variable. For example, `{{{htmlContent}}}`.
+
+### I have multiple Email Providers or integrations active, how do I associate them to a workflow?
+
+There can be more than one active email integrations of same or different providers. However only one email integration can be primary at one time per environment. To use a different email integration than your primary integration, the integrationIdentifier field of email overrides can be used. Read about using [different email integration](/integrations/providers/email/overview#using-different-email-integration) for more details.
+
+### Why am I getting the error “message content could not be generated”?
+
+This error occurs when the HTML content is not properly formatted. Make sure the HTML content is properly formatted and does not contain any syntax errors. Make sure Step Variables are properly formatted and are not missing opening or closing curly braces.
+
+### Can email or phone be used as step variables?
+
+Novu recommends not to use subscriber attributes as step variables. Instead, use the subscriber attributes directly in the step. For example, use `{{subscriber.email}}` instead of `{{email}}` as your step variable. Novu will select the email from subscriber attributes and inject the value in the content.
+
+### How to use Provider templates in place of email content?
+
+Currently, we support external Provider template support for [sendgrid](/integrations/providers/email/sendgrid) and [mailersend](/integrations/providers/email/mailersend) only.
+
+### Can I retrieve only Email content?
+
+Novu does not have a separate API to retrieve email content. However, email content can be retrieved from the email step of the workflow. The email step contains the email content in the `content` field of [get worklfow api](/api-reference/workflows/get-workflow) response.
+
+### How to send inline images in email?
+
+Inline images can be sent using `` tag in the email content. PNG, JPEG, GIF are supported. SVGs are not supported as inline images by most of email clients like gmail, outlook.
+
+### How to add unsubscribe link in emails?
+
+Add `{{unsubscribe}}` variable in email step content editor and send url value of this variable in payload. Create a simple html page to [fetch preferences](/api-reference/subscribers/get-subscriber-preferences) and then use [update preference api](/api-reference/subscribers/update-subscriber-preference) to update.
+
+
diff --git a/help/channels/in-app.mdx b/help/channels/in-app.mdx
new file mode 100644
index 00000000..d1f89a60
--- /dev/null
+++ b/help/channels/in-app.mdx
@@ -0,0 +1,29 @@
+---
+title: "In App"
+sidebarTitle: "In App"
+icon: "bell"
+---
+
+import ContactSupportSnippet from "/snippets/contact-support.mdx";
+
+### Creating two In-App Notification Centers in one account
+
+To configure your In-App notifications, you must use the In-App integration from our integration store.
+
+To display in-app notifications to users, any SDKs in our [client library](/inbox/overview) can be used. Every library requires `applicationIdentifier` and `subscriberId` to fetch notifications. Currently, only one `applicationIdentifier` is supported per environment per organization.
+
+Feel free to reach out to us at support@novu.co if you have speicific requirements of multiple `applicationIdentifier` per organization.
+
+### Issues with a redirect URL
+
+To redirect to specified redirect url on clicking the in-app notification, notification click event should be handled in client side code. Here is an example in [React](/inbox/react/get-started).
+
+### Changing the default colors of the Notification Center
+
+Styling of your Inbox can be customized using the [styles](/inbox/react/styling#customization-hierarchy) prop. If you want to customize only a single component, you can use [available](/inbox/react/styling#appearance-prop) props.
+
+### Additional Security for In-App Notifications
+
+To add an extra layer of security, we recommend using our HMAC encryption feature to eliminate subscriber impersonation Checkout [HMAC](/inbox/react/production#hmac-encryption) section for more details.
+
+
diff --git a/help/channels/push.mdx b/help/channels/push.mdx
new file mode 100644
index 00000000..3ce7fcb7
--- /dev/null
+++ b/help/channels/push.mdx
@@ -0,0 +1,28 @@
+---
+title: "Push"
+sidebarTitle: "Push"
+icon: "mobile"
+---
+
+import ContactSupportSnippet from "/snippets/contact-support.mdx";
+
+### Can I send a Desktop Push notification?
+
+Yes, Desktop Push Notifications can be sent by using a Web Push Notification. It is supported with almost all [push channel](/integrations/providers/push/overview) providers.
+
+### Why am I getting the error “unexpected provider” with FCM?
+
+The possible reasons for this error are:
+
+- Invalid FCM credentials.
+- Invalid or stale FCM token.
+
+### Why am I getting the error “Sending message failed due to Requested entity was not found” with FCM?
+
+- This error occurs when the FCM token is invalid or stale. You can fix this by updating the FCM device token.
+
+### Can I store device tokens for particular FCM integration if I have multiple FCM providers active?
+
+- Device tokens for particular FCM integration can be stored using the `integrationIdentifier` field of [update subscriber credentials api](/api-reference/subscribers/update-subscriber-credentials). `integrationIdentifier` is the unique identifier of the integration which is similar to `providerIdentifier`. It can be copied from the integration form.
+
+
diff --git a/help/channels/sms.mdx b/help/channels/sms.mdx
new file mode 100644
index 00000000..82578261
--- /dev/null
+++ b/help/channels/sms.mdx
@@ -0,0 +1,19 @@
+---
+title: "SMS"
+sidebarTitle: "SMS"
+icon: "message-sms"
+---
+
+import ContactSupportSnippet from "/snippets/contact-support.mdx";
+
+### Why am I getting the error “ subscriber does not have a configured channel”?
+
+This error occurs when a Subscriber is missing `phone` attribute. To fix this error, you need to update the subscriber and set the `phone` attribute with a valid phone number.
+
+### How to change the default sender name for SMS?
+
+First, ensure a custom sendername (sender id or from) is allowed with your Provider. Some countries expect to verify the sender name before using it.
+
+You can change the default sender name for SMS by using [overrides](/integrations/providers/sms/overview) field while triggering the workflow.
+
+
diff --git a/help/content-management.mdx b/help/content-management.mdx
new file mode 100644
index 00000000..7831c123
--- /dev/null
+++ b/help/content-management.mdx
@@ -0,0 +1,29 @@
+---
+title: "Content Management"
+sidebarTitle: "Content Management"
+icon: "file-pen"
+---
+
+import ContactSupportSnippet from "/snippets/contact-support.mdx";
+
+### What is a Layout and how can I use them?
+
+Layout is similar to a style container for email templates. Layouts can be used for your organization’s branding and style. It can be used to define the structure of the email template such as the header, footer, and other common elements. It must have a `{{{body}}}` variable which will be replaced by the actual email template content.
+
+### Can Layouts be used across all channels?
+
+Layouts are specific to Emails.
+
+### Can MJML templates be used in content editors?
+
+Only HTML templates can be used in content editors. We support [handlebar helpers](/content-creation-design/handlebars-helpers) for common use cases like iteration, conditions etc. If you need any specific Handlebar Helpers, please reach out to us at support@novu.co.
+
+### Can the content of one Step be used in another?
+
+Currently each Step has content that is unique to it and can not be reused. If this is important to you, please send us a note at support@novu.co.
+
+### Which Translation Language will be used if the Subscriber’s locale field does not match with translation language files?
+
+If the Subscriber's locale field does not match with Translation locale files, or it is not available, then the defaul language of the translation group will be used. If a default is not set, the automatic fallback is English.
+
+
diff --git a/help/faq.mdx b/help/faq.mdx
new file mode 100644
index 00000000..460af36e
--- /dev/null
+++ b/help/faq.mdx
@@ -0,0 +1,89 @@
+---
+title: "Frequently Asked Questions"
+icon: "comment"
+---
+
+## Platform
+
+### What is an event?
+
+An event is every call to our [trigger API](https://docs.novu.co/api-reference/events/trigger-event/?utm_campaign=docs-faq) or digested with other notifications into any call can be converted through the Novu logic engine into many different notifications or digested with other ones into one notification. In case a trigger was used for many subscribers at once, it will be calculated per unique subscriber. In case many events are converted into one notification they will be calculated at a lower rate.
+
+### What is a notification?
+
+A notification is any message sent over any channel, after the Novu logic engine, Novu user preferences, and your configuration calculated and crafted the message. A notification can be sent to an email, In-App notification center, chat, push, and more.
+
+### What is the difference between seen and read in the context of notifications?
+
+An in-app notification (message) is marked as seen when you open the notification center and that notification(message) is visible
+whereas it is marked as read either by clicking on that notification (message) or clicking the 'Mark as Read' option.
+
+### I want to send millions of events, but only send thousands of notifications, can I?
+
+Sure thing! This can easily be done using Novu’s digest + delay, features allowing for ingesting many events into well crafted must have messages over all channels.
+
+### What are the differences between Novu Cloud and Open Source?
+
+Novu [Open source](https://github.com/novuhq/novu) is a technology available under an MIT license, built and maintained by the Novu community, you can adapt it to your needs, contribute, or simply use docker files and run it. Novu Cloud is a managed service run, managed, and maintained by Novu Cloud version is scalable and robust by design, and uptime SLAs, and includes more business-facing features, that are not available under the Open Source version.
+
+### Can I send more than 1 million events a month on Novu Cloud?
+
+Yes, you absolutely can. Our managed cloud system was built to scale with your usage, so you don’t have to worry about it.
+
+### I need some help configuring Novu, where can I find it?
+
+No worries, we got your back. Many questions can be answered on our [documentation site](https://docs.novu.co/?utm_campaign=docs-faq). If for some reason something is missing, go ahead and use our [Discord](https://discord.gg/novu?ref=docs-faqs), we usually answer right away, and with the help of the community members and moderators, it’s very effective.
+
+### What are the differences between Discord and Intercom support?
+
+We have two main public support channels, Discord and Intercom. Discord is our community support channel supporting anything between self-hosted issues, bugs, code contributions, general discussions, feature requests, Novu-Cloud questions, and don’t forget memes :) On our Intercom support, we focus on Novu-Cloud questions, and ensure we can provide the best support based on our knowledge of Cloud usage.
+
+### Where can I find the roadmap?
+
+Glad you want to see what’s next, check out our roadmap [here](https://roadmap.novu.co/roadmap). Feel free to suggest other things and upvote features and requests.
+
+### Does the Novu In-App notification center work with Angular, Vue, and React?
+
+Yes, it does, The Novu In-App notification center is built using pure HTML components, and wrapped in your most beloved frameworks wrapper to provide a nice and consistent developer experience.
+
+### Do you offer any enterprise plan?
+
+Yes, we do. Enterprise plans are available for teams who require extended enterprise-grade features, and specific SLAs or have unique concerns about PII, security, insurance, or legal. Feel free to reach out to us over Intercom, or go to [contact us](https://novu.co/contact-us/?utm_campaign=docs-faq).
+
+### Do you have offer enterprise support plan?
+
+As part of our enterprise support, you’ll get our usual support channels as well as a Microsoft Teams/Slack/Discord channel. In case you want to add an enterprise support plan, with dedicated technical support, we are happy to offer that next to an active enterprise plan.
+
+### I need more events or enterprise features, can you help?
+
+We sure can. We are happy to work with enterprises and build Novu to support both their needs as well as the grand community ones. From new features, security, and privacy, or if you need to send dozens or hundreds of events a month, we are here to help. Feel free to reach out to us over Chat, or [contact us](https://novu.co/contact-us/?utm_campaign=docs-faq).
+
+### Can I see some examples of how to use Novu?
+
+Code examples can be found on our [Github Repo](https://github.com/novuhq/examples), and [our documentation](https://docs.novu.co/getting-started/your-first-notification?utm_campaign=docs-faq). We have examples for common use cases like sending notifications from a web server, mobile app, or integrating with other services.
+
+### I am missing a channel provider, can I ask for one? Or add it myself?
+
+You sure can, if you just want to ask for a new one, the easiest way is to go to our [Github issues](https://github.com/novuhq/novu/issues), and ask for a [new one](https://github.com/novuhq/novu/issues/new?assignees=&labels=feature&template=feature_request.yml&title=%F0%9F%9A%80+Feature%3A+), just make sure if there is another request you upvote it instead of creating a new one. If you are willing to contribute a provider just go to [how to contribute one](https://docs.novu.co/community/add-a-new-provider?utm_campaign=docs-faq) on our docs, thank you!
+
+### Do you have a status page?
+
+Yes we do, check it out [here](https://novustatus.com/).
+
+### I started using Novu Open Source, and I want to move to Novu Cloud, is there any migration tool?
+
+We are now building an automatic tool to help with this migration process, for now, feel free to ask us over Discord, or at engineering@novu.co.
+
+## Pricing
+
+### Can I use Novu free of charge?
+
+Yes, you can. If you send less than 30K events per month, then Novu Cloud is entirely free. Another option is to deploy the Open-Source version of Novu onto your own infrastructure, but that does not give you the unique SLA and global redundancy we have in the Cloud version of Novu.
+
+### What is the pricing?
+
+Pricing page is available [here](https://novu.co/pricing/?utm_campaign=docs-faq).
+
+### If I am sending millions of events, but thousands of notifications, will this affect the price?
+
+That’s a great way to do it, sending many events and only handful of notifications means you are able to convey a lot of value while keeping your subscribers happy. We are not only supporting it, it’s also affecting price. On high-volume events to notification, where digest is heavily used each event is calculated as 1/10 of the normal usage.
diff --git a/help/observability.mdx b/help/observability.mdx
new file mode 100644
index 00000000..77733aa4
--- /dev/null
+++ b/help/observability.mdx
@@ -0,0 +1,21 @@
+---
+title: "Observability"
+icon: "magnifying-glass"
+description: "Analyzing your Triggered Events and Notification execution logs"
+---
+
+import ContactSupportSnippet from "/snippets/contact-support.mdx";
+
+### My Subscribers have preferences set, but the Activity Feed is showing Triggers as being sent.
+
+The Activity Feed is a place each event (workflow trigger) is listed. If a Subscriber has a preference to not receive notifications through a certain channel, Novu’s system will adhere to that. However, this is still a valid Trigger as the fanout occurs before preferences are accommodated.
+
+### What does "step filtered by subscriber preferences" mean in the logs?
+
+It means that the Subscriber has set preferences configured and the step was filtered based on that preference. If the subscriber has disabled a step Channel then, the step will be filtered and notification will not be sent to the subscriber.
+
+### How to export Activity Feed data?
+
+Currently, we don't have any option to export activity feed data. But you can use our [API](/api-reference/notification/get-notifications) to fetch the data. If there is a specific system you’re looking to integrate with, please send us a note at support@novu.co.
+
+
diff --git a/help/orchestration.mdx b/help/orchestration.mdx
new file mode 100644
index 00000000..e76ee745
--- /dev/null
+++ b/help/orchestration.mdx
@@ -0,0 +1,31 @@
+---
+icon: "screwdriver-wrench"
+---
+
+import ContactSupportSnippet from "/snippets/contact-support.mdx";
+
+## Digest
+
+### New event triggers are merging an active Digest with an old duration..
+
+When a Digest is created with a set duration and an event is triggered, the Digest stays active ( and will batch the events) until the configured duration is over.
+
+If you change the duration before the initial time period has completed , the previous digest will still be active and the new duration is merged with the old duration Digest until the previous duration is complete.
+
+To remove the previous Digest, use [event cancel api](/api-reference/events/cancel-triggered-event) to cancel all event triggers that are still pending and are merged in active Digests.
+
+### Can I dynamically make the Digest inactive??
+
+Yes, Digest supports conditions and can be applied to your Digest step. If the Triggered Event passes the set conditions, then it will be active or inactive based on the condition.
+
+### Can a Subscriber control the Digest duration and time?
+
+Not currently. The Digest duration can be changed at the workflow level only and is applied to all subscribers that workflow is triggered to. If you have an identified use case for this, please let us know at support@novu.co.
+
+## Delay
+
+### What happens if the delay scheduled date and time is in the past?
+
+If the delay scheduled date and time is in the past, the delay step will fail. Based on the step configuration, workflow will either continue to the next step or workflow will be failed
+
+
diff --git a/help/others.mdx b/help/others.mdx
new file mode 100644
index 00000000..214b0ade
--- /dev/null
+++ b/help/others.mdx
@@ -0,0 +1,11 @@
+---
+icon: "crystal-ball"
+---
+
+### Workkflows changes are not showing on the changes
+
+If some changes are pending to promote for the same workflow, then Novu merges subsequent changes into the existing change item.
+
+### Getting CORS error with the API
+
+Novu APIs are developed to be used on the server side. If you are using the API from the browser, you will get a CORS error. You can use the API from the server side or checkout our [client-side libraries](/sdks/overview#web-and-mobile-sdks) to fetch in-app notifications.
diff --git a/help/overview.mdx b/help/overview.mdx
new file mode 100644
index 00000000..e343cd77
--- /dev/null
+++ b/help/overview.mdx
@@ -0,0 +1,64 @@
+---
+title: "Help Center Overview"
+description: "Introduction to Novu help center"
+sidebarTitle: "Overview"
+icon: "bolt"
+---
+
+At our Help Center, we're dedicated to provide comprehensive support to ensure your experience with our products and services is seamless and enjoyable. Whether you're a new user looking for guidance or a seasoned customer seeking assistance, we're here to help. Help Center is organized as per our product architecture and is divided into the following sections:
+
+
+
+ Manage your account and access your data.
+
+
+ Manage subscribers and topics
+
+
+ Explore all channels and how to use each one.
+
+
+ Explore orchestration digest, delay, conditions and more.
+
+
+ Content management, layouts, templates, and more.
+
+
+ Observability, metrics, analytics, and more.
+
+
+ Others help topics.
+
+
+ Read frequently asked questions
+
+
+
+
+
+
diff --git a/help/user-management.mdx b/help/user-management.mdx
new file mode 100644
index 00000000..101177c4
--- /dev/null
+++ b/help/user-management.mdx
@@ -0,0 +1,45 @@
+---
+title: "User Management"
+sidebarTitle: "User Management"
+icon: "users"
+---
+
+## Subscribers
+
+### What is a Subscriber?
+
+A subscriber is an entity to which notifications are sent. It is a Novu term similar to a user in your system. As in your system, a user is identified by a unique id, similarly in Novu, a subscriber is identified by a unique id called `subscriberId`. We recommend that the existing value you use in your system as a `userId` to be used as `subscriberId` in Novu. Read more about subscribers on [subscribers documentation page](/concepts/subscribers).
+
+### Is it necessary to use subscriberId as same as userId?
+
+No, it is not necessary to use subscriberId as same as userId. You can use any unique id as subscriberId. We recommend using userId as subscriberId to avoid any confusion. Some of our customers use a pattern like `auth0|userId` as a value for `subscriberId`.
+
+### Can notification be sent without adding a Subscriber?
+
+No, a notification cannot be sent without adding a subscriber. A subscriber is an entity to which notifications are sent. You need to add a subscriber to Novu before triggering the workflow or inline of trigger. Read more about subscribers on [subscribers documentation page](/concepts/subscribers).
+
+### What is the right approach to add Subscribers?
+
+Subscribers can be added either [ahead of trigger](/concepts/subscribers#1-ahead-of-trigger) or [inline of trigger](/concepts/subscribers#2-inline-of-trigger). One way to add a subscriber using inline of trigger is to create a new subscriber in Novu as soon as they sign up or register in your system.
+
+You can add your existing users as subscribers in Novu using [bulk create subscriber creation method](/concepts/subscribers#bulk-subscriber-creation).
+
+### Can Subscriber credentials be added while creating a new Subscriber?
+
+Subscriber credentials for Push and Chat channel providers cannot be added while creating a new subscriber. Subscriber credentials for Push and Chat channel providers can be added using the [update subscriber credentials api](/api-reference/subscribers/update-subscriber-credentials) or corresponding sdk methods.
+
+SMS and Email channels are a bit different because all providers can work with the same value, so `email` and `phone` [attributes](/concepts/subscribers#subscriber-attributes) can be added while creating a new Subscriber.
+
+### Can Subscriber preferences be updated while adding a new Subscriber?
+
+Subscriber preference cannot be updated while adding a new subscriber. Novu sets all channels as true by default for new subscribers. You can modify preferences at any time. Read more about [subscriber preferences](/concepts/preferences#subscriber-global-preferences).
+
+### Can two subscribers with different `subscriberId`s have the same email address?
+
+Yes, it is possible for two subscribers to have the same email address in our system. This is because the `subscriberId` is the unique identifier that distinguishes one subscriber from another. Even if two subscribers share the same email address, they will have different `subscriberId`s, which allows our system to identify and manage them separately.
+
+## Topics
+
+### Can a Subscriber be added to multiple topics at once?
+
+Currently, Novu supports adding a subscriber to only one topic at a time. You can add a subscriber to multiple topics by calling the [add subscriber to topic api](/api-reference/topics/subscribers-addition) multiple times with a particular topic key
diff --git a/hubspot.js b/hubspot.js
new file mode 100644
index 00000000..9f64dd07
--- /dev/null
+++ b/hubspot.js
@@ -0,0 +1,5 @@
+!function(e,t){if(!document.getElementById(e)){var c=document.createElement("script");c.src="https://js.hs-analytics.net/analytics/1705938600000/44416662.js",c.type="text/javascript",c.id=e;var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(c,n)}}("hs-analytics");
+!function(t,e,r){if(!document.getElementById(t)){var n=document.createElement("script");for(var a in n.src="https://js.hscollectedforms.net/collectedforms.js",n.type="text/javascript",n.id=t,r)r.hasOwnProperty(a)&&n.setAttribute(a,r[a]);var i=document.getElementsByTagName("script")[0];i.parentNode.insertBefore(n,i)}}("CollectedForms-44416662",0,{"crossorigin":"anonymous","data-leadin-portal-id":44416662,"data-leadin-env":"prod","data-loader":"hs-scriptloader","data-hsjs-portal":44416662,"data-hsjs-env":"prod","data-hsjs-hublet":"na1"});
+var _hsp = window._hsp = window._hsp || [];
+ _hsp.push(['addEnabledFeatureGates', []]);
+!function(t,e,r){if(!document.getElementById(t)){var n=document.createElement("script");for(var a in n.src="https://js.hs-banner.com/v2/44416662/banner.js",n.type="text/javascript",n.id=t,r)r.hasOwnProperty(a)&&n.setAttribute(a,r[a]);var i=document.getElementsByTagName("script")[0];i.parentNode.insertBefore(n,i)}}("cookieBanner-44416662",0,{"data-cookieconsent":"ignore","data-hs-ignore":true,"data-loader":"hs-scriptloader","data-hsjs-portal":44416662,"data-hsjs-env":"prod","data-hsjs-hublet":"na1"});
\ No newline at end of file
diff --git a/images/Add-filter.png b/images/Add-filter.png
new file mode 100644
index 00000000..1f17bdfd
Binary files /dev/null and b/images/Add-filter.png differ
diff --git a/images/Create-new-orgnization.png b/images/Create-new-orgnization.png
new file mode 100644
index 00000000..2d52323a
Binary files /dev/null and b/images/Create-new-orgnization.png differ
diff --git a/images/Dashboead-sidebar.png b/images/Dashboead-sidebar.png
new file mode 100644
index 00000000..2d67d0d6
Binary files /dev/null and b/images/Dashboead-sidebar.png differ
diff --git a/images/Deley-action-properties.png b/images/Deley-action-properties.png
new file mode 100644
index 00000000..214de2fd
Binary files /dev/null and b/images/Deley-action-properties.png differ
diff --git a/images/Novu-mental-model.png b/images/Novu-mental-model.png
new file mode 100644
index 00000000..d2debaac
Binary files /dev/null and b/images/Novu-mental-model.png differ
diff --git a/images/Novu_Architecture_v2.png b/images/Novu_Architecture_v2.png
new file mode 100644
index 00000000..87bf06c9
Binary files /dev/null and b/images/Novu_Architecture_v2.png differ
diff --git a/images/Or-And-filters.png b/images/Or-And-filters.png
new file mode 100644
index 00000000..cc848aa0
Binary files /dev/null and b/images/Or-And-filters.png differ
diff --git a/images/SCR-20230825-pigg.png b/images/SCR-20230825-pigg.png
new file mode 100644
index 00000000..390111ac
Binary files /dev/null and b/images/SCR-20230825-pigg.png differ
diff --git a/images/SCR-20230825-pkxt.png b/images/SCR-20230825-pkxt.png
new file mode 100644
index 00000000..baca26df
Binary files /dev/null and b/images/SCR-20230825-pkxt.png differ
diff --git a/images/SCR-20230825-pmtt.png b/images/SCR-20230825-pmtt.png
new file mode 100644
index 00000000..995b1378
Binary files /dev/null and b/images/SCR-20230825-pmtt.png differ
diff --git a/images/Template Store.gif b/images/Template Store.gif
new file mode 100644
index 00000000..69c32ce3
Binary files /dev/null and b/images/Template Store.gif differ
diff --git a/images/abandoned-cart-recovery-workflow.png b/images/abandoned-cart-recovery-workflow.png
new file mode 100644
index 00000000..c1fd16aa
Binary files /dev/null and b/images/abandoned-cart-recovery-workflow.png differ
diff --git a/images/account/billing/manage-invoices.gif b/images/account/billing/manage-invoices.gif
new file mode 100644
index 00000000..9da4c27f
Binary files /dev/null and b/images/account/billing/manage-invoices.gif differ
diff --git a/images/account/billing/purchase-business-tier.gif b/images/account/billing/purchase-business-tier.gif
new file mode 100644
index 00000000..9e33d973
Binary files /dev/null and b/images/account/billing/purchase-business-tier.gif differ
diff --git a/images/activity-feed-usecase.png b/images/activity-feed-usecase.png
new file mode 100644
index 00000000..ad43f980
Binary files /dev/null and b/images/activity-feed-usecase.png differ
diff --git a/images/activity-feed.png b/images/activity-feed.png
new file mode 100644
index 00000000..1d5cdfcc
Binary files /dev/null and b/images/activity-feed.png differ
diff --git a/images/activity-feed/job-statuses.png b/images/activity-feed/job-statuses.png
new file mode 100644
index 00000000..500de746
Binary files /dev/null and b/images/activity-feed/job-statuses.png differ
diff --git a/images/add-fields-kotlin.png b/images/add-fields-kotlin.png
new file mode 100644
index 00000000..25887516
Binary files /dev/null and b/images/add-fields-kotlin.png differ
diff --git a/images/add-new-provider.png b/images/add-new-provider.png
new file mode 100644
index 00000000..bff4b733
Binary files /dev/null and b/images/add-new-provider.png differ
diff --git a/images/add-variables-kotlin.png b/images/add-variables-kotlin.png
new file mode 100644
index 00000000..ba19dc3f
Binary files /dev/null and b/images/add-variables-kotlin.png differ
diff --git a/images/add-variant.png b/images/add-variant.png
new file mode 100644
index 00000000..c6f1bea9
Binary files /dev/null and b/images/add-variant.png differ
diff --git a/images/adding-digest.png b/images/adding-digest.png
new file mode 100644
index 00000000..90ea2d81
Binary files /dev/null and b/images/adding-digest.png differ
diff --git a/images/angular.png b/images/angular.png
new file mode 100644
index 00000000..cc22876d
Binary files /dev/null and b/images/angular.png differ
diff --git a/images/api-nextjs.png b/images/api-nextjs.png
new file mode 100644
index 00000000..d418ac1d
Binary files /dev/null and b/images/api-nextjs.png differ
diff --git a/images/assign-layout.png b/images/assign-layout.png
new file mode 100644
index 00000000..65b329e8
Binary files /dev/null and b/images/assign-layout.png differ
diff --git a/images/auto-sub-react.png b/images/auto-sub-react.png
new file mode 100644
index 00000000..2b9efa0d
Binary files /dev/null and b/images/auto-sub-react.png differ
diff --git a/images/bell-angular.png b/images/bell-angular.png
new file mode 100644
index 00000000..1bf2f809
Binary files /dev/null and b/images/bell-angular.png differ
diff --git a/images/bell-ui-angular.png b/images/bell-ui-angular.png
new file mode 100644
index 00000000..b57362fe
Binary files /dev/null and b/images/bell-ui-angular.png differ
diff --git a/images/blank-workflow.png b/images/blank-workflow.png
new file mode 100644
index 00000000..82c60837
Binary files /dev/null and b/images/blank-workflow.png differ
diff --git a/images/branding-section.png b/images/branding-section.png
new file mode 100644
index 00000000..6d39c33f
Binary files /dev/null and b/images/branding-section.png differ
diff --git a/images/change-update-notification-group.gif b/images/change-update-notification-group.gif
new file mode 100644
index 00000000..14559efd
Binary files /dev/null and b/images/change-update-notification-group.gif differ
diff --git a/images/channels-and-providers/chat/msteams/create-incoming-webhook.gif b/images/channels-and-providers/chat/msteams/create-incoming-webhook.gif
new file mode 100644
index 00000000..0b1f7efb
Binary files /dev/null and b/images/channels-and-providers/chat/msteams/create-incoming-webhook.gif differ
diff --git a/images/channels-and-providers/chat/zulip/step_01.png b/images/channels-and-providers/chat/zulip/step_01.png
new file mode 100644
index 00000000..e6c32023
Binary files /dev/null and b/images/channels-and-providers/chat/zulip/step_01.png differ
diff --git a/images/channels-and-providers/chat/zulip/step_02.png b/images/channels-and-providers/chat/zulip/step_02.png
new file mode 100644
index 00000000..4877d8f1
Binary files /dev/null and b/images/channels-and-providers/chat/zulip/step_02.png differ
diff --git a/images/channels-and-providers/chat/zulip/step_03.png b/images/channels-and-providers/chat/zulip/step_03.png
new file mode 100644
index 00000000..7fa22ef3
Binary files /dev/null and b/images/channels-and-providers/chat/zulip/step_03.png differ
diff --git a/images/channels-and-providers/chat/zulip/step_04.png b/images/channels-and-providers/chat/zulip/step_04.png
new file mode 100644
index 00000000..09d9eb2a
Binary files /dev/null and b/images/channels-and-providers/chat/zulip/step_04.png differ
diff --git a/images/channels-and-providers/email/writing-email-template/email-blocks.gif b/images/channels-and-providers/email/writing-email-template/email-blocks.gif
new file mode 100644
index 00000000..0414e6e3
Binary files /dev/null and b/images/channels-and-providers/email/writing-email-template/email-blocks.gif differ
diff --git a/images/channels-and-providers/email/writing-email-template/email-editor.png b/images/channels-and-providers/email/writing-email-template/email-editor.png
new file mode 100644
index 00000000..ca5240d1
Binary files /dev/null and b/images/channels-and-providers/email/writing-email-template/email-editor.png differ
diff --git a/images/channels-and-providers/email/writing-email-template/show-block.gif b/images/channels-and-providers/email/writing-email-template/show-block.gif
new file mode 100644
index 00000000..8a9d775c
Binary files /dev/null and b/images/channels-and-providers/email/writing-email-template/show-block.gif differ
diff --git a/images/channels-and-providers/notifications-to-channel.png b/images/channels-and-providers/notifications-to-channel.png
new file mode 100644
index 00000000..80d23813
Binary files /dev/null and b/images/channels-and-providers/notifications-to-channel.png differ
diff --git a/images/channels-and-providers/permission-to-access.png b/images/channels-and-providers/permission-to-access.png
new file mode 100644
index 00000000..33e730a6
Binary files /dev/null and b/images/channels-and-providers/permission-to-access.png differ
diff --git a/images/channels-and-providers/push/onesignal/select-external-id-option.gif b/images/channels-and-providers/push/onesignal/select-external-id-option.gif
new file mode 100644
index 00000000..93b5e2f4
Binary files /dev/null and b/images/channels-and-providers/push/onesignal/select-external-id-option.gif differ
diff --git a/images/channels-and-providers/redirect-url.png b/images/channels-and-providers/redirect-url.png
new file mode 100644
index 00000000..9c2fea98
Binary files /dev/null and b/images/channels-and-providers/redirect-url.png differ
diff --git a/images/channels-and-providers/shreable-url.png b/images/channels-and-providers/shreable-url.png
new file mode 100644
index 00000000..23149070
Binary files /dev/null and b/images/channels-and-providers/shreable-url.png differ
diff --git a/images/channels-and-providers/sms/africas-talking/api-key-africa.png b/images/channels-and-providers/sms/africas-talking/api-key-africa.png
new file mode 100644
index 00000000..61ec5349
Binary files /dev/null and b/images/channels-and-providers/sms/africas-talking/api-key-africa.png differ
diff --git a/images/channels-and-providers/sms/africas-talking/image-57.png b/images/channels-and-providers/sms/africas-talking/image-57.png
new file mode 100644
index 00000000..3d98f88b
Binary files /dev/null and b/images/channels-and-providers/sms/africas-talking/image-57.png differ
diff --git a/images/channels-and-providers/sms/africas-talking/image-58.png b/images/channels-and-providers/sms/africas-talking/image-58.png
new file mode 100644
index 00000000..6abd75a6
Binary files /dev/null and b/images/channels-and-providers/sms/africas-talking/image-58.png differ
diff --git a/images/channels-and-providers/sms/africas-talking/image-59.png b/images/channels-and-providers/sms/africas-talking/image-59.png
new file mode 100644
index 00000000..b51f4d16
Binary files /dev/null and b/images/channels-and-providers/sms/africas-talking/image-59.png differ
diff --git a/images/channels-and-providers/sms/africas-talking/image-60.png b/images/channels-and-providers/sms/africas-talking/image-60.png
new file mode 100644
index 00000000..dfa27100
Binary files /dev/null and b/images/channels-and-providers/sms/africas-talking/image-60.png differ
diff --git a/images/channels-and-providers/sms/africas-talking/image-61.png b/images/channels-and-providers/sms/africas-talking/image-61.png
new file mode 100644
index 00000000..585f053a
Binary files /dev/null and b/images/channels-and-providers/sms/africas-talking/image-61.png differ
diff --git a/images/channels-and-providers/sms/africas-talking/new app-africa-talking.png b/images/channels-and-providers/sms/africas-talking/new app-africa-talking.png
new file mode 100644
index 00000000..1f74b9df
Binary files /dev/null and b/images/channels-and-providers/sms/africas-talking/new app-africa-talking.png differ
diff --git a/images/channels-and-providers/sms/africas-talking/new-created-app-africa.png b/images/channels-and-providers/sms/africas-talking/new-created-app-africa.png
new file mode 100644
index 00000000..426eaea5
Binary files /dev/null and b/images/channels-and-providers/sms/africas-talking/new-created-app-africa.png differ
diff --git a/images/channels-and-providers/sms/africas-talking/new-team.png b/images/channels-and-providers/sms/africas-talking/new-team.png
new file mode 100644
index 00000000..bfe8fbe9
Binary files /dev/null and b/images/channels-and-providers/sms/africas-talking/new-team.png differ
diff --git a/images/channels-and-providers/sms/africas-talking/team-africa.png b/images/channels-and-providers/sms/africas-talking/team-africa.png
new file mode 100644
index 00000000..f8666657
Binary files /dev/null and b/images/channels-and-providers/sms/africas-talking/team-africa.png differ
diff --git a/images/channels-and-providers/sms/clickatell/step_01.png b/images/channels-and-providers/sms/clickatell/step_01.png
new file mode 100644
index 00000000..118a9680
Binary files /dev/null and b/images/channels-and-providers/sms/clickatell/step_01.png differ
diff --git a/images/channels-and-providers/sms/clickatell/step_02.png b/images/channels-and-providers/sms/clickatell/step_02.png
new file mode 100644
index 00000000..2dd08c99
Binary files /dev/null and b/images/channels-and-providers/sms/clickatell/step_02.png differ
diff --git a/images/channels-and-providers/sms/clickatell/step_03.png b/images/channels-and-providers/sms/clickatell/step_03.png
new file mode 100644
index 00000000..c7f684da
Binary files /dev/null and b/images/channels-and-providers/sms/clickatell/step_03.png differ
diff --git a/images/channels-and-providers/sms/clickatell/step_04.png b/images/channels-and-providers/sms/clickatell/step_04.png
new file mode 100644
index 00000000..eb2b044c
Binary files /dev/null and b/images/channels-and-providers/sms/clickatell/step_04.png differ
diff --git a/images/channels-and-providers/sms/clickatell/step_05.png b/images/channels-and-providers/sms/clickatell/step_05.png
new file mode 100644
index 00000000..8f99e64e
Binary files /dev/null and b/images/channels-and-providers/sms/clickatell/step_05.png differ
diff --git a/images/channels-and-providers/sms/clickatell/step_06.png b/images/channels-and-providers/sms/clickatell/step_06.png
new file mode 100644
index 00000000..388db83e
Binary files /dev/null and b/images/channels-and-providers/sms/clickatell/step_06.png differ
diff --git a/images/channels-and-providers/sms/firetext/step_01.png b/images/channels-and-providers/sms/firetext/step_01.png
new file mode 100644
index 00000000..bb680614
Binary files /dev/null and b/images/channels-and-providers/sms/firetext/step_01.png differ
diff --git a/images/channels-and-providers/sms/firetext/step_02.png b/images/channels-and-providers/sms/firetext/step_02.png
new file mode 100644
index 00000000..2edb1406
Binary files /dev/null and b/images/channels-and-providers/sms/firetext/step_02.png differ
diff --git a/images/channels-and-providers/sms/firetext/step_03.png b/images/channels-and-providers/sms/firetext/step_03.png
new file mode 100644
index 00000000..6684c6c4
Binary files /dev/null and b/images/channels-and-providers/sms/firetext/step_03.png differ
diff --git a/images/channels-and-providers/sms/infobip/image-63.png b/images/channels-and-providers/sms/infobip/image-63.png
new file mode 100644
index 00000000..3c3dbbcf
Binary files /dev/null and b/images/channels-and-providers/sms/infobip/image-63.png differ
diff --git a/images/channels-and-providers/sms/infobip/image-64.png b/images/channels-and-providers/sms/infobip/image-64.png
new file mode 100644
index 00000000..9819de8f
Binary files /dev/null and b/images/channels-and-providers/sms/infobip/image-64.png differ
diff --git a/images/channels-and-providers/sms/infobip/image-65.png b/images/channels-and-providers/sms/infobip/image-65.png
new file mode 100644
index 00000000..3da2c516
Binary files /dev/null and b/images/channels-and-providers/sms/infobip/image-65.png differ
diff --git a/images/channels-and-providers/sms/infobip/image-66.png b/images/channels-and-providers/sms/infobip/image-66.png
new file mode 100644
index 00000000..97555e34
Binary files /dev/null and b/images/channels-and-providers/sms/infobip/image-66.png differ
diff --git a/images/channels-and-providers/sms/sendchamp/image-67.png b/images/channels-and-providers/sms/sendchamp/image-67.png
new file mode 100644
index 00000000..d6e2e26b
Binary files /dev/null and b/images/channels-and-providers/sms/sendchamp/image-67.png differ
diff --git a/images/channels-and-providers/sms/sendchamp/image-68.png b/images/channels-and-providers/sms/sendchamp/image-68.png
new file mode 100644
index 00000000..c5d48482
Binary files /dev/null and b/images/channels-and-providers/sms/sendchamp/image-68.png differ
diff --git a/images/channels-and-providers/sms/sendchamp/image-69.png b/images/channels-and-providers/sms/sendchamp/image-69.png
new file mode 100644
index 00000000..fa608f85
Binary files /dev/null and b/images/channels-and-providers/sms/sendchamp/image-69.png differ
diff --git a/images/channels-and-providers/sms/sendchamp/image-70.png b/images/channels-and-providers/sms/sendchamp/image-70.png
new file mode 100644
index 00000000..7527d3f5
Binary files /dev/null and b/images/channels-and-providers/sms/sendchamp/image-70.png differ
diff --git a/images/channels-and-providers/sms/sendchamp/image-71.png b/images/channels-and-providers/sms/sendchamp/image-71.png
new file mode 100644
index 00000000..8afd3518
Binary files /dev/null and b/images/channels-and-providers/sms/sendchamp/image-71.png differ
diff --git a/images/channels-and-providers/sms/termii/image-72.png b/images/channels-and-providers/sms/termii/image-72.png
new file mode 100644
index 00000000..7dffc897
Binary files /dev/null and b/images/channels-and-providers/sms/termii/image-72.png differ
diff --git a/images/channels-and-providers/sms/termii/image-73.png b/images/channels-and-providers/sms/termii/image-73.png
new file mode 100644
index 00000000..49dcdf21
Binary files /dev/null and b/images/channels-and-providers/sms/termii/image-73.png differ
diff --git a/images/channels-and-providers/sms/termii/image-74.png b/images/channels-and-providers/sms/termii/image-74.png
new file mode 100644
index 00000000..b422d650
Binary files /dev/null and b/images/channels-and-providers/sms/termii/image-74.png differ
diff --git a/images/channels-and-providers/sms/termii/image-75.png b/images/channels-and-providers/sms/termii/image-75.png
new file mode 100644
index 00000000..dd76cd07
Binary files /dev/null and b/images/channels-and-providers/sms/termii/image-75.png differ
diff --git a/images/channels.gif b/images/channels.gif
new file mode 100644
index 00000000..24030b09
Binary files /dev/null and b/images/channels.gif differ
diff --git a/images/community/feature-flags/multi-provider-feature.png b/images/community/feature-flags/multi-provider-feature.png
new file mode 100644
index 00000000..1fb01a07
Binary files /dev/null and b/images/community/feature-flags/multi-provider-feature.png differ
diff --git a/images/community/feature-flags/template-store-feature.png b/images/community/feature-flags/template-store-feature.png
new file mode 100644
index 00000000..d1f81886
Binary files /dev/null and b/images/community/feature-flags/template-store-feature.png differ
diff --git a/images/community/feature-flags/tenants-feature.png b/images/community/feature-flags/tenants-feature.png
new file mode 100644
index 00000000..f402516c
Binary files /dev/null and b/images/community/feature-flags/tenants-feature.png differ
diff --git a/images/community/machine-setup/aws-cli.png b/images/community/machine-setup/aws-cli.png
new file mode 100644
index 00000000..cbe36ed6
Binary files /dev/null and b/images/community/machine-setup/aws-cli.png differ
diff --git a/images/community/machine-setup/dock.png b/images/community/machine-setup/dock.png
new file mode 100644
index 00000000..9548063b
Binary files /dev/null and b/images/community/machine-setup/dock.png differ
diff --git a/images/configure-in-app-react.png b/images/configure-in-app-react.png
new file mode 100644
index 00000000..17e362f3
Binary files /dev/null and b/images/configure-in-app-react.png differ
diff --git a/images/content-creation-design/chat-editor.png b/images/content-creation-design/chat-editor.png
new file mode 100644
index 00000000..a322cde4
Binary files /dev/null and b/images/content-creation-design/chat-editor.png differ
diff --git a/images/content-creation-design/push-editor.png b/images/content-creation-design/push-editor.png
new file mode 100644
index 00000000..6ca53f26
Binary files /dev/null and b/images/content-creation-design/push-editor.png differ
diff --git a/images/content-of-an-special-offer-email.png b/images/content-of-an-special-offer-email.png
new file mode 100644
index 00000000..df67deee
Binary files /dev/null and b/images/content-of-an-special-offer-email.png differ
diff --git a/images/controls-autocomplete.gif b/images/controls-autocomplete.gif
new file mode 100644
index 00000000..4f66feab
Binary files /dev/null and b/images/controls-autocomplete.gif differ
diff --git a/images/create&edit-layouts.png b/images/create&edit-layouts.png
new file mode 100644
index 00000000..320883c3
Binary files /dev/null and b/images/create&edit-layouts.png differ
diff --git a/images/create-workflow-nextjs.png b/images/create-workflow-nextjs.png
new file mode 100644
index 00000000..2038483c
Binary files /dev/null and b/images/create-workflow-nextjs.png differ
diff --git a/images/create-workflow.png b/images/create-workflow.png
new file mode 100644
index 00000000..2038483c
Binary files /dev/null and b/images/create-workflow.png differ
diff --git a/images/custom-code-input.png b/images/custom-code-input.png
new file mode 100644
index 00000000..0d65eac8
Binary files /dev/null and b/images/custom-code-input.png differ
diff --git a/images/custom-code-output.png b/images/custom-code-output.png
new file mode 100644
index 00000000..5af040eb
Binary files /dev/null and b/images/custom-code-output.png differ
diff --git a/images/custom-style.png b/images/custom-style.png
new file mode 100644
index 00000000..91c56e8c
Binary files /dev/null and b/images/custom-style.png differ
diff --git a/images/custom-var-react.png b/images/custom-var-react.png
new file mode 100644
index 00000000..8661a754
Binary files /dev/null and b/images/custom-var-react.png differ
diff --git a/images/custom-vars.png b/images/custom-vars.png
new file mode 100644
index 00000000..2c732d15
Binary files /dev/null and b/images/custom-vars.png differ
diff --git a/images/dark-is-defined-filter.png b/images/dark-is-defined-filter.png
new file mode 100644
index 00000000..b8c0d7f8
Binary files /dev/null and b/images/dark-is-defined-filter.png differ
diff --git a/images/dark-webhook-rule-example.png b/images/dark-webhook-rule-example.png
new file mode 100644
index 00000000..cb6e650b
Binary files /dev/null and b/images/dark-webhook-rule-example.png differ
diff --git a/images/default-providers/SCR-20230829-uosu.png b/images/default-providers/SCR-20230829-uosu.png
new file mode 100644
index 00000000..51ce3aac
Binary files /dev/null and b/images/default-providers/SCR-20230829-uosu.png differ
diff --git a/images/default-providers/SCR-20230829-uppz.png b/images/default-providers/SCR-20230829-uppz.png
new file mode 100644
index 00000000..6cdfead4
Binary files /dev/null and b/images/default-providers/SCR-20230829-uppz.png differ
diff --git a/images/default-providers/SCR-20230829-uqld.png b/images/default-providers/SCR-20230829-uqld.png
new file mode 100644
index 00000000..8e319f27
Binary files /dev/null and b/images/default-providers/SCR-20230829-uqld.png differ
diff --git a/images/default-sub-kotlin.png b/images/default-sub-kotlin.png
new file mode 100644
index 00000000..6a699b67
Binary files /dev/null and b/images/default-sub-kotlin.png differ
diff --git a/images/default-ui.png b/images/default-ui.png
new file mode 100644
index 00000000..aa240d8a
Binary files /dev/null and b/images/default-ui.png differ
diff --git a/images/digest-app.gif b/images/digest-app.gif
new file mode 100644
index 00000000..14d8d383
Binary files /dev/null and b/images/digest-app.gif differ
diff --git a/images/digest-email.png b/images/digest-email.png
new file mode 100644
index 00000000..511fac92
Binary files /dev/null and b/images/digest-email.png differ
diff --git a/images/digest-flow.png b/images/digest-flow.png
new file mode 100644
index 00000000..2945978d
Binary files /dev/null and b/images/digest-flow.png differ
diff --git a/images/digest-info.png b/images/digest-info.png
new file mode 100644
index 00000000..b7eff060
Binary files /dev/null and b/images/digest-info.png differ
diff --git a/images/digest-node.png b/images/digest-node.png
new file mode 100644
index 00000000..5d1eb02a
Binary files /dev/null and b/images/digest-node.png differ
diff --git a/images/digest-settings.png b/images/digest-settings.png
new file mode 100644
index 00000000..a2eeac71
Binary files /dev/null and b/images/digest-settings.png differ
diff --git a/images/digest.gif b/images/digest.gif
new file mode 100644
index 00000000..507a2c85
Binary files /dev/null and b/images/digest.gif differ
diff --git a/images/digest.png b/images/digest.png
new file mode 100644
index 00000000..a4a4d0f6
Binary files /dev/null and b/images/digest.png differ
diff --git a/images/discord-guide/active.png b/images/discord-guide/active.png
new file mode 100644
index 00000000..30d8de18
Binary files /dev/null and b/images/discord-guide/active.png differ
diff --git a/images/discord-guide/channel.png b/images/discord-guide/channel.png
new file mode 100644
index 00000000..7989d4a0
Binary files /dev/null and b/images/discord-guide/channel.png differ
diff --git a/images/discord-guide/chat.png b/images/discord-guide/chat.png
new file mode 100644
index 00000000..476a74f5
Binary files /dev/null and b/images/discord-guide/chat.png differ
diff --git a/images/discord-guide/edit.png b/images/discord-guide/edit.png
new file mode 100644
index 00000000..3df599d9
Binary files /dev/null and b/images/discord-guide/edit.png differ
diff --git a/images/discord-guide/integrations.png b/images/discord-guide/integrations.png
new file mode 100644
index 00000000..a1f943bf
Binary files /dev/null and b/images/discord-guide/integrations.png differ
diff --git a/images/discord-guide/outputt.gif b/images/discord-guide/outputt.gif
new file mode 100644
index 00000000..3b197df8
Binary files /dev/null and b/images/discord-guide/outputt.gif differ
diff --git a/images/discord-guide/snippet.png b/images/discord-guide/snippet.png
new file mode 100644
index 00000000..768e3777
Binary files /dev/null and b/images/discord-guide/snippet.png differ
diff --git a/images/discord-guide/webhook.png b/images/discord-guide/webhook.png
new file mode 100644
index 00000000..cfffd46d
Binary files /dev/null and b/images/discord-guide/webhook.png differ
diff --git a/images/edit-nextjs.png b/images/edit-nextjs.png
new file mode 100644
index 00000000..25887516
Binary files /dev/null and b/images/edit-nextjs.png differ
diff --git a/images/email-digest-engine-demo.png b/images/email-digest-engine-demo.png
new file mode 100644
index 00000000..d9783610
Binary files /dev/null and b/images/email-digest-engine-demo.png differ
diff --git a/images/email-digest.gif b/images/email-digest.gif
new file mode 100644
index 00000000..20612ab2
Binary files /dev/null and b/images/email-digest.gif differ
diff --git a/images/email-node.png b/images/email-node.png
new file mode 100644
index 00000000..4115076f
Binary files /dev/null and b/images/email-node.png differ
diff --git a/images/email-nodejs.png b/images/email-nodejs.png
new file mode 100644
index 00000000..6a3aa4f2
Binary files /dev/null and b/images/email-nodejs.png differ
diff --git a/images/email2-nodejs.png b/images/email2-nodejs.png
new file mode 100644
index 00000000..26e89e84
Binary files /dev/null and b/images/email2-nodejs.png differ
diff --git a/images/email3-nodejs.png b/images/email3-nodejs.png
new file mode 100644
index 00000000..0108941f
Binary files /dev/null and b/images/email3-nodejs.png differ
diff --git a/images/execution-details-for-digest.png b/images/execution-details-for-digest.png
new file mode 100644
index 00000000..6c6f4f25
Binary files /dev/null and b/images/execution-details-for-digest.png differ
diff --git a/images/execution-details-for-sms.png b/images/execution-details-for-sms.png
new file mode 100644
index 00000000..2c71d338
Binary files /dev/null and b/images/execution-details-for-sms.png differ
diff --git a/images/execution-details.png b/images/execution-details.png
new file mode 100644
index 00000000..15567dda
Binary files /dev/null and b/images/execution-details.png differ
diff --git a/images/execution-details2.png b/images/execution-details2.png
new file mode 100644
index 00000000..4ae1995e
Binary files /dev/null and b/images/execution-details2.png differ
diff --git a/images/fcm-novu-rna/android.png b/images/fcm-novu-rna/android.png
new file mode 100644
index 00000000..5e660487
Binary files /dev/null and b/images/fcm-novu-rna/android.png differ
diff --git a/images/fcm-novu-rna/cd.png b/images/fcm-novu-rna/cd.png
new file mode 100644
index 00000000..082aa7f8
Binary files /dev/null and b/images/fcm-novu-rna/cd.png differ
diff --git a/images/fcm-novu-rna/console.png b/images/fcm-novu-rna/console.png
new file mode 100644
index 00000000..b6d8619a
Binary files /dev/null and b/images/fcm-novu-rna/console.png differ
diff --git a/images/fcm-novu-rna/custom.png b/images/fcm-novu-rna/custom.png
new file mode 100644
index 00000000..d2299436
Binary files /dev/null and b/images/fcm-novu-rna/custom.png differ
diff --git a/images/fcm-novu-rna/dynamic.png b/images/fcm-novu-rna/dynamic.png
new file mode 100644
index 00000000..d8af0294
Binary files /dev/null and b/images/fcm-novu-rna/dynamic.png differ
diff --git a/images/fcm-novu-rna/fp.png b/images/fcm-novu-rna/fp.png
new file mode 100644
index 00000000..5ee4f2d2
Binary files /dev/null and b/images/fcm-novu-rna/fp.png differ
diff --git a/images/fcm-novu-rna/greet.png b/images/fcm-novu-rna/greet.png
new file mode 100644
index 00000000..ec066e21
Binary files /dev/null and b/images/fcm-novu-rna/greet.png differ
diff --git a/images/fcm-novu-rna/hardcoded.png b/images/fcm-novu-rna/hardcoded.png
new file mode 100644
index 00000000..11af7e7d
Binary files /dev/null and b/images/fcm-novu-rna/hardcoded.png differ
diff --git a/images/fcm-novu-rna/image.png b/images/fcm-novu-rna/image.png
new file mode 100644
index 00000000..c277a216
Binary files /dev/null and b/images/fcm-novu-rna/image.png differ
diff --git a/images/fcm-novu-rna/json.png b/images/fcm-novu-rna/json.png
new file mode 100644
index 00000000..f049a8f0
Binary files /dev/null and b/images/fcm-novu-rna/json.png differ
diff --git a/images/fcm-novu-rna/key.png b/images/fcm-novu-rna/key.png
new file mode 100644
index 00000000..bd64d94c
Binary files /dev/null and b/images/fcm-novu-rna/key.png differ
diff --git a/images/fcm-novu-rna/keys.png b/images/fcm-novu-rna/keys.png
new file mode 100644
index 00000000..b4a892bf
Binary files /dev/null and b/images/fcm-novu-rna/keys.png differ
diff --git a/images/fcm-novu-rna/location.png b/images/fcm-novu-rna/location.png
new file mode 100644
index 00000000..77301ad6
Binary files /dev/null and b/images/fcm-novu-rna/location.png differ
diff --git a/images/fcm-novu-rna/package.png b/images/fcm-novu-rna/package.png
new file mode 100644
index 00000000..0b225f9b
Binary files /dev/null and b/images/fcm-novu-rna/package.png differ
diff --git a/images/fcm-novu-rna/priority.png b/images/fcm-novu-rna/priority.png
new file mode 100644
index 00000000..9d257195
Binary files /dev/null and b/images/fcm-novu-rna/priority.png differ
diff --git a/images/fcm-novu-rna/qr.png b/images/fcm-novu-rna/qr.png
new file mode 100644
index 00000000..3da0ea2e
Binary files /dev/null and b/images/fcm-novu-rna/qr.png differ
diff --git a/images/fcm-novu-rna/serv.png b/images/fcm-novu-rna/serv.png
new file mode 100644
index 00000000..9a67d0f4
Binary files /dev/null and b/images/fcm-novu-rna/serv.png differ
diff --git a/images/fcm-novu-rna/server.png b/images/fcm-novu-rna/server.png
new file mode 100644
index 00000000..80bed324
Binary files /dev/null and b/images/fcm-novu-rna/server.png differ
diff --git a/images/fcm-novu-rna/settings.png b/images/fcm-novu-rna/settings.png
new file mode 100644
index 00000000..0f858608
Binary files /dev/null and b/images/fcm-novu-rna/settings.png differ
diff --git a/images/fcm-novu-rna/snippet.png b/images/fcm-novu-rna/snippet.png
new file mode 100644
index 00000000..66cf00eb
Binary files /dev/null and b/images/fcm-novu-rna/snippet.png differ
diff --git a/images/fcm-novu-rna/title.png b/images/fcm-novu-rna/title.png
new file mode 100644
index 00000000..816b902c
Binary files /dev/null and b/images/fcm-novu-rna/title.png differ
diff --git a/images/fcm-web-push/add.png b/images/fcm-web-push/add.png
new file mode 100644
index 00000000..c8acf9a6
Binary files /dev/null and b/images/fcm-web-push/add.png differ
diff --git a/images/fcm-web-push/blank.png b/images/fcm-web-push/blank.png
new file mode 100644
index 00000000..f79b04e5
Binary files /dev/null and b/images/fcm-web-push/blank.png differ
diff --git a/images/fcm-web-push/drag.png b/images/fcm-web-push/drag.png
new file mode 100644
index 00000000..563e957d
Binary files /dev/null and b/images/fcm-web-push/drag.png differ
diff --git a/images/fcm-web-push/error.png b/images/fcm-web-push/error.png
new file mode 100644
index 00000000..0f3fd857
Binary files /dev/null and b/images/fcm-web-push/error.png differ
diff --git a/images/fcm-web-push/identifiers.png b/images/fcm-web-push/identifiers.png
new file mode 100644
index 00000000..411637f7
Binary files /dev/null and b/images/fcm-web-push/identifiers.png differ
diff --git a/images/fcm-web-push/novu-dashboard.png b/images/fcm-web-push/novu-dashboard.png
new file mode 100644
index 00000000..f4062e66
Binary files /dev/null and b/images/fcm-web-push/novu-dashboard.png differ
diff --git a/images/fcm-web-push/project.png b/images/fcm-web-push/project.png
new file mode 100644
index 00000000..89c66287
Binary files /dev/null and b/images/fcm-web-push/project.png differ
diff --git a/images/fcm-web-push/register.png b/images/fcm-web-push/register.png
new file mode 100644
index 00000000..b4fa8f99
Binary files /dev/null and b/images/fcm-web-push/register.png differ
diff --git a/images/fcm-web-push/trigger.png b/images/fcm-web-push/trigger.png
new file mode 100644
index 00000000..292e3549
Binary files /dev/null and b/images/fcm-web-push/trigger.png differ
diff --git a/images/fcm-web-push/vapid.png b/images/fcm-web-push/vapid.png
new file mode 100644
index 00000000..f01e5e21
Binary files /dev/null and b/images/fcm-web-push/vapid.png differ
diff --git a/images/fcm-web-push/web.png b/images/fcm-web-push/web.png
new file mode 100644
index 00000000..a00f3ce9
Binary files /dev/null and b/images/fcm-web-push/web.png differ
diff --git a/images/fcm-web-push/workflows.png b/images/fcm-web-push/workflows.png
new file mode 100644
index 00000000..1533253f
Binary files /dev/null and b/images/fcm-web-push/workflows.png differ
diff --git a/images/filtering-by-channel.png b/images/filtering-by-channel.png
new file mode 100644
index 00000000..c85de1c0
Binary files /dev/null and b/images/filtering-by-channel.png differ
diff --git a/images/filtering-by-workflow.png b/images/filtering-by-workflow.png
new file mode 100644
index 00000000..d68313ac
Binary files /dev/null and b/images/filtering-by-workflow.png differ
diff --git a/images/framework-mjml/1.png b/images/framework-mjml/1.png
new file mode 100644
index 00000000..b993d6b1
Binary files /dev/null and b/images/framework-mjml/1.png differ
diff --git a/images/framework-mjml/2.png b/images/framework-mjml/2.png
new file mode 100644
index 00000000..1f431ee9
Binary files /dev/null and b/images/framework-mjml/2.png differ
diff --git a/images/framework-mjml/3.png b/images/framework-mjml/3.png
new file mode 100644
index 00000000..ea0ce72a
Binary files /dev/null and b/images/framework-mjml/3.png differ
diff --git a/images/framework-mjml/4.png b/images/framework-mjml/4.png
new file mode 100644
index 00000000..e31bc759
Binary files /dev/null and b/images/framework-mjml/4.png differ
diff --git a/images/framework-mjml/5.png b/images/framework-mjml/5.png
new file mode 100644
index 00000000..a100b3b3
Binary files /dev/null and b/images/framework-mjml/5.png differ
diff --git a/images/framework-mjml/6.png b/images/framework-mjml/6.png
new file mode 100644
index 00000000..fea7eb4b
Binary files /dev/null and b/images/framework-mjml/6.png differ
diff --git a/images/framework-mjml/7.png b/images/framework-mjml/7.png
new file mode 100644
index 00000000..53268b18
Binary files /dev/null and b/images/framework-mjml/7.png differ
diff --git a/images/framework-mjml/8.png b/images/framework-mjml/8.png
new file mode 100644
index 00000000..cf5aeebf
Binary files /dev/null and b/images/framework-mjml/8.png differ
diff --git a/images/framework-nuxt-vuemail/eight.png b/images/framework-nuxt-vuemail/eight.png
new file mode 100644
index 00000000..2ebc9b0d
Binary files /dev/null and b/images/framework-nuxt-vuemail/eight.png differ
diff --git a/images/framework-nuxt-vuemail/eleven.png b/images/framework-nuxt-vuemail/eleven.png
new file mode 100644
index 00000000..00c04458
Binary files /dev/null and b/images/framework-nuxt-vuemail/eleven.png differ
diff --git a/images/framework-nuxt-vuemail/five.png b/images/framework-nuxt-vuemail/five.png
new file mode 100644
index 00000000..67d062c7
Binary files /dev/null and b/images/framework-nuxt-vuemail/five.png differ
diff --git a/images/framework-nuxt-vuemail/four.png b/images/framework-nuxt-vuemail/four.png
new file mode 100644
index 00000000..36be08fe
Binary files /dev/null and b/images/framework-nuxt-vuemail/four.png differ
diff --git a/images/framework-nuxt-vuemail/nine.png b/images/framework-nuxt-vuemail/nine.png
new file mode 100644
index 00000000..9bcf7af0
Binary files /dev/null and b/images/framework-nuxt-vuemail/nine.png differ
diff --git a/images/framework-nuxt-vuemail/one.png b/images/framework-nuxt-vuemail/one.png
new file mode 100644
index 00000000..57d42d34
Binary files /dev/null and b/images/framework-nuxt-vuemail/one.png differ
diff --git a/images/framework-nuxt-vuemail/seven.png b/images/framework-nuxt-vuemail/seven.png
new file mode 100644
index 00000000..de2002bd
Binary files /dev/null and b/images/framework-nuxt-vuemail/seven.png differ
diff --git a/images/framework-nuxt-vuemail/six.png b/images/framework-nuxt-vuemail/six.png
new file mode 100644
index 00000000..61dbdb41
Binary files /dev/null and b/images/framework-nuxt-vuemail/six.png differ
diff --git a/images/framework-nuxt-vuemail/ten.png b/images/framework-nuxt-vuemail/ten.png
new file mode 100644
index 00000000..ce13e599
Binary files /dev/null and b/images/framework-nuxt-vuemail/ten.png differ
diff --git a/images/framework-nuxt-vuemail/three.png b/images/framework-nuxt-vuemail/three.png
new file mode 100644
index 00000000..9d13c68f
Binary files /dev/null and b/images/framework-nuxt-vuemail/three.png differ
diff --git a/images/framework-nuxt-vuemail/two.png b/images/framework-nuxt-vuemail/two.png
new file mode 100644
index 00000000..4b062512
Binary files /dev/null and b/images/framework-nuxt-vuemail/two.png differ
diff --git a/images/framework-react-email/1.png b/images/framework-react-email/1.png
new file mode 100644
index 00000000..94d43d3f
Binary files /dev/null and b/images/framework-react-email/1.png differ
diff --git a/images/framework-react-email/10.png b/images/framework-react-email/10.png
new file mode 100644
index 00000000..e987c4fb
Binary files /dev/null and b/images/framework-react-email/10.png differ
diff --git a/images/framework-react-email/11.png b/images/framework-react-email/11.png
new file mode 100644
index 00000000..787c2c3a
Binary files /dev/null and b/images/framework-react-email/11.png differ
diff --git a/images/framework-react-email/12.png b/images/framework-react-email/12.png
new file mode 100644
index 00000000..a7a5615e
Binary files /dev/null and b/images/framework-react-email/12.png differ
diff --git a/images/framework-react-email/13.png b/images/framework-react-email/13.png
new file mode 100644
index 00000000..53268b18
Binary files /dev/null and b/images/framework-react-email/13.png differ
diff --git a/images/framework-react-email/14.png b/images/framework-react-email/14.png
new file mode 100644
index 00000000..43e35082
Binary files /dev/null and b/images/framework-react-email/14.png differ
diff --git a/images/framework-react-email/15.png b/images/framework-react-email/15.png
new file mode 100644
index 00000000..d5adf3ea
Binary files /dev/null and b/images/framework-react-email/15.png differ
diff --git a/images/framework-react-email/16.png b/images/framework-react-email/16.png
new file mode 100644
index 00000000..ec397d9b
Binary files /dev/null and b/images/framework-react-email/16.png differ
diff --git a/images/framework-react-email/2.png b/images/framework-react-email/2.png
new file mode 100644
index 00000000..33ad5f38
Binary files /dev/null and b/images/framework-react-email/2.png differ
diff --git a/images/framework-react-email/3.png b/images/framework-react-email/3.png
new file mode 100644
index 00000000..0be786aa
Binary files /dev/null and b/images/framework-react-email/3.png differ
diff --git a/images/framework-react-email/4.png b/images/framework-react-email/4.png
new file mode 100644
index 00000000..8c6d4939
Binary files /dev/null and b/images/framework-react-email/4.png differ
diff --git a/images/framework-react-email/5.png b/images/framework-react-email/5.png
new file mode 100644
index 00000000..d54923ab
Binary files /dev/null and b/images/framework-react-email/5.png differ
diff --git a/images/framework-react-email/6.png b/images/framework-react-email/6.png
new file mode 100644
index 00000000..b683e99d
Binary files /dev/null and b/images/framework-react-email/6.png differ
diff --git a/images/framework-react-email/7.png b/images/framework-react-email/7.png
new file mode 100644
index 00000000..198e1f8f
Binary files /dev/null and b/images/framework-react-email/7.png differ
diff --git a/images/framework-react-email/8.png b/images/framework-react-email/8.png
new file mode 100644
index 00000000..cfa96791
Binary files /dev/null and b/images/framework-react-email/8.png differ
diff --git a/images/framework-react-email/9.png b/images/framework-react-email/9.png
new file mode 100644
index 00000000..389252c3
Binary files /dev/null and b/images/framework-react-email/9.png differ
diff --git a/images/framework-remix/novu-dev-studio-first-run.png b/images/framework-remix/novu-dev-studio-first-run.png
new file mode 100644
index 00000000..68c2a98d
Binary files /dev/null and b/images/framework-remix/novu-dev-studio-first-run.png differ
diff --git a/images/framework-remix/signup-email-workflow-preview.png b/images/framework-remix/signup-email-workflow-preview.png
new file mode 100644
index 00000000..ee8fc4d2
Binary files /dev/null and b/images/framework-remix/signup-email-workflow-preview.png differ
diff --git a/images/framework-svelte-email/dev-studio-first-run.png b/images/framework-svelte-email/dev-studio-first-run.png
new file mode 100644
index 00000000..78c25123
Binary files /dev/null and b/images/framework-svelte-email/dev-studio-first-run.png differ
diff --git a/images/framework-svelte-email/email-workflow-preview.png b/images/framework-svelte-email/email-workflow-preview.png
new file mode 100644
index 00000000..8de52fd2
Binary files /dev/null and b/images/framework-svelte-email/email-workflow-preview.png differ
diff --git a/images/framework/diagram.png b/images/framework/diagram.png
new file mode 100644
index 00000000..62a6efe2
Binary files /dev/null and b/images/framework/diagram.png differ
diff --git a/images/get-started.png b/images/get-started.png
new file mode 100644
index 00000000..3d350934
Binary files /dev/null and b/images/get-started.png differ
diff --git a/images/getting-snippet.png b/images/getting-snippet.png
new file mode 100644
index 00000000..5e7376c7
Binary files /dev/null and b/images/getting-snippet.png differ
diff --git a/images/getting-started/send-your-first-notification/SCR-20230830-qyst.png b/images/getting-started/send-your-first-notification/SCR-20230830-qyst.png
new file mode 100644
index 00000000..981fa32b
Binary files /dev/null and b/images/getting-started/send-your-first-notification/SCR-20230830-qyst.png differ
diff --git a/images/getting-started/send-your-first-notification/SCR-20230830-qywy.png b/images/getting-started/send-your-first-notification/SCR-20230830-qywy.png
new file mode 100644
index 00000000..b5e4565f
Binary files /dev/null and b/images/getting-started/send-your-first-notification/SCR-20230830-qywy.png differ
diff --git a/images/getting-started/send-your-first-notification/SCR-20230831-jzdl.png b/images/getting-started/send-your-first-notification/SCR-20230831-jzdl.png
new file mode 100644
index 00000000..7be8d7b4
Binary files /dev/null and b/images/getting-started/send-your-first-notification/SCR-20230831-jzdl.png differ
diff --git a/images/getting-started/send-your-first-notification/SCR-20230831-kaqq.png b/images/getting-started/send-your-first-notification/SCR-20230831-kaqq.png
new file mode 100644
index 00000000..5e92d0e9
Binary files /dev/null and b/images/getting-started/send-your-first-notification/SCR-20230831-kaqq.png differ
diff --git a/images/getting-started/send-your-first-notification/SCR-20230831-kcqe.png b/images/getting-started/send-your-first-notification/SCR-20230831-kcqe.png
new file mode 100644
index 00000000..25d1e4c3
Binary files /dev/null and b/images/getting-started/send-your-first-notification/SCR-20230831-kcqe.png differ
diff --git a/images/getting-started/send-your-first-notification/SCR-20230831-kgka.png b/images/getting-started/send-your-first-notification/SCR-20230831-kgka.png
new file mode 100644
index 00000000..724687af
Binary files /dev/null and b/images/getting-started/send-your-first-notification/SCR-20230831-kgka.png differ
diff --git a/images/getting-started/send-your-first-notification/SCR-20230831-khui.png b/images/getting-started/send-your-first-notification/SCR-20230831-khui.png
new file mode 100644
index 00000000..f452039b
Binary files /dev/null and b/images/getting-started/send-your-first-notification/SCR-20230831-khui.png differ
diff --git a/images/getting-started/send-your-first-notification/SCR-20230831-kjhb.png b/images/getting-started/send-your-first-notification/SCR-20230831-kjhb.png
new file mode 100644
index 00000000..3a5f47a2
Binary files /dev/null and b/images/getting-started/send-your-first-notification/SCR-20230831-kjhb.png differ
diff --git a/images/getting-started/send-your-first-notification/SCR-20230831-klfw.png b/images/getting-started/send-your-first-notification/SCR-20230831-klfw.png
new file mode 100644
index 00000000..5af04fb8
Binary files /dev/null and b/images/getting-started/send-your-first-notification/SCR-20230831-klfw.png differ
diff --git a/images/getting-started/send-your-first-notification/SCR-20230831-pkxe.png b/images/getting-started/send-your-first-notification/SCR-20230831-pkxe.png
new file mode 100644
index 00000000..e4cdf79a
Binary files /dev/null and b/images/getting-started/send-your-first-notification/SCR-20230831-pkxe.png differ
diff --git a/images/getting-started/send-your-first-notification/SCR-20230831-plrx.png b/images/getting-started/send-your-first-notification/SCR-20230831-plrx.png
new file mode 100644
index 00000000..d5f30022
Binary files /dev/null and b/images/getting-started/send-your-first-notification/SCR-20230831-plrx.png differ
diff --git a/images/getting-started/send-your-first-notification/SCR-20230831-pmvs.png b/images/getting-started/send-your-first-notification/SCR-20230831-pmvs.png
new file mode 100644
index 00000000..a16c49e5
Binary files /dev/null and b/images/getting-started/send-your-first-notification/SCR-20230831-pmvs.png differ
diff --git a/images/getting-started/send-your-first-notification/SCR-20230901-pung.png b/images/getting-started/send-your-first-notification/SCR-20230901-pung.png
new file mode 100644
index 00000000..f283e027
Binary files /dev/null and b/images/getting-started/send-your-first-notification/SCR-20230901-pung.png differ
diff --git a/images/getting-started/send-your-first-notification/SCR-20230901-puwn.png b/images/getting-started/send-your-first-notification/SCR-20230901-puwn.png
new file mode 100644
index 00000000..92671c86
Binary files /dev/null and b/images/getting-started/send-your-first-notification/SCR-20230901-puwn.png differ
diff --git a/images/getting-started/send-your-first-notification/SCR-20230901-pxbj.png b/images/getting-started/send-your-first-notification/SCR-20230901-pxbj.png
new file mode 100644
index 00000000..a7f4d8c1
Binary files /dev/null and b/images/getting-started/send-your-first-notification/SCR-20230901-pxbj.png differ
diff --git a/images/getting-started/send-your-first-notification/SCR-20230901-pxtd.png b/images/getting-started/send-your-first-notification/SCR-20230901-pxtd.png
new file mode 100644
index 00000000..2a2e3ec5
Binary files /dev/null and b/images/getting-started/send-your-first-notification/SCR-20230901-pxtd.png differ
diff --git a/images/getting-started/send-your-first-notification/SCR-20230901-qaok.png b/images/getting-started/send-your-first-notification/SCR-20230901-qaok.png
new file mode 100644
index 00000000..66fd346a
Binary files /dev/null and b/images/getting-started/send-your-first-notification/SCR-20230901-qaok.png differ
diff --git a/images/getting-started/send-your-first-notification/SCR-20230901-qcqx.png b/images/getting-started/send-your-first-notification/SCR-20230901-qcqx.png
new file mode 100644
index 00000000..3f10d03f
Binary files /dev/null and b/images/getting-started/send-your-first-notification/SCR-20230901-qcqx.png differ
diff --git a/images/getting-started/send-your-first-notification/SCR-20230901-qcwk.png b/images/getting-started/send-your-first-notification/SCR-20230901-qcwk.png
new file mode 100644
index 00000000..9b414ab0
Binary files /dev/null and b/images/getting-started/send-your-first-notification/SCR-20230901-qcwk.png differ
diff --git a/images/getting-started/send-your-first-notification/SCR-20230901-qdih.png b/images/getting-started/send-your-first-notification/SCR-20230901-qdih.png
new file mode 100644
index 00000000..9260b357
Binary files /dev/null and b/images/getting-started/send-your-first-notification/SCR-20230901-qdih.png differ
diff --git a/images/getting-started/send-your-first-notification/blank-workflow1.png b/images/getting-started/send-your-first-notification/blank-workflow1.png
new file mode 100644
index 00000000..23f88da8
Binary files /dev/null and b/images/getting-started/send-your-first-notification/blank-workflow1.png differ
diff --git a/images/getting-started/send-your-first-notification/email-node1.png b/images/getting-started/send-your-first-notification/email-node1.png
new file mode 100644
index 00000000..b6c4c59d
Binary files /dev/null and b/images/getting-started/send-your-first-notification/email-node1.png differ
diff --git a/images/getting-started/send-your-first-notification/execute.png b/images/getting-started/send-your-first-notification/execute.png
new file mode 100644
index 00000000..16c8cecc
Binary files /dev/null and b/images/getting-started/send-your-first-notification/execute.png differ
diff --git a/images/getting-started/send-your-first-notification/inbox.png b/images/getting-started/send-your-first-notification/inbox.png
new file mode 100644
index 00000000..44458f2e
Binary files /dev/null and b/images/getting-started/send-your-first-notification/inbox.png differ
diff --git a/images/getting-started/send-your-first-notification/run-a-test.png b/images/getting-started/send-your-first-notification/run-a-test.png
new file mode 100644
index 00000000..e5dfce57
Binary files /dev/null and b/images/getting-started/send-your-first-notification/run-a-test.png differ
diff --git a/images/getting-started/send-your-first-notification/saving-workflow.png b/images/getting-started/send-your-first-notification/saving-workflow.png
new file mode 100644
index 00000000..63c3ea84
Binary files /dev/null and b/images/getting-started/send-your-first-notification/saving-workflow.png differ
diff --git a/images/getting-started/send-your-first-notification/triggering.gif b/images/getting-started/send-your-first-notification/triggering.gif
new file mode 100644
index 00000000..4be35334
Binary files /dev/null and b/images/getting-started/send-your-first-notification/triggering.gif differ
diff --git a/images/getting-started/send-your-first-notification/workflow-editor1.png b/images/getting-started/send-your-first-notification/workflow-editor1.png
new file mode 100644
index 00000000..b002f184
Binary files /dev/null and b/images/getting-started/send-your-first-notification/workflow-editor1.png differ
diff --git a/images/getting-started/send-your-first-notification/workflows.png b/images/getting-started/send-your-first-notification/workflows.png
new file mode 100644
index 00000000..e0041034
Binary files /dev/null and b/images/getting-started/send-your-first-notification/workflows.png differ
diff --git a/images/github-or-email.png b/images/github-or-email.png
new file mode 100644
index 00000000..5b96787b
Binary files /dev/null and b/images/github-or-email.png differ
diff --git a/images/guides/segment-novu-integration/activity-feed.gif b/images/guides/segment-novu-integration/activity-feed.gif
new file mode 100644
index 00000000..5e34bb10
Binary files /dev/null and b/images/guides/segment-novu-integration/activity-feed.gif differ
diff --git a/images/guides/segment-novu-integration/add-setting-dialog.png b/images/guides/segment-novu-integration/add-setting-dialog.png
new file mode 100644
index 00000000..dd0ca6b7
Binary files /dev/null and b/images/guides/segment-novu-integration/add-setting-dialog.png differ
diff --git a/images/guides/segment-novu-integration/autofill-events.gif b/images/guides/segment-novu-integration/autofill-events.gif
new file mode 100644
index 00000000..6d9f9f43
Binary files /dev/null and b/images/guides/segment-novu-integration/autofill-events.gif differ
diff --git a/images/guides/segment-novu-integration/custom-segment-event.gif b/images/guides/segment-novu-integration/custom-segment-event.gif
new file mode 100644
index 00000000..83f8f15a
Binary files /dev/null and b/images/guides/segment-novu-integration/custom-segment-event.gif differ
diff --git a/images/guides/segment-novu-integration/editor-test.gif b/images/guides/segment-novu-integration/editor-test.gif
new file mode 100644
index 00000000..02570430
Binary files /dev/null and b/images/guides/segment-novu-integration/editor-test.gif differ
diff --git a/images/guides/segment-novu-integration/novu-segment-settings-variables.png b/images/guides/segment-novu-integration/novu-segment-settings-variables.png
new file mode 100644
index 00000000..bcfc9cfa
Binary files /dev/null and b/images/guides/segment-novu-integration/novu-segment-settings-variables.png differ
diff --git a/images/guides/segment-novu-integration/running-segment-func-for-novu-destination.gif b/images/guides/segment-novu-integration/running-segment-func-for-novu-destination.gif
new file mode 100644
index 00000000..4abc4c10
Binary files /dev/null and b/images/guides/segment-novu-integration/running-segment-func-for-novu-destination.gif differ
diff --git a/images/guides/segment-novu-integration/settings-tab-empty.png b/images/guides/segment-novu-integration/settings-tab-empty.png
new file mode 100644
index 00000000..1c35829a
Binary files /dev/null and b/images/guides/segment-novu-integration/settings-tab-empty.png differ
diff --git a/images/guides/slack-guide/add.png b/images/guides/slack-guide/add.png
new file mode 100644
index 00000000..b1164805
Binary files /dev/null and b/images/guides/slack-guide/add.png differ
diff --git a/images/guides/slack-guide/client-id-Novu.png b/images/guides/slack-guide/client-id-Novu.png
new file mode 100644
index 00000000..37b43cf0
Binary files /dev/null and b/images/guides/slack-guide/client-id-Novu.png differ
diff --git a/images/guides/slack-guide/client-id-Slack.png b/images/guides/slack-guide/client-id-Slack.png
new file mode 100644
index 00000000..3c9fd088
Binary files /dev/null and b/images/guides/slack-guide/client-id-Slack.png differ
diff --git a/images/guides/slack-guide/create-app.png b/images/guides/slack-guide/create-app.png
new file mode 100644
index 00000000..27d212ad
Binary files /dev/null and b/images/guides/slack-guide/create-app.png differ
diff --git a/images/guides/slack-guide/create.png b/images/guides/slack-guide/create.png
new file mode 100644
index 00000000..fa60c9ef
Binary files /dev/null and b/images/guides/slack-guide/create.png differ
diff --git a/images/guides/slack-guide/drag.png b/images/guides/slack-guide/drag.png
new file mode 100644
index 00000000..d5e9d948
Binary files /dev/null and b/images/guides/slack-guide/drag.png differ
diff --git a/images/guides/slack-guide/hook.png b/images/guides/slack-guide/hook.png
new file mode 100644
index 00000000..8a2d0c30
Binary files /dev/null and b/images/guides/slack-guide/hook.png differ
diff --git a/images/guides/slack-guide/install.png b/images/guides/slack-guide/install.png
new file mode 100644
index 00000000..acfd9753
Binary files /dev/null and b/images/guides/slack-guide/install.png differ
diff --git a/images/guides/slack-guide/integration.png b/images/guides/slack-guide/integration.png
new file mode 100644
index 00000000..17d60bb1
Binary files /dev/null and b/images/guides/slack-guide/integration.png differ
diff --git a/images/guides/slack-guide/name.png b/images/guides/slack-guide/name.png
new file mode 100644
index 00000000..128e2cfa
Binary files /dev/null and b/images/guides/slack-guide/name.png differ
diff --git a/images/guides/slack-guide/on.png b/images/guides/slack-guide/on.png
new file mode 100644
index 00000000..33bd2d1d
Binary files /dev/null and b/images/guides/slack-guide/on.png differ
diff --git a/images/guides/slack-guide/output.gif b/images/guides/slack-guide/output.gif
new file mode 100644
index 00000000..76e99591
Binary files /dev/null and b/images/guides/slack-guide/output.gif differ
diff --git a/images/guides/slack-guide/redirect.png b/images/guides/slack-guide/redirect.png
new file mode 100644
index 00000000..57ee0c51
Binary files /dev/null and b/images/guides/slack-guide/redirect.png differ
diff --git a/images/guides/slack-guide/scratch.png b/images/guides/slack-guide/scratch.png
new file mode 100644
index 00000000..c79b994b
Binary files /dev/null and b/images/guides/slack-guide/scratch.png differ
diff --git a/images/guides/slack-guide/trigger-code.png b/images/guides/slack-guide/trigger-code.png
new file mode 100644
index 00000000..7006b304
Binary files /dev/null and b/images/guides/slack-guide/trigger-code.png differ
diff --git a/images/guides/slack-guide/trigger.png b/images/guides/slack-guide/trigger.png
new file mode 100644
index 00000000..5dfd558d
Binary files /dev/null and b/images/guides/slack-guide/trigger.png differ
diff --git a/images/guides/slack-guide/variable.png b/images/guides/slack-guide/variable.png
new file mode 100644
index 00000000..b6871efb
Binary files /dev/null and b/images/guides/slack-guide/variable.png differ
diff --git a/images/guides/slack-guide/welcome.png b/images/guides/slack-guide/welcome.png
new file mode 100644
index 00000000..d413a6e5
Binary files /dev/null and b/images/guides/slack-guide/welcome.png differ
diff --git a/images/guides/slack-guide/workflow.png b/images/guides/slack-guide/workflow.png
new file mode 100644
index 00000000..557ba6e6
Binary files /dev/null and b/images/guides/slack-guide/workflow.png differ
diff --git a/images/headless-notification-center-demo.png b/images/headless-notification-center-demo.png
new file mode 100644
index 00000000..17a8010c
Binary files /dev/null and b/images/headless-notification-center-demo.png differ
diff --git a/images/headless.gif b/images/headless.gif
new file mode 100644
index 00000000..491cf5e5
Binary files /dev/null and b/images/headless.gif differ
diff --git a/images/help/overview/product-architecture.jpg b/images/help/overview/product-architecture.jpg
new file mode 100644
index 00000000..c1f65403
Binary files /dev/null and b/images/help/overview/product-architecture.jpg differ
diff --git a/images/hero-dark.svg b/images/hero-dark.svg
index e274f2fe..3ae94f0d 100644
--- a/images/hero-dark.svg
+++ b/images/hero-dark.svg
@@ -18,8 +18,8 @@
-
-
+
+
@@ -112,247 +112,247 @@
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
diff --git a/images/hero-light.svg b/images/hero-light.svg
index efcc4de1..2fd6fddc 100644
--- a/images/hero-light.svg
+++ b/images/hero-light.svg
@@ -18,8 +18,8 @@
-
-
+
+
@@ -112,247 +112,247 @@
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
diff --git a/images/how-to/feedback-review.png b/images/how-to/feedback-review.png
new file mode 100644
index 00000000..a438114a
Binary files /dev/null and b/images/how-to/feedback-review.png differ
diff --git a/images/how-to/invoice-receipt.png b/images/how-to/invoice-receipt.png
new file mode 100644
index 00000000..dfc55725
Binary files /dev/null and b/images/how-to/invoice-receipt.png differ
diff --git a/images/how-to/otp-email.png b/images/how-to/otp-email.png
new file mode 100644
index 00000000..40160da7
Binary files /dev/null and b/images/how-to/otp-email.png differ
diff --git a/images/how-to/otp-push.png b/images/how-to/otp-push.png
new file mode 100644
index 00000000..10432873
Binary files /dev/null and b/images/how-to/otp-push.png differ
diff --git a/images/how-to/otp-sms.png b/images/how-to/otp-sms.png
new file mode 100644
index 00000000..f29131b3
Binary files /dev/null and b/images/how-to/otp-sms.png differ
diff --git a/images/how-to/otp.png b/images/how-to/otp.png
new file mode 100644
index 00000000..fb33c715
Binary files /dev/null and b/images/how-to/otp.png differ
diff --git a/images/how-to/password-reset.png b/images/how-to/password-reset.png
new file mode 100644
index 00000000..e67ee34d
Binary files /dev/null and b/images/how-to/password-reset.png differ
diff --git a/images/how-to/recent-login-email.png b/images/how-to/recent-login-email.png
new file mode 100644
index 00000000..3a60f6bf
Binary files /dev/null and b/images/how-to/recent-login-email.png differ
diff --git a/images/how-to/recent-login-push.png b/images/how-to/recent-login-push.png
new file mode 100644
index 00000000..5d914d64
Binary files /dev/null and b/images/how-to/recent-login-push.png differ
diff --git a/images/how-to/recent-login.png b/images/how-to/recent-login.png
new file mode 100644
index 00000000..9f722a79
Binary files /dev/null and b/images/how-to/recent-login.png differ
diff --git a/images/how-to/shipping-confirmation.png b/images/how-to/shipping-confirmation.png
new file mode 100644
index 00000000..68e4b1ab
Binary files /dev/null and b/images/how-to/shipping-confirmation.png differ
diff --git a/images/how-to/shipping-order-confirmation.png b/images/how-to/shipping-order-confirmation.png
new file mode 100644
index 00000000..fb6a39ed
Binary files /dev/null and b/images/how-to/shipping-order-confirmation.png differ
diff --git a/images/icons8-kotlin.svg b/images/icons8-kotlin.svg
new file mode 100644
index 00000000..c433779f
--- /dev/null
+++ b/images/icons8-kotlin.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/images/in-app-node.png b/images/in-app-node.png
new file mode 100644
index 00000000..74eb48fd
Binary files /dev/null and b/images/in-app-node.png differ
diff --git a/images/in-app-react.png b/images/in-app-react.png
new file mode 100644
index 00000000..d6296532
Binary files /dev/null and b/images/in-app-react.png differ
diff --git a/images/inapp-digest-engine-demo.png b/images/inapp-digest-engine-demo.png
new file mode 100644
index 00000000..999f5782
Binary files /dev/null and b/images/inapp-digest-engine-demo.png differ
diff --git a/images/inapp-editor.png b/images/inapp-editor.png
new file mode 100644
index 00000000..93a58bdc
Binary files /dev/null and b/images/inapp-editor.png differ
diff --git a/images/inbox.png b/images/inbox.png
new file mode 100644
index 00000000..be40207f
Binary files /dev/null and b/images/inbox.png differ
diff --git a/images/inbox/Elements.png b/images/inbox/Elements.png
new file mode 100644
index 00000000..dff68ab9
Binary files /dev/null and b/images/inbox/Elements.png differ
diff --git a/images/inbox/Variables Inbox.png b/images/inbox/Variables Inbox.png
new file mode 100644
index 00000000..7fa6a2b6
Binary files /dev/null and b/images/inbox/Variables Inbox.png differ
diff --git a/images/inbox/client/react/get-started/application-identifier.png b/images/inbox/client/react/get-started/application-identifier.png
new file mode 100644
index 00000000..90a8e592
Binary files /dev/null and b/images/inbox/client/react/get-started/application-identifier.png differ
diff --git a/images/inbox/client/react/get-started/avatar-in-app-1.png b/images/inbox/client/react/get-started/avatar-in-app-1.png
new file mode 100644
index 00000000..96ff3cc4
Binary files /dev/null and b/images/inbox/client/react/get-started/avatar-in-app-1.png differ
diff --git a/images/inbox/client/react/get-started/avatar-in-app-2.png b/images/inbox/client/react/get-started/avatar-in-app-2.png
new file mode 100644
index 00000000..68ad282d
Binary files /dev/null and b/images/inbox/client/react/get-started/avatar-in-app-2.png differ
diff --git a/images/inbox/client/react/get-started/hmac-encryption-enable.png b/images/inbox/client/react/get-started/hmac-encryption-enable.png
new file mode 100644
index 00000000..ce41630e
Binary files /dev/null and b/images/inbox/client/react/get-started/hmac-encryption-enable.png differ
diff --git a/images/inbox/client/react/get-started/subscriberId.png b/images/inbox/client/react/get-started/subscriberId.png
new file mode 100644
index 00000000..7646e79b
Binary files /dev/null and b/images/inbox/client/react/get-started/subscriberId.png differ
diff --git a/images/inbox/client/react/preferences/preferences.png b/images/inbox/client/react/preferences/preferences.png
new file mode 100644
index 00000000..5654036e
Binary files /dev/null and b/images/inbox/client/react/preferences/preferences.png differ
diff --git a/images/inbox/component.png b/images/inbox/component.png
new file mode 100644
index 00000000..11868abc
Binary files /dev/null and b/images/inbox/component.png differ
diff --git a/images/inbox/component_composition.png b/images/inbox/component_composition.png
new file mode 100644
index 00000000..8b88dac2
Binary files /dev/null and b/images/inbox/component_composition.png differ
diff --git a/images/inbox/framework-preferences-name.png b/images/inbox/framework-preferences-name.png
new file mode 100644
index 00000000..c7b18dcf
Binary files /dev/null and b/images/inbox/framework-preferences-name.png differ
diff --git a/images/integration-store-central.png b/images/integration-store-central.png
new file mode 100644
index 00000000..611d3c95
Binary files /dev/null and b/images/integration-store-central.png differ
diff --git a/images/integrations-store.png b/images/integrations-store.png
new file mode 100644
index 00000000..1b13fb5f
Binary files /dev/null and b/images/integrations-store.png differ
diff --git a/images/mailbox-nextjs.png b/images/mailbox-nextjs.png
new file mode 100644
index 00000000..70724b34
Binary files /dev/null and b/images/mailbox-nextjs.png differ
diff --git a/images/manage-layouts.png b/images/manage-layouts.png
new file mode 100644
index 00000000..11020a0c
Binary files /dev/null and b/images/manage-layouts.png differ
diff --git a/images/manage-subscribers/user-preference.png b/images/manage-subscribers/user-preference.png
new file mode 100644
index 00000000..31d5b808
Binary files /dev/null and b/images/manage-subscribers/user-preference.png differ
diff --git a/images/manage-subscribers/workflow-level-channel-preferences.png b/images/manage-subscribers/workflow-level-channel-preferences.png
new file mode 100644
index 00000000..fadd7e07
Binary files /dev/null and b/images/manage-subscribers/workflow-level-channel-preferences.png differ
diff --git a/images/navigating-novu-cloud.png b/images/navigating-novu-cloud.png
new file mode 100644
index 00000000..f2d2c73b
Binary files /dev/null and b/images/navigating-novu-cloud.png differ
diff --git a/images/notif-sent-nextjs.png b/images/notif-sent-nextjs.png
new file mode 100644
index 00000000..57072f53
Binary files /dev/null and b/images/notif-sent-nextjs.png differ
diff --git a/images/notification-center-demo.png b/images/notification-center-demo.png
new file mode 100644
index 00000000..e38eaf40
Binary files /dev/null and b/images/notification-center-demo.png differ
diff --git a/images/notification-center.gif b/images/notification-center.gif
new file mode 100644
index 00000000..3397e099
Binary files /dev/null and b/images/notification-center.gif differ
diff --git a/images/notification-lifecycle.png b/images/notification-lifecycle.png
new file mode 100644
index 00000000..67f39377
Binary files /dev/null and b/images/notification-lifecycle.png differ
diff --git a/images/novu-cloud-home.png b/images/novu-cloud-home.png
new file mode 100644
index 00000000..3ae23728
Binary files /dev/null and b/images/novu-cloud-home.png differ
diff --git a/images/novu-cloud.png b/images/novu-cloud.png
new file mode 100644
index 00000000..3ae23728
Binary files /dev/null and b/images/novu-cloud.png differ
diff --git a/images/novu-works.png b/images/novu-works.png
new file mode 100644
index 00000000..ec7d6c3e
Binary files /dev/null and b/images/novu-works.png differ
diff --git a/images/org.gif b/images/org.gif
new file mode 100644
index 00000000..4e7e3efa
Binary files /dev/null and b/images/org.gif differ
diff --git a/images/org.png b/images/org.png
new file mode 100644
index 00000000..4208b93f
Binary files /dev/null and b/images/org.png differ
diff --git a/images/otp/render.png b/images/otp/render.png
new file mode 100644
index 00000000..bfda0c44
Binary files /dev/null and b/images/otp/render.png differ
diff --git a/images/otp/studio.png b/images/otp/studio.png
new file mode 100644
index 00000000..325b77d1
Binary files /dev/null and b/images/otp/studio.png differ
diff --git a/images/platform/environments.png b/images/platform/environments.png
new file mode 100644
index 00000000..677f089b
Binary files /dev/null and b/images/platform/environments.png differ
diff --git a/images/postman-nextjs.png b/images/postman-nextjs.png
new file mode 100644
index 00000000..41ba5f7e
Binary files /dev/null and b/images/postman-nextjs.png differ
diff --git a/images/preferences/define-preferences.png b/images/preferences/define-preferences.png
new file mode 100644
index 00000000..2de0f514
Binary files /dev/null and b/images/preferences/define-preferences.png differ
diff --git a/images/preferences/inbox-global-preferences.png b/images/preferences/inbox-global-preferences.png
new file mode 100644
index 00000000..296bdc2b
Binary files /dev/null and b/images/preferences/inbox-global-preferences.png differ
diff --git a/images/preferences/inbox-preferences.png b/images/preferences/inbox-preferences.png
new file mode 100644
index 00000000..d2b7a9d0
Binary files /dev/null and b/images/preferences/inbox-preferences.png differ
diff --git a/images/preferences/inbox-workflow-preferences.png b/images/preferences/inbox-workflow-preferences.png
new file mode 100644
index 00000000..e2d5ab2b
Binary files /dev/null and b/images/preferences/inbox-workflow-preferences.png differ
diff --git a/images/preferences/preferences.png b/images/preferences/preferences.png
new file mode 100644
index 00000000..692630f3
Binary files /dev/null and b/images/preferences/preferences.png differ
diff --git a/images/product-team/1.png b/images/product-team/1.png
new file mode 100644
index 00000000..8a69bc0b
Binary files /dev/null and b/images/product-team/1.png differ
diff --git a/images/product-team/2.png b/images/product-team/2.png
new file mode 100644
index 00000000..e86636d0
Binary files /dev/null and b/images/product-team/2.png differ
diff --git a/images/product-team/3.png b/images/product-team/3.png
new file mode 100644
index 00000000..3b72f780
Binary files /dev/null and b/images/product-team/3.png differ
diff --git a/images/product-team/4.png b/images/product-team/4.png
new file mode 100644
index 00000000..4fd62c6a
Binary files /dev/null and b/images/product-team/4.png differ
diff --git a/images/product-team/5.png b/images/product-team/5.png
new file mode 100644
index 00000000..3f338798
Binary files /dev/null and b/images/product-team/5.png differ
diff --git a/images/product-team/7.gif b/images/product-team/7.gif
new file mode 100644
index 00000000..58386fe8
Binary files /dev/null and b/images/product-team/7.gif differ
diff --git a/images/providers-webhook-url.png b/images/providers-webhook-url.png
new file mode 100644
index 00000000..929e7cf3
Binary files /dev/null and b/images/providers-webhook-url.png differ
diff --git a/images/quickstart-react.png b/images/quickstart-react.png
new file mode 100644
index 00000000..49c3f064
Binary files /dev/null and b/images/quickstart-react.png differ
diff --git a/images/quickstarts/common/sub-nodejs.png b/images/quickstarts/common/sub-nodejs.png
new file mode 100644
index 00000000..6a699b67
Binary files /dev/null and b/images/quickstarts/common/sub-nodejs.png differ
diff --git a/images/quickstarts/iframe/1.png b/images/quickstarts/iframe/1.png
new file mode 100644
index 00000000..d6296532
Binary files /dev/null and b/images/quickstarts/iframe/1.png differ
diff --git a/images/quickstarts/iframe/10.png b/images/quickstarts/iframe/10.png
new file mode 100644
index 00000000..a5376792
Binary files /dev/null and b/images/quickstarts/iframe/10.png differ
diff --git a/images/quickstarts/iframe/11.png b/images/quickstarts/iframe/11.png
new file mode 100644
index 00000000..99b2e1cf
Binary files /dev/null and b/images/quickstarts/iframe/11.png differ
diff --git a/images/quickstarts/iframe/2.png b/images/quickstarts/iframe/2.png
new file mode 100644
index 00000000..17e362f3
Binary files /dev/null and b/images/quickstarts/iframe/2.png differ
diff --git a/images/quickstarts/iframe/3.png b/images/quickstarts/iframe/3.png
new file mode 100644
index 00000000..8661a754
Binary files /dev/null and b/images/quickstarts/iframe/3.png differ
diff --git a/images/quickstarts/iframe/4.png b/images/quickstarts/iframe/4.png
new file mode 100644
index 00000000..49c3f064
Binary files /dev/null and b/images/quickstarts/iframe/4.png differ
diff --git a/images/quickstarts/iframe/5.png b/images/quickstarts/iframe/5.png
new file mode 100644
index 00000000..2b9efa0d
Binary files /dev/null and b/images/quickstarts/iframe/5.png differ
diff --git a/images/quickstarts/iframe/6.png b/images/quickstarts/iframe/6.png
new file mode 100644
index 00000000..1a07bb46
Binary files /dev/null and b/images/quickstarts/iframe/6.png differ
diff --git a/images/quickstarts/iframe/7.png b/images/quickstarts/iframe/7.png
new file mode 100644
index 00000000..7b0f8618
Binary files /dev/null and b/images/quickstarts/iframe/7.png differ
diff --git a/images/quickstarts/iframe/8.png b/images/quickstarts/iframe/8.png
new file mode 100644
index 00000000..7ce3711b
Binary files /dev/null and b/images/quickstarts/iframe/8.png differ
diff --git a/images/quickstarts/iframe/9.png b/images/quickstarts/iframe/9.png
new file mode 100644
index 00000000..d4fa7988
Binary files /dev/null and b/images/quickstarts/iframe/9.png differ
diff --git a/images/quickstarts/iframe/test.gif b/images/quickstarts/iframe/test.gif
new file mode 100644
index 00000000..3cff7803
Binary files /dev/null and b/images/quickstarts/iframe/test.gif differ
diff --git a/images/quickstarts/vue/configuration-vue.png b/images/quickstarts/vue/configuration-vue.png
new file mode 100644
index 00000000..d2ee1f01
Binary files /dev/null and b/images/quickstarts/vue/configuration-vue.png differ
diff --git a/images/quickstarts/vue/configure-in-app-vue.png b/images/quickstarts/vue/configure-in-app-vue.png
new file mode 100644
index 00000000..0c953a8d
Binary files /dev/null and b/images/quickstarts/vue/configure-in-app-vue.png differ
diff --git a/images/quickstarts/vue/in-app-vue.png b/images/quickstarts/vue/in-app-vue.png
new file mode 100644
index 00000000..a30d5769
Binary files /dev/null and b/images/quickstarts/vue/in-app-vue.png differ
diff --git a/images/quickstarts/vue/rename-vue.png b/images/quickstarts/vue/rename-vue.png
new file mode 100644
index 00000000..5a5bd124
Binary files /dev/null and b/images/quickstarts/vue/rename-vue.png differ
diff --git a/images/react-in-app-ui.png b/images/react-in-app-ui.png
new file mode 100644
index 00000000..1b545ef2
Binary files /dev/null and b/images/react-in-app-ui.png differ
diff --git a/images/recipes/multi-workflow-digest.png b/images/recipes/multi-workflow-digest.png
new file mode 100644
index 00000000..79c7aea3
Binary files /dev/null and b/images/recipes/multi-workflow-digest.png differ
diff --git a/images/regular-editor-input.png b/images/regular-editor-input.png
new file mode 100644
index 00000000..e935982c
Binary files /dev/null and b/images/regular-editor-input.png differ
diff --git a/images/regular-editor-output.png b/images/regular-editor-output.png
new file mode 100644
index 00000000..0042f230
Binary files /dev/null and b/images/regular-editor-output.png differ
diff --git a/images/renam-workflow-kotlin.png b/images/renam-workflow-kotlin.png
new file mode 100644
index 00000000..eae32085
Binary files /dev/null and b/images/renam-workflow-kotlin.png differ
diff --git a/images/rename-nextjs.png b/images/rename-nextjs.png
new file mode 100644
index 00000000..eae32085
Binary files /dev/null and b/images/rename-nextjs.png differ
diff --git a/images/ruby.svg b/images/ruby.svg
new file mode 100644
index 00000000..c196630a
--- /dev/null
+++ b/images/ruby.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/images/select-email-kotlin.png b/images/select-email-kotlin.png
new file mode 100644
index 00000000..29908488
Binary files /dev/null and b/images/select-email-kotlin.png differ
diff --git a/images/set-variant.png b/images/set-variant.png
new file mode 100644
index 00000000..e9685268
Binary files /dev/null and b/images/set-variant.png differ
diff --git a/images/sms-editor.png b/images/sms-editor.png
new file mode 100644
index 00000000..87c48e5e
Binary files /dev/null and b/images/sms-editor.png differ
diff --git a/images/styling-docs.png b/images/styling-docs.png
new file mode 100644
index 00000000..a654c890
Binary files /dev/null and b/images/styling-docs.png differ
diff --git a/images/sub-activity.png b/images/sub-activity.png
new file mode 100644
index 00000000..4ffd65cd
Binary files /dev/null and b/images/sub-activity.png differ
diff --git a/images/sub-added-nextjs.png b/images/sub-added-nextjs.png
new file mode 100644
index 00000000..83991132
Binary files /dev/null and b/images/sub-added-nextjs.png differ
diff --git a/images/subscriber-online-filter.gif b/images/subscriber-online-filter.gif
new file mode 100644
index 00000000..5f316c14
Binary files /dev/null and b/images/subscriber-online-filter.gif differ
diff --git a/images/subscriber-seenread-filter2.gif b/images/subscriber-seenread-filter2.gif
new file mode 100644
index 00000000..b051ad9b
Binary files /dev/null and b/images/subscriber-seenread-filter2.gif differ
diff --git a/images/switch-organization.png b/images/switch-organization.png
new file mode 100644
index 00000000..a5769786
Binary files /dev/null and b/images/switch-organization.png differ
diff --git a/images/template-nodejs.png b/images/template-nodejs.png
new file mode 100644
index 00000000..de7f4b67
Binary files /dev/null and b/images/template-nodejs.png differ
diff --git a/images/test-successful-nextjs.jpg b/images/test-successful-nextjs.jpg
new file mode 100644
index 00000000..36ac34df
Binary files /dev/null and b/images/test-successful-nextjs.jpg differ
diff --git a/images/topics-postman-nextjs.png b/images/topics-postman-nextjs.png
new file mode 100644
index 00000000..fffa24a5
Binary files /dev/null and b/images/topics-postman-nextjs.png differ
diff --git a/images/translations/translations_1.png b/images/translations/translations_1.png
new file mode 100644
index 00000000..bb0be932
Binary files /dev/null and b/images/translations/translations_1.png differ
diff --git a/images/translations/translations_2.png b/images/translations/translations_2.png
new file mode 100644
index 00000000..399f439a
Binary files /dev/null and b/images/translations/translations_2.png differ
diff --git a/images/translations/translations_3.png b/images/translations/translations_3.png
new file mode 100644
index 00000000..d8157191
Binary files /dev/null and b/images/translations/translations_3.png differ
diff --git a/images/translations/translations_4.png b/images/translations/translations_4.png
new file mode 100644
index 00000000..d12213d7
Binary files /dev/null and b/images/translations/translations_4.png differ
diff --git a/images/translations/translations_5.png b/images/translations/translations_5.png
new file mode 100644
index 00000000..f0f57eca
Binary files /dev/null and b/images/translations/translations_5.png differ
diff --git a/images/translations/translations_6.png b/images/translations/translations_6.png
new file mode 100644
index 00000000..cbb92fba
Binary files /dev/null and b/images/translations/translations_6.png differ
diff --git a/images/translations/translations_7.png b/images/translations/translations_7.png
new file mode 100644
index 00000000..17daca9e
Binary files /dev/null and b/images/translations/translations_7.png differ
diff --git a/images/translations/translations_8.png b/images/translations/translations_8.png
new file mode 100644
index 00000000..3cb9227b
Binary files /dev/null and b/images/translations/translations_8.png differ
diff --git a/images/trigger-code.png b/images/trigger-code.png
new file mode 100644
index 00000000..849c1381
Binary files /dev/null and b/images/trigger-code.png differ
diff --git a/images/troubleshoot.png b/images/troubleshoot.png
new file mode 100644
index 00000000..1f18566f
Binary files /dev/null and b/images/troubleshoot.png differ
diff --git a/images/v1-v0-variant.png b/images/v1-v0-variant.png
new file mode 100644
index 00000000..0a5113b7
Binary files /dev/null and b/images/v1-v0-variant.png differ
diff --git a/images/values-in-app.png b/images/values-in-app.png
new file mode 100644
index 00000000..5866f1aa
Binary files /dev/null and b/images/values-in-app.png differ
diff --git a/images/variable-popover.png b/images/variable-popover.png
new file mode 100644
index 00000000..d8759741
Binary files /dev/null and b/images/variable-popover.png differ
diff --git a/images/vars-and-test-email-nextjs.png b/images/vars-and-test-email-nextjs.png
new file mode 100644
index 00000000..ba19dc3f
Binary files /dev/null and b/images/vars-and-test-email-nextjs.png differ
diff --git a/images/vue-email-notification.png b/images/vue-email-notification.png
new file mode 100644
index 00000000..1ee15984
Binary files /dev/null and b/images/vue-email-notification.png differ
diff --git a/images/workflow-editor.png b/images/workflow-editor.png
new file mode 100644
index 00000000..8689d241
Binary files /dev/null and b/images/workflow-editor.png differ
diff --git a/images/workflow-feed-message.png b/images/workflow-feed-message.png
new file mode 100644
index 00000000..01f37484
Binary files /dev/null and b/images/workflow-feed-message.png differ
diff --git a/images/workflow-kotlin.png b/images/workflow-kotlin.png
new file mode 100644
index 00000000..2038483c
Binary files /dev/null and b/images/workflow-kotlin.png differ
diff --git a/images/workflow-mental-model.png b/images/workflow-mental-model.png
new file mode 100644
index 00000000..b7475a81
Binary files /dev/null and b/images/workflow-mental-model.png differ
diff --git a/images/workflows/notification-workflows/create-workflow-group.gif b/images/workflows/notification-workflows/create-workflow-group.gif
new file mode 100644
index 00000000..e3828794
Binary files /dev/null and b/images/workflows/notification-workflows/create-workflow-group.gif differ
diff --git a/images/workflows/notification-workflows/search-workflow_00.png b/images/workflows/notification-workflows/search-workflow_00.png
new file mode 100644
index 00000000..d9e401c8
Binary files /dev/null and b/images/workflows/notification-workflows/search-workflow_00.png differ
diff --git a/images/workflows/notification-workflows/search-workflow_01.png b/images/workflows/notification-workflows/search-workflow_01.png
new file mode 100644
index 00000000..eaf429b5
Binary files /dev/null and b/images/workflows/notification-workflows/search-workflow_01.png differ
diff --git a/images/workflows/trigger-id.png b/images/workflows/trigger-id.png
new file mode 100644
index 00000000..aa4a1e96
Binary files /dev/null and b/images/workflows/trigger-id.png differ
diff --git a/images/workflows/workflow-id.png b/images/workflows/workflow-id.png
new file mode 100644
index 00000000..5dc88dee
Binary files /dev/null and b/images/workflows/workflow-id.png differ
diff --git a/inbox/headless/api-reference.mdx b/inbox/headless/api-reference.mdx
new file mode 100644
index 00000000..da96ac46
--- /dev/null
+++ b/inbox/headless/api-reference.mdx
@@ -0,0 +1,565 @@
+---
+title: 'Headless Inbox API Reference'
+sidebarTitle: 'API Reference'
+---
+
+import FunctionParams from '/snippets/inbox/headless/api-reference/function-params.mdx';
+import NovuParams from '/snippets/inbox/headless/api-reference/novu-params.mdx';
+
+## Novu
+
+`Novu` is the primary class used to interact with the Novu API client, enabling you to fetch and manage notifications and preferences within your custom Inbox implementation.
+
+### Constructor options
+
+
+
+### Usage
+
+
+```typescript
+import { Novu } from "@novu/js";
+
+const novu = new Novu({
+ subscriberId: "SUBSCRIBER_ID",
+ applicationIdentifier: "APPLICATION_IDENTIFIER",
+});
+```
+
+
+```typescript
+import { Novu } from "@novu/js";
+
+const novu = new Novu({
+ subscriberId: "SUBSCRIBER_ID",
+ applicationIdentifier: "APPLICATION_IDENTIFIER",
+ backendUrl: "https://eu.api.novu.co",
+ socketUrl: "https://eu.ws.novu.co",
+});
+```
+
+
+```typescript
+import { Novu } from "@novu/js";
+
+const novu = new Novu({
+ subscriberId: "SUBSCRIBER_ID",
+ applicationIdentifier: "APPLICATION_IDENTIFIER",
+ subscriberHash: "SUBSCRIBER_HASH_HMAC_ENCRYPTION",
+});
+```
+
+
+### Modules interface
+
+- [Notifications](#notifications)
+- [Preferences](#preferences)
+
+## Notifications
+
+### Methods
+- [list](#list)
+- [count](#count)
+- [read](#read)
+- [unread](#unread)
+- [archive](#archive)
+- [unarchive](#unarchive)
+- [readAll](#readAll)
+- [archiveAll](#archiveAll)
+- [archiveAllRead](#archiveAllRead)
+- [completePrimary](#completePrimary)
+- [completeSecondary](#completeSecondary)
+- [revertPrimary](#revertPrimary)
+- [revertSecondary](#revertSecondary)
+
+### Notification Interface
+```typescript
+type Subscriber = {
+ id: string;
+ firstName?: string;
+ lastName?: string;
+ avatar?: string;
+ subscriberId: string;
+};
+
+enum ChannelType {
+ IN_APP = "in_app",
+ EMAIL = "email",
+ SMS = "sms",
+ CHAT = "chat",
+ PUSH = "push"
+}
+
+type Redirect = {
+ url: string;
+ target?: '_self' | '_blank' | '_parent' | '_top' | '_unfencedTop';
+};
+
+type Action = {
+ label: string;
+ isCompleted: boolean;
+ redirect?: Redirect;
+};
+
+interface Notification = {
+ id: string;
+ subject?: string;
+ body: string;
+ to: Subscriber;
+ isRead: boolean;
+ isArchived: boolean;
+ createdAt: string;
+ readAt?: string | null;
+ archivedAt?: string | null;
+ avatar?: string;
+ primaryAction?: Action;
+ secondaryAction?: Action;
+ channelType: ChannelType;
+ tags?: string[];
+ data?: Record;
+ redirect?: Redirect;
+};
+```
+### list
+Method to fetch the list of notifications based on the provided filters.
+By default the response is cached internally and stored by provided filters as a key.
+The cache will be updated automatically with the subsequent response if the filters didn't change.
+All cached responses will be merged into a single list and returned as the result.
+You can disable this behaviour and the cache by setting the `useCache` parameter to `false` in the Novu constructor [options](/inbox/headless/api-reference#constructor-options).
+
+#### Params
+
+
+ Workflow tags can be used to filter notifications where at least one specified
+ tag matches. Read more about how can you define [workflow
+ tags](/workflow/overview#tags).
+
+
+ Filter notifications based on their read status. If not provided, all read/unread notifications will be returned.
+
+
+ Filter notifications based on their archived status. Archived notifications
+ are also read at the same time. If not provided, all archived/unarchived
+ notifications will be returned.
+
+
+ Limit the number of notifications to be fetched. It can take any number between 1 and 100. Default is 10.
+
+`after` can be used to fetch the next set of notifications after a specific notification. It can take `notification.id` as value.
+
+ Field value can be used to skip the first `n` notifications. It can be used in
+ conjunction with `limit` to paginate the notifications.
+
+
+#### Returns
+The returned object contains the data and error fields.
+
+
+ The data object contains the list of notifications and other metadata.
+
+
+ List of notifications. Read more about [Notification Interface](/inbox/headless/api-reference#notification-interface).
+
+
+ Indicates if there are more notifications available.
+
+
+ Filters used to fetch the notifications.
+
+
+
+
+ Error object if the request fails.
+
+
+
+#### Usage
+```typescript
+const notifications = await novu.notifications.list({
+ limit: 30,
+ read: false,
+ archived: false,
+ tags: ["tag1", "tag2"],
+ offset: 0,
+});
+```
+
+### count
+
+Method to fetch the count of read, unread, archived and unarchived notifications grouped by tags.
+You can also pass multiple filters to fetch the different notifications counts.
+
+#### Params
+
+
+ Workflow tags can be used to count notifications where at least one specified
+ tag matches. Read more about how can you define [workflow
+ tags](/workflow/overview#tags).
+
+
+ Count notifications based on their read status. If not provided, all
+ read/unread notifications will be counted.
+
+
+ Count notifications based on their archived status. Archived notifications are
+ also read at the same time. If not provided, all archived/unarchived
+ notifications will be counted.
+
+
+ Multiple filters can be used to count notifications. Each filter can have
+ different tags, read and archived status.
+
+
+#### Returns
+
+The returned object contains the data and error fields.
+
+
+
+
+
+ The data object contains the count of notifications and other metadata.
+
+
+ List of notifications. Read more about [Notification class]().
+
+
+ Filters used to count the notifications.
+
+
+
+
+ Error object if the request fails.
+
+
+
+
+
+
+ The data object contains the counts for notifications by specified filters.
+
+
+
+ List of notifications. Read more about [Notification class]().
+
+
+ Filters used to count the notifications.
+
+
+
+
+
+ Error object if the request fails.
+
+
+
+
+
+#### Usage
+
+```typescript
+const count = await novu.notifications.count({
+ read: true,
+ archived: false,
+ tags: ['tag1', 'tag2'],
+});
+// OR
+const multipleCounts = await novu.notifications.count([
+ {
+ read: true,
+ archived: false,
+ tags: ['tag1', 'tag2'],
+ },
+ {
+ read: false,
+ archived: false,
+ tags: ['tag3', 'tag4'],
+ },
+]);
+```
+
+### read
+
+Method marks a notification as read.
+
+
+
+#### Usage
+
+```typescript
+const readNotification = await novu.notifications.read({
+ notificationId: 'notification.id',
+});
+// OR
+const readNotification = await novu.notifications.read({
+ notification,
+});
+```
+
+### unread
+
+Method marks a notification as unread.
+
+
+
+#### Usage
+
+```typescript
+const unreadNotification = await novu.notifications.unread({
+ notificationId: 'notification.id',
+});
+// OR
+const unreadNotification = await novu.notifications.unread({
+ notification,
+});
+```
+
+### archive
+
+Method marks a notification as archived.
+
+
+
+#### Usage
+
+```typescript
+const archivedNotification = await novu.notifications.archive({
+ notificationId: 'notification.id',
+});
+// OR
+const archivedNotification = await novu.notifications.archive({
+ notification,
+});
+```
+
+### unarchive
+
+Method marks a notification as unarchived.
+
+
+
+#### Usage
+
+```typescript
+const unarchivedNotification = await novu.notifications.unarchive({
+ notificationId: 'notification.id',
+});
+// OR
+const unarchivedNotification = await novu.notifications.unarchive({
+ notification,
+});
+```
+
+### readAll
+
+Method marks all notifications as read. Notifications can be filtered by tags.
+
+#### Params
+
+
+ Workflow tags can be used to filter notifications, and organize them into
+ different groups. Read more about how can you define [workflow
+ tags](/workflow/overview#tags).
+
+
+#### Usage
+
+```typescript
+await novu.notifications.readAll({
+ tags: ['tag1', 'tag2'],
+});
+```
+
+### archiveAll
+
+Method marks all notifications as archived. Notifications can be filtered by tags.
+
+#### Params
+
+
+ Workflow tags can be used to filter notifications, and organize them into
+ different groups. Read more about how can you define [workflow
+ tags](/workflow/overview#tags).
+
+
+#### Usage
+
+```typescript
+await novu.notifications.archiveAll({
+ tags: ['tag1', 'tag2'],
+});
+```
+
+### archiveAllRead
+
+Method marks all read notifications as archived. Notifications can be filtered by tags.
+
+#### Params
+
+
+ Workflow tags can be used to filter notifications, and organize them into
+ different groups. Read more about how can you define [workflow
+ tags](/workflow/overview#tags).
+
+
+#### Usage
+
+```typescript
+await novu.notifications.archiveAllRead({
+ tags: ['tag1', 'tag2'],
+});
+```
+
+### completePrimary
+
+Method marks primary action of a notification as completed. It changes the `isCompleted` field of `primaryAction` to **done** from **pending**.
+
+#### Params
+
+
+#### Usage
+```typescript
+await novu.notifications.completePrimary({
+ notificationId: "notification.id",
+});
+```
+
+### completeSecondary
+
+Method marks secondary action of a notification as completed. It changes the `isCompleted` field of `primaryAction` to **done** from **pending**.
+
+#### Params
+
+
+#### Usage
+```typescript
+await novu.notifications.completeSecondary({
+ notificationId: "notification.id",
+});
+```
+
+### revertPrimary
+
+Method marks primary action of a notification as pending. It changes the `isCompleted` field of `primaryAction` to **pending** from **done**.
+
+#### Params
+
+
+#### Usage
+```typescript
+await novu.notifications.revertPrimary({
+ notificationId: "notification.id",
+});
+```
+
+### revertSecondary
+
+Method marks secondary action of a notification as pending. It changes the `isCompleted` field of `secondaryAction` to **pending** from **done**.
+
+#### Params
+
+
+#### Usage
+```typescript
+await novu.notifications.revertSecondary({
+ notificationId: "notification.id",
+});
+```
+
+## Preferences
+
+### Methods
+
+- [list](#list)
+
+### Preference Interface
+
+```typescript
+type Workflow = {
+ id: string;
+ identifier: string;
+ name: string;
+ critical: boolean;
+ tags?: string[];
+};
+
+type ChannelPreference = {
+ email?: boolean;
+ sms?: boolean;
+ in_app?: boolean;
+ chat?: boolean;
+ push?: boolean;
+};
+
+enum PreferenceLevel {
+ GLOBAL = 'global',
+ TEMPLATE = 'template',
+}
+
+interface Preference {
+ enabled: boolean;
+ workflow?: Workflow;
+ channels: ChannelPreference;
+ level: PreferenceLevel;
+}
+```
+
+### list
+
+Method fetch the global and workflows channel preferences of the subscriber.
+
+#### Usage
+
+```typescript
+await novu.preferences.list();
+```
+
+## Preference
+
+### Methods
+
+- [update](#update)
+
+### update
+
+Update the global or per workflow channel preferences of the subscriber.
+
+If `workflowId` is provided, it updates the channel preferences of the specific workflow. Otherwise, it updates the global channel preferences.
+
+#### Params
+
+
+{' '}
+
+```typescript ChannelPreference
+type ChannelPreference = {
+ email?: boolean;
+ sms?: boolean;
+ in_app?: boolean;
+ chat?: boolean;
+ push?: boolean;
+};
+```
+
+#### Usage
+
+```typescript
+await preference.update({
+ workflowId: 'workflow_id',
+ channelPreferences: { email: false, sms: false },
+});
+```
+
+## Events
+
+### Usage
+
+```typescript
+novu.on('notifications.notification_received', (data) => {
+ console.log('new notification =>', data);
+});
+
+novu.on('notifications.unread_count_changed', (data) => {
+ console.log('new unread notifications count =>', data);
+});
+```
diff --git a/inbox/headless/get-started.mdx b/inbox/headless/get-started.mdx
new file mode 100644
index 00000000..9ce550a1
--- /dev/null
+++ b/inbox/headless/get-started.mdx
@@ -0,0 +1,84 @@
+---
+title: 'Headless Inbox'
+sidebarTitle: 'Get Started'
+description: 'A lightweight, standalone package for building custom in-app notification interfaces with Novu, providing essential functionalities and flexibility.'
+---
+
+The headless version of Novu’s notification library package provides users with a lightweight solution for integrating notification functionality into their web applications. With just the essential API methods, users can easily incorporate our notification system into any framework or vanilla JavaScript project, without being constrained by our default UI or dependencies. The SDK includes real-time notifications through a WebSocket connection and can be safely used across web browsers.
+
+## Get Started
+
+
+
+```bash
+npm install @novu/js
+```
+
+
+```typescript
+import { Novu } from "@novu/js";
+```
+
+
+
+
+```typescript
+import { Novu } from "@novu/js";
+
+const novu = new Novu({
+ subscriberId: "SUBSCRIBER_ID",
+ applicationIdentifier: "APPLICATION_IDENTIFIER",
+});
+```
+
+
+```typescript
+import { Novu } from "@novu/js";
+
+const novu = new Novu({
+ subscriberId: "SUBSCRIBER_ID",
+ applicationIdentifier: "APPLICATION_IDENTIFIER",
+ backendUrl: "https://eu.api.novu.co",
+ socketUrl: "https://eu.ws.novu.co",
+});
+```
+
+
+Read more about [HMAC Encryption](/inbox/react/production#hmac-encryption).
+```typescript
+import { Novu } from "@novu/js";
+
+const novu = new Novu({
+ subscriberId: "SUBSCRIBER_ID",
+ applicationIdentifier: "APPLICATION_IDENTIFIER",
+ subscriberHash: "SUBSCRIBER_HASH_HMAC_SUBSCRIBER_HASH_HMAC_ENCRYPTION",
+});
+```
+
+
+
+
+```typescript
+const response = await novu.notifications.list({
+ limit: 30,
+});
+
+const notifications = response.data.notifications
+```
+
+
+Display `notifications` in your UI.
+
+
+
+## Realtime Notifications
+Events are emitted when notifications are received, and when the unread notificatons count changes. `novu.on()` is used to listen to these events.
+```typescript
+novu.on("notifications.notification_received", (data) => {
+ console.log("new notification =>", data);
+});
+
+novu.on("notifications.unread_count_changed", (data) => {
+ console.log("new unread notifications count =>", data);
+});
+```
\ No newline at end of file
diff --git a/inbox/overview.mdx b/inbox/overview.mdx
new file mode 100644
index 00000000..11b19ad3
--- /dev/null
+++ b/inbox/overview.mdx
@@ -0,0 +1,42 @@
+---
+title: 'Inbox Notification Center'
+sidebarTitle: 'Inbox Overview'
+description: "Learn how to integrate Novu's pre-built notification center component to add real-time in-app notifications to your application."
+---
+
+The Inbox component enables a rich context-aware in-app notifications center directly in your application, and with minimal effort.
+
+Novu provides a pre-built, ready-to-use, and customizable Inbox UI component. It's in React today, and soon will be available in other popular frameworks including Vue, React Native, and full headless.
+
+
+
+
+
+## Why use the Novu Inbox?
+
+- You want to notify users directly within your application.
+- Pre-built UI components make the Inbox easy to include.
+- Highly customizable and can be made to exactly match your existing branding and design.
+- Users can access powerful subscriber preferences management direct from the Inbox.
+- Provide a multi-tab notification experience.
+- Synchronize notifications across devices in real-time, powered by web sockets.
+- Secure your connection using HMAC encryption.
+
+## Concepts
+
+1. `Subscriber` - is your product user who will receive the notifications through the In-App channel.
+2. `Notification` - is a message sent to the subscriber using an In-App channel (step) in the workflow.
+3. `Notifications list` - the list of all notifications sent to a particular subscriber.
+4. `Tabs` - allows to switch between notifications lists where the notifications can be grouped by particular workflow tags.
+5. `Notification actions` - are the buttons below notification content that can be clicked to perform any action or external API call.
+6. `Redirect URL` - when a single notification item from the list is clicked then it can be configured to perform an action or redirect to any URL.
+
+## Getting started
+
+- Pre built UI component in [React](/inbox/react/get-started).
+- Learn how to use [headless library](/inbox/headless/get-started) to build custom UI for other frameworks like Vue, Angular etc.
+- [React Native](/inbox/react-native/quickstart) - Add a notification feed to your React Native app.
+
+## Design files
+
+To aide your design process, we provide a free [Figma file](https://www.figma.com/community/file/1425407348374863860) that contains all of the design assets. Make a copy of the file into your own account to get started with customizing your graphical Inbox elements.
\ No newline at end of file
diff --git a/inbox/react-native/hooks/novu-provider.mdx b/inbox/react-native/hooks/novu-provider.mdx
new file mode 100644
index 00000000..fffe2d62
--- /dev/null
+++ b/inbox/react-native/hooks/novu-provider.mdx
@@ -0,0 +1,17 @@
+---
+title: 'NovuProvider'
+---
+
+import NovuProviderExample from '/snippets/inbox/react/novu-provider-example.mdx';
+import NovuParams from '/snippets/inbox/headless/api-reference/novu-params.mdx';
+
+The `NovuProvider` is the top-level component that provides the [Novu instance](/inbox/headless/api-reference#novu) to the rest of the hooks through the context.
+Usually, it's placed somewhere in the root of your application, which makes the hooks accessible throughout the application.
+
+## `NovuProvider` props
+
+
+
+## Example usage
+
+
diff --git a/inbox/react-native/hooks/use-counts.mdx b/inbox/react-native/hooks/use-counts.mdx
new file mode 100644
index 00000000..96e36906
--- /dev/null
+++ b/inbox/react-native/hooks/use-counts.mdx
@@ -0,0 +1,14 @@
+---
+title: 'useCounts'
+---
+
+import UseUnreadCountExample from '/snippets/inbox/react/use-unread-count-example.mdx';
+import UseCountsContent from '/snippets/hooks/use-counts-content.mdx';
+
+
+
+## Example usage
+
+The below example demonstrates how to use the `useCounts` hook to fetch the unread notifications count.
+
+
diff --git a/inbox/react-native/hooks/use-notifications.mdx b/inbox/react-native/hooks/use-notifications.mdx
new file mode 100644
index 00000000..d4037144
--- /dev/null
+++ b/inbox/react-native/hooks/use-notifications.mdx
@@ -0,0 +1,12 @@
+---
+title: 'useNotifications'
+---
+
+import UseNotificationsExample from '/snippets/inbox/react/use-notifications-example.mdx';
+import UseNotificationsContent from '/snippets/hooks/use-notifications-content.mdx';
+
+
+
+## Example usage
+
+
diff --git a/inbox/react-native/hooks/use-novu.mdx b/inbox/react-native/hooks/use-novu.mdx
new file mode 100644
index 00000000..f67374e2
--- /dev/null
+++ b/inbox/react-native/hooks/use-novu.mdx
@@ -0,0 +1,30 @@
+---
+title: 'useNovu Hook for React Native'
+sidebarTitle: 'useNovu'
+---
+
+import UseNovuContent from '/snippets/hooks/use-novu-content.mdx';
+
+
+
+```tsx
+import { useNovu } from '@novu/react-native';
+
+const NotificationToast = () => {
+ const novu = useNovu();
+
+ useEffect(() => {
+ const listener = ({ result: notification }) => {
+ // Show a toast notification
+ };
+
+ novu.on('notifications.notification_received', listener);
+
+ return () => {
+ novu.off('notifications.notification_received', listener);
+ };
+ }, [novu]);
+
+ return null;
+};
+```
\ No newline at end of file
diff --git a/inbox/react-native/hooks/use-preferences.mdx b/inbox/react-native/hooks/use-preferences.mdx
new file mode 100644
index 00000000..b5f737f7
--- /dev/null
+++ b/inbox/react-native/hooks/use-preferences.mdx
@@ -0,0 +1,26 @@
+---
+title: 'usePreferences Hook for React Native'
+sidebarTitle: 'usePreferences'
+---
+
+import UsePreferencesContent from '/snippets/hooks/use-preferences-content.mdx';
+
+
+
+## Example usage
+
+```tsx
+import { usePreferences } from '@novu/react-native';
+
+const PreferencesList = () => {
+ const { preferences, isLoading, error, refetch } = usePreferences();
+
+ return (
+ }>
+ {preferences?.map((preference) => {
+ return ;
+ })}
+
+ );
+};
+```
diff --git a/inbox/react-native/quickstart.mdx b/inbox/react-native/quickstart.mdx
new file mode 100644
index 00000000..3a9c64ee
--- /dev/null
+++ b/inbox/react-native/quickstart.mdx
@@ -0,0 +1,120 @@
+---
+title: 'React Native Get Started'
+sidebarTitle: 'Quickstart'
+description: 'Learn how to add Novu powered Inbox to your React Native app'
+---
+
+You can build your own Inbox UI with Novu's React Native hooks SDK. Novu will take care of data fetching, real-time updates and will help you build a fully custom notification-center UI.
+
+
+View our [React Native Expo Template](https://github.com/novuhq/novu-expo) for a live example of the inbox in action.
+
+
+
+
+ ```bash
+ npm install @novu/react-native
+ ```
+
+
+ The `NovuProvider` component is used to provide the Novu context to the inbox hooks. Make sure that the provider is wrapping your Inbox hook usages at the component tree.
+
+```tsx React Native
+import { NovuProvider } from '@novu/react-native';
+
+function Layout() {
+ return (
+
+ {/* Your app components where you want to use the hooks */}
+
+
+ );
+}
+```
+
+
+
+ You can find the `applicationIdentifier` in the Novu Dashboard under the [API keys page](https://dashboard-v2.novu.co/api-keys).
+ The `subscriberId` is the unique identifier of the user in your application, learn more about subscribers [here](/concepts/subscribers).
+
+
+
+
+
+```tsx React Native
+import {
+ FlatList,
+ View,
+ Text,
+ ActivityIndicator,
+ RefreshControl,
+} from "react-native";
+import { useNotifications, Notification } from "@novu/react-native";
+
+function YourCustomInbox() {
+ const { notifications, isLoading, fetchMore, hasMore, refetch } = useNotifications();
+
+ const renderItem = ({ item }) => (
+
+ {item.body}
+
+ );
+
+ const renderFooter = () => {
+ if (!hasMore) return null;
+
+ return (
+
+
+
+ );
+ };
+
+ const renderEmpty = () => (
+
+ No updates available
+
+ );
+
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+ item.id}
+ contentContainerStyle={styles.listContainer}
+ onEndReached={fetchMore}
+ onEndReachedThreshold={0.5}
+ ListFooterComponent={renderFooter}
+ ListEmptyComponent={renderEmpty}
+ refreshControl={
+
+ }
+ />
+ );
+}
+```
+
+
+
+
+ Now that you have the inbox component added to your application, you can trigger an Inbox notification to see it in action.
+ To create your first workflow, follow our [quickstart guide](/getting-started/quickstart).
+
+
+
+
diff --git a/inbox/react/components/bell.mdx b/inbox/react/components/bell.mdx
new file mode 100644
index 00000000..bdd43599
--- /dev/null
+++ b/inbox/react/components/bell.mdx
@@ -0,0 +1,90 @@
+---
+title: ''
+---
+
+The `Bell` component displays the notification bell icon. It can also be used to show the number of unread notifications.
+
+```tsx
+import { Inbox, Bell } from '@novu/react';
+
+function Novu() {
+ return (
+
+
+
+ );
+}
+```
+
+### Custom Bell
+
+You can pass any custom components as children to `` and render them as custom bell icon.
+
+```tsx
+import { Inbox, Bell } from '@novu/react';
+import { BellIcon } from './icons';
+
+function Novu() {
+ return (
+
+ (
+
+ {unreadCount}
+
+
+ )}
+ />
+
+ );
+}
+```
+
+### Bring your own Popover
+
+`` can be mounted in your own popover component. For further
+customization, you can also use the `renderBell` and `renderNotification` render
+props. Below is an example of how to use `` with [Radix
+UI](https://www.radix-ui.com/).
+
+```tsx
+import React from 'react';
+import * as Popover from '@radix-ui/react-popover';
+import { BellIcon, Cross2Icon } from '@radix-ui/react-icons';
+import { Inbox, Bell, Notifications } from '@novu/react';
+import './styles.css';
+
+const PopoverDemo = () => (
+
+
+
+ (
+
+ {unreadCount}
+
+
+ )}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default PopoverDemo;
+```
diff --git a/inbox/react/components/inbox-content.mdx b/inbox/react/components/inbox-content.mdx
new file mode 100644
index 00000000..a0ea77b2
--- /dev/null
+++ b/inbox/react/components/inbox-content.mdx
@@ -0,0 +1,72 @@
+---
+title: ""
+---
+
+By default, `` renders the content of the `` popover. This component is meant to be used with a custom popover.
+
+```tsx
+import { Bell, Inbox, InboxContent } from '@novu/react';
+import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
+
+export default function CustomPopoverPage() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+}
+```
+
+## Behaviour
+
+The `` component can receive all the props that `` does, besides the configuration and bell specific ones.
+
+```tsx
+import { Bell, Inbox, InboxContent } from '@novu/react';
+import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
+
+export default function CustomPopoverPage() {
+ return (
+
+
+
+
+
+
+ {
+ // your logic to handle notification click
+ }}
+ onPrimaryActionClick={(notification) => {
+ // your logic to handle primary action click
+ }}
+ onSecondaryActionClick={(notification) => {
+ // your logic to handle secondary action click
+ }}
+ renderNotification={(notification) => (
+
+
{notification.subject}
+
{notification.body}
+
{notification.data.text}
+
+ )}
+ />
+
+
+
+ );
+}
+```
diff --git a/inbox/react/components/inbox.mdx b/inbox/react/components/inbox.mdx
new file mode 100644
index 00000000..38624781
--- /dev/null
+++ b/inbox/react/components/inbox.mdx
@@ -0,0 +1,245 @@
+---
+title: ''
+---
+
+By default, the `` renders a bell button, that opens a popover on click. The popover contains the notifications list and the user preferences.
+
+```tsx
+import { Inbox } from '@novu/react';
+
+function Novu() {
+ return (
+
+ );
+}
+```
+
+## Navigation
+
+The Inbox component uses the `routerPush` prop to make your notifications navigatable. We will automatically pass the `redirect.url` from your workflow definitions to the `routerPush` prop.
+
+To make the navigation work, you will need to specify the `routerPush` behaviour depending on your routing library.
+
+
+ ```tsx Next.js
+ import { Inbox } from '@novu/react';
+ import { useRouter } from 'next/navigation'
+
+ function Novu() {
+ const router = useRouter();
+
+ return (
+ router.push(path)}
+ />
+ );
+ }
+ ```
+
+ ```tsx Remix
+ import { Inbox } from '@novu/react';
+ import { useNavigate } from '@remix-run/react';
+
+ function Novu() {
+ const navigate = useNavigate();
+
+ return (
+ navigate(path)}
+ />
+ );
+ }
+ ```
+
+ ```tsx React Router
+ import { Inbox } from '@novu/react';
+ import { useNavigate } from 'react-router-dom';
+
+ function Novu() {
+ const navigate = useNavigate();
+
+ return (
+ navigate(path)}
+ />
+ );
+ }
+ ```
+
+ ```tsx Gatsby
+ import { Inbox } from '@novu/react';
+ import { navigate } from 'gatsby';
+
+ function Novu() {
+ return (
+ navigate(path)}
+ />
+ );
+ }
+ ```
+
+ ```tsx Create React App
+ import { Inbox } from '@novu/react';
+ import { useNavigate } from 'react-router-dom';
+
+ function Novu() {
+ const navigate = useNavigate();
+
+ return (
+ navigate(path)}
+ />
+ );
+ }
+ ```
+
+
+
+### Handle notification click
+
+In some cases, you might want to handle the notification click event without navigating. You can do so by passing a callback function to the `onNotificationClick` prop.
+
+This can be useful if you want to open a modal or a drawer instead of navigating to a page.
+
+```tsx
+import { Inbox } from '@novu/react';
+
+function Novu() {
+ return (
+ {
+ // your logic to handle notification click
+ }}
+ />
+ );
+}
+```
+
+### Handler action clicks
+
+For scenarios where you want to handle the action button click event without navigating, you can pass a callback function to the `onPrimaryActionClick` and `onSecondaryActionClick` props.
+
+In the callback function, an http request can be made or any other logic can be implemented. For common usecases such as accepting a friend request, a project invitation, etc...
+
+```tsx
+import { Inbox } from '@novu/react';
+
+function Novu() {
+ return (
+ {
+ // your logic to handle primary action click
+ }}
+ onSecondaryActionClick={(notification) => {
+ // your logic to handle secondary action click
+ }}
+ />
+ );
+}
+```
+
+## Controlled Popover state
+
+```tsx
+import { Inbox } from '@novu/react';
+
+function Novu() {
+ const [open, setOpen] = React.useState(false);
+
+ return (
+ <>
+
+
+ >
+ );
+}
+```
+
+## Custom Notification item
+
+Customize the notification item by passing a render function to the `renderNotification` prop.
+You can access the notification object and render the notification item as per your requirements.
+The `notification.data` property allows you acessing the custom information while rendering notification item.
+You can check how to pass it with the Novu Framework in-app step output [here](/framework/typescript/steps/inApp).
+
+```tsx
+import { Inbox } from '@novu/react';
+
+function Novu() {
+ return (
+ (
+
+ )}
+ />
+ );
+}
+```
+
+## Filter visible preferences
+
+You can customize the visible preferences by passing the `preferencesFilter` prop to the Inbox component, allowing you to display only relevant preferences to your users.
+The filtering works by matching the workflow [tags](/workflow/overview#tags) field with the specified filter `tags` value, showing workflows that contain at least one associated tag.
+Additionally, you can combine tags from different workflows to create a tailored preferences view.
+
+```tsx
+import { Inbox, Preferences } from '@novu/react';
+
+function Novu() {
+ return (
+
+ );
+}
+```
diff --git a/inbox/react/components/notifications.mdx b/inbox/react/components/notifications.mdx
new file mode 100644
index 00000000..b2642e50
--- /dev/null
+++ b/inbox/react/components/notifications.mdx
@@ -0,0 +1,47 @@
+---
+title: ''
+---
+
+The `Notifications` component is used to display the list of notifications.
+
+### Notifications as a list without the Bell and Popover
+
+```tsx
+import { Inbox, Notifications } from '@novu/react';
+
+function Novu() {
+ return (
+
+
+
+ );
+}
+```
+
+### Notifications as a list with custom Notification item
+
+```tsx
+import { Inbox, Notifications } from '@novu/react';
+
+function Novu() {
+ return (
+
+ (
+
+
{notification.subject}
+
{notification.body}
+
{notification.data.text}
+
+ )}
+ />
+
+ );
+}
+```
diff --git a/inbox/react/components/overview.mdx b/inbox/react/components/overview.mdx
new file mode 100644
index 00000000..465b548e
--- /dev/null
+++ b/inbox/react/components/overview.mdx
@@ -0,0 +1,126 @@
+---
+title: 'Overview'
+---
+
+## Composition
+
+The Inbox is composed of the following sub-components that you can use to build your Inbox structure and layout:
+
+- [\](/inbox/react/components/inbox) - The Inbox wrapper, which is used to wrap the entire inbox UI and establish the session.
+- [\](/inbox/react/components/bell) - Used to display the bell icon and trigger the popover component when clicked.
+- [\](/inbox/react/components/notifications) - Displays the notifications list.
+- [\](/inbox/react/components/preferences) - Used to display the preferences modal.
+- [\](/inbox/react/components/inbox-content) - Usage with a custom popover.
+
+The internal UI and styles for those components can be modified using the [`appearance`](/inbox/react/styling#appearance-prop) prop.
+
+
+
+
+
+## Layouts
+
+The composition of the indivudal components can generate multiple different popular inbox layouts.
+
+### Inbox with Bell (Default)
+
+A trigger button usually located at the top right corner of the screen, which triggers the popover component when clicked.
+
+
+
+
+```tsx Defualt Bell
+import { Inbox } from '@novu/react';
+
+function Novu() {
+ return (
+
+ );
+}
+```
+
+```tsx Custom Bell
+import { Inbox, Bell } from '@novu/react';
+import { BellIcon } from './icons';
+
+function Novu() {
+ return (
+
+ (
+
+ {unreadCount}
+
+
+ )}
+ />
+
+ );
+}
+```
+
+
+### Side menu Inbox
+
+To render a side menu inbox, use the `` component as a direct child of the `` component, you can hide or show it based on any custom logic.
+
+```tsx
+import { Inbox, Notifications } from '@novu/react';
+
+function Novu() {
+ return (
+
+ {showSideMenu && (
+
+ )}
+
+ );
+}
+```
+
+### Full page Inbox
+
+Similiary to the side menu inbox, you can use the `` component as a direct child of the `` component, you can hide or show it based on any custom logic.
+
+```tsx
+import { Inbox, Notifications } from '@novu/react';
+
+function Novu() {
+ return (
+
+
+
+ );
+}
+```
+
+### Inbox with a custom popover
+
+```tsx
+import { Bell, Inbox, InboxContent } from '@novu/react';
+import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
+
+export default function CustomPopoverPage() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+}
+```
diff --git a/inbox/react/components/preferences.mdx b/inbox/react/components/preferences.mdx
new file mode 100644
index 00000000..c4e26a14
--- /dev/null
+++ b/inbox/react/components/preferences.mdx
@@ -0,0 +1,45 @@
+---
+title: 'React Preferences Component'
+description: 'Learn how to use and customize the Preferences component in React to manage notification settings.'
+---
+
+By default, Novu will show the subscriber preferences cog icon on the Inbox component.
+
+Users can enable/disable any active channel in the workflow using subscriber preferences or they can update the preference globally for all workflows under the `Global Preferences`.
+
+
+
+
+### Preferences Component
+
+The `Preferences` component is used to display the subscriber preferences, use it when you want to render preference in another part of your application or a custom layout for the Inbox.
+
+```tsx
+import { Inbox, Preferences } from '@novu/react';
+
+function Novu() {
+ return (
+
+
+
+ );
+}
+```
+
+### Filtering preferences
+
+You can filter the preferences visible by the user by specifying the `preferenceFilter` on the `Inbox` component, learn more about it [here](/inbox/react/components/inbox#filter-visible-preferences)
+
+### Hide global preferences
+Global subscriber preferences are shown by default, it can hidden by using below code in global css file
+```css
+.nv-workflowContainer:first-child {
+ display: none;
+}
+```
\ No newline at end of file
diff --git a/inbox/react/get-started.mdx b/inbox/react/get-started.mdx
new file mode 100644
index 00000000..61d5bb54
--- /dev/null
+++ b/inbox/react/get-started.mdx
@@ -0,0 +1,161 @@
+---
+title: 'React Get Started'
+sidebarTitle: 'Quickstart'
+description: 'Learn how to add a Novu-Powered In-App Inbox to your React app'
+---
+
+Novu provides the `@novu/react` package that helps to add a fully functioning Inbox to your web application in minutes. Let's see how easily you can use it in your application.
+
+
+
+```bash
+npm install @novu/react
+```
+
+
+
+```tsx Next.js
+'use client';
+
+import { Inbox } from '@novu/react';
+import { useRouter } from 'next/navigation';
+
+function Novu() {
+ const router = useRouter();
+
+ return (
+ router.push(path)}
+ />
+ );
+}
+```
+```tsx Remix
+import { Inbox } from '@novu/react';
+import { useNavigate } from '@remix-run/react';
+
+function Novu() {
+ const navigate = useNavigate();
+
+ return (
+ navigate(path)}
+ />
+ );
+}
+```
+```tsx React Router
+import { Inbox } from '@novu/react';
+import { useNavigate } from 'react-router-dom';
+
+function Novu() {
+ const navigate = useNavigate();
+
+ return (
+ navigate(path)}
+ />
+ );
+}
+```
+```tsx Gatsby
+import { Inbox } from '@novu/react';
+import { navigate } from 'gatsby';
+
+function Novu() {
+ return (
+ navigate(path)}
+ />
+ );
+}
+```
+```tsx Create React App
+import { Inbox } from '@novu/react';
+import { useNavigate } from 'react-router-dom';
+
+function Novu() {
+ const navigate = useNavigate();
+
+ return (
+ navigate(path)}
+ />
+ );
+}
+```
+
+
+
+ You can find the `applicationIdentifier` in the Novu Dashboard under the [API keys page](https://dashboard-v2.novu.co/api-keys).
+ The `subscriberId` is the unique identifier of the user in your application, learn more about subscribers [here](/concepts/subscribers).
+
+
+ Novu uses the `routerPush` prop to make your notifications navigatable. We will automatically pass the `redirect.url` from your workflow definitions to the `routerPush` prop.
+
+
+ If you are using the European region in novu, you need to set the `backendUrl` and `socketUrl` props.
+ ```tsx
+
+ ```
+
+
+
+ Now that you have the inbox component added to your application, you can trigger an Inbox notification to see it in action.
+ To create your first workflow, follow our [quickstart guide](/getting-started/quickstart).
+
+
+
+
+
+
+
+## Next Steps
+
+
+
+ Learn how to customize the inbox with your own branding and design.
+
+
+ Learn more about the composable components of the inbox.
+
+
+
+## Frequently Asked Questions
+
+
+
+
+Possible reasons for the Inbox not loading properly:
+
+ - Invalid subscriberId
+ - Invalid applicationIdentifier
+ - Invalid subscriberHash (in case of active HMAC encryption)
+ - Invalid backendUrl (in case of self-hosted)
+ - Invalid socketUrl (in case of self-hosted)
+
+
+
+
+ Our Inbox is configures for all hosts. Please disable root level `withCredentials` config in axios or fetch http requests.
+
+
+
diff --git a/inbox/react/hooks/novu-provider.mdx b/inbox/react/hooks/novu-provider.mdx
new file mode 100644
index 00000000..fffe2d62
--- /dev/null
+++ b/inbox/react/hooks/novu-provider.mdx
@@ -0,0 +1,17 @@
+---
+title: 'NovuProvider'
+---
+
+import NovuProviderExample from '/snippets/inbox/react/novu-provider-example.mdx';
+import NovuParams from '/snippets/inbox/headless/api-reference/novu-params.mdx';
+
+The `NovuProvider` is the top-level component that provides the [Novu instance](/inbox/headless/api-reference#novu) to the rest of the hooks through the context.
+Usually, it's placed somewhere in the root of your application, which makes the hooks accessible throughout the application.
+
+## `NovuProvider` props
+
+
+
+## Example usage
+
+
diff --git a/inbox/react/hooks/overview.mdx b/inbox/react/hooks/overview.mdx
new file mode 100644
index 00000000..90372a4e
--- /dev/null
+++ b/inbox/react/hooks/overview.mdx
@@ -0,0 +1,80 @@
+---
+title: 'Create Custom Inbox UI with React Hooks'
+sidebarTitle: 'Overview'
+---
+
+import UseNotificationsExample from '/snippets/inbox/react/use-notifications-example.mdx';
+import UseUnreadCountExample from '/snippets/inbox/react/use-unread-count-example.mdx';
+import NovuProviderExample from '/snippets/inbox/react/novu-provider-example.mdx';
+
+The `@novu/react` package offers an interface that enables you to build a custom notifications UI using React hooks that are powered by real-time data from the Novu services.
+
+These hooks are designed for use in both mobile and web applications, offering a flexible approach to building a custom notifications UI tailored to your application's requirements.
+
+### Getting Started
+
+Follow these steps to get started with building your custom inbox UI:
+
+
+
+ ```bash
+ npm install @novu/react
+ ```
+
+
+ To implement the [NovuProvider](/inbox/react/hooks/novu-provider) component, you need to place it in your application's code at the tree level where you want the hooks to be accessible. Here's how you can do it:
+
+
+ You can find the `applicationIdentifier` in the Novu Dashboard under the [API
+ keys page](https://dashboard-v2.novu.co/api-keys). The `subscriberId` is the
+ unique identifier of the user in your application, learn more about
+ subscribers [here](/concepts/subscribers).
+
+
+
+
+ For example, you can create a custom popover UI with a bell icon that shows the unread notifications count and a list of notifications.
+ ```tsx
+ const YourCustomInbox = () => {
+ return (
+
+
+
+
+
+
+
+
+ );
+ };
+ ```
+
+ The `BellButton` component fetches the unread notifications count and renders the count value in the indicator.
+
+
+
+ The `NotificationsList` component retrieves and displays the notifications list with infinite scrolling functionality.
+
+
+
+ The `NotificationItem` component renders each notification item.
+
+ When any action is performed on the `notification` instance for example "read" button is clicked,
+ the SDK will optimistically update notification that will result in the `useNotifications` hook rerender, so yo don't need to refetch and manually update your UI.
+
+ ```tsx
+ const NotificationItem = ({ notification }) => {
+ return (
+
+ {notification.isRead && }
+
{notification.subject}
+
{notification.body}
+
+
+
+ );
+ };
+ ```
+
+
+
diff --git a/inbox/react/hooks/use-counts.mdx b/inbox/react/hooks/use-counts.mdx
new file mode 100644
index 00000000..96e36906
--- /dev/null
+++ b/inbox/react/hooks/use-counts.mdx
@@ -0,0 +1,14 @@
+---
+title: 'useCounts'
+---
+
+import UseUnreadCountExample from '/snippets/inbox/react/use-unread-count-example.mdx';
+import UseCountsContent from '/snippets/hooks/use-counts-content.mdx';
+
+
+
+## Example usage
+
+The below example demonstrates how to use the `useCounts` hook to fetch the unread notifications count.
+
+
diff --git a/inbox/react/hooks/use-notifications.mdx b/inbox/react/hooks/use-notifications.mdx
new file mode 100644
index 00000000..d4037144
--- /dev/null
+++ b/inbox/react/hooks/use-notifications.mdx
@@ -0,0 +1,12 @@
+---
+title: 'useNotifications'
+---
+
+import UseNotificationsExample from '/snippets/inbox/react/use-notifications-example.mdx';
+import UseNotificationsContent from '/snippets/hooks/use-notifications-content.mdx';
+
+
+
+## Example usage
+
+
diff --git a/inbox/react/hooks/use-novu.mdx b/inbox/react/hooks/use-novu.mdx
new file mode 100644
index 00000000..7ca54d41
--- /dev/null
+++ b/inbox/react/hooks/use-novu.mdx
@@ -0,0 +1,30 @@
+---
+title: 'useNovu Hook for React'
+sidebarTitle: 'useNovu'
+---
+
+import UseNovuContent from '/snippets/hooks/use-novu-content.mdx';
+
+
+
+```tsx
+import { useNovu } from '@novu/react/hooks';
+
+const NotificationToast = () => {
+ const novu = useNovu();
+
+ useEffect(() => {
+ const listener = ({ result: notification }) => {
+ // Show a toast notification
+ };
+
+ novu.on('notifications.notification_received', listener);
+
+ return () => {
+ novu.off('notifications.notification_received', listener);
+ };
+ }, [novu]);
+
+ return null;
+};
+```
\ No newline at end of file
diff --git a/inbox/react/hooks/use-preferences.mdx b/inbox/react/hooks/use-preferences.mdx
new file mode 100644
index 00000000..7020661e
--- /dev/null
+++ b/inbox/react/hooks/use-preferences.mdx
@@ -0,0 +1,26 @@
+---
+title: 'usePreferences Hook for React'
+sidebarTitle: 'usePreferences'
+---
+
+import UsePreferencesContent from '/snippets/hooks/use-preferences-content.mdx';
+
+
+
+## Example usage
+
+```tsx
+import { usePreferences } from '@novu/react/hooks';
+
+const PreferencesList = () => {
+ const { preferences, isLoading, error, refetch } = usePreferences();
+
+ return (
+ }>
+ {preferences?.map((preference) => {
+ return ;
+ })}
+
+ );
+};
+```
diff --git a/inbox/react/localization.mdx b/inbox/react/localization.mdx
new file mode 100644
index 00000000..64fa5905
--- /dev/null
+++ b/inbox/react/localization.mdx
@@ -0,0 +1,75 @@
+---
+title: 'Localizing the Inbox component'
+sidebarTitle: 'Localization'
+description: 'Learn how to localize the pre built Inbox component'
+---
+
+## Localization Prop
+
+The `localization` prop can be used to change the copywriting of the Inbox to a different language for your users or change the wording to suit your product. See the list of [available keys](https://github.com/novuhq/novu/blob/next/packages/js/src/ui/config/defaultLocalization.ts#L1), or use the fully typed TS auto complete to find the key you need.
+
+```tsx
+import { Inbox } from '@novu/react';
+
+function Novu() {
+ return (
+
+ `${notificationCount > 99 ? "99+" : notificationCount} new ${
+ notificationCount === 1 ? "notification" : "notifications"
+ }`,
+ "notification.actions.archive.tooltip": "Archive",
+ "notification.actions.unarchive.tooltip": "Unarchive",
+ "preferences.title": "Notification Preferences",
+ "preferences.global": "Global Preferences",
+ "preferences.workflow.disabled.notice":
+ "Contact admin to enable subscription management for this critical notification.",
+ "preferences.workflow.disabled.tooltip": "Contact admin to edit",
+ dynamic: {
+ // use the workflowId as a key to localize the workflow name
+ 'comment-on-post': 'Post comments',
+ },
+ locale: 'en-US',
+ }}
+ />
+ );
+}
+```
+
+### Specifying the workflow name using Framework
+
+In addition to specifying a workflow name in the `` component, you can also specify the workflow name in your Framework definition. The `name` property is optional, so if you don't specify it, the `workflowId` will be used as the name.
+
+```typescript
+import { workflow } from '@novu/framework';
+
+const weeklyCommentsWorkflow = workflow(
+ 'comment-on-post',
+ async ({ step }) => {
+ ...
+ });
+}, {
+ name: 'Post comments', // 👈 name is optional
+});
+```
+
+Now, your specified workflow name will be displayed in the `` component:
+
+
+
+
diff --git a/inbox/react/migration-guide.mdx b/inbox/react/migration-guide.mdx
new file mode 100644
index 00000000..b3c75cab
--- /dev/null
+++ b/inbox/react/migration-guide.mdx
@@ -0,0 +1,881 @@
+---
+title: "Migration Guide"
+sidebarTitle: "Migration Guide"
+description: "This guide outlines the key differences between the `@novu/notification-center` package and the new `@novu/react` package. Follow the steps below to migrate your application to the latest version."
+---
+
+ [@novu/react](/inbox/react/get-started) Inbox react component and [@novu/js](/inbox/headless/get-started) headless package is compatible with [@novu/framework](/workflow/overview) based workflows only. With old UI based workflows, our old [@novu/notification-center](https://v0.x-docs.novu.co/notification-center/client/react/get-started) component should be used.
+
+
+The `@novu/react` package introduces a more flexible and customizable way to display notifications in your application. This guide outlines the key differences between the `@novu/notification-center` package and the new `@novu/react` package. Follow the steps below to migrate your application to the latest Inbox version.
+
+## Why you should upgrade to @novu/react
+
+- **Customization**: The `@novu/react` package provides more customization options for the appearance and behavior of the notification components.
+- **Flexibility**: The new package offers more flexibility in handling notifications and integrating with third-party libraries.
+- **Performance**: It is optimized for performance and provides a smoother user experience.
+- **Bundle Size**: The new package has a smaller bundle size and improved tree-shaking capabilities.
+- **Compatibility with the `@novu/framework`**: The `@novu/react` package is designed to work seamlessly with the `@novu/framework` package for creating and managing notifications.
+
+## Breaking Changes
+
+The `@novu/react` package introduces several breaking changes compared to the `@novu/notification-center` package. Here are the key differences:
+
+### Components
+
+- The `PopoverNotificationCenter` component has been replaced with the `Inbox` component.
+- The `NotificationCenter` component has been replaced with the `Notifications` component.
+- The `NotificationBell` component has been replaced with the `Bell` component.
+
+### Styling
+
+- The `styles` props is replaced by an enchanced and easy to use `appearance` prop to customize the appearance of the notification components. For more information on `appearance` customization visit [here](/inbox/react/styling).
+
+### Notification
+
+- Removal of `seen` , `lastSeenDate`, `content`, `templateIdentifier`, `payload`, `cta` properties from the Notification object.
+- We have introduced `archive` functionality to the Notification object.
+
+```diff
+type Notification = {
+- _id: string;
++ id: string;
+
+- content: string;
++ body: string;
+
+- cta: IMessageCTA;
++ redirect?: Redirect;
++ primaryAction?: Action;
++ secondaryAction?: Action;
+
+- channel: ChannelTypeEnum;
++ channelType: ChannelType;
+
+- payload: Record;
++ data?: Record;
+
+- subscriber: Subscriber;
++ to: Subscriber;
+
+- seen: boolean;
+- lastSeenDate: string;
+- templateIdentifier: string;
+
++ subject?: string;
++ isRead: boolean;
++ isArchived: boolean;
++ readAt?: string | null;
++ archivedAt?: string | null;
++ avatar?: string;
++ tags?: string[];
+
+createdAt: string;
+};
+
+
+type Subscriber = {
+ id: string;
+ firstName?: string;
+ lastName?: string;
+ avatar?: string;
+ subscriberId: string;
+};
+
+type Redirect = {
+ url: string;
+ target?: '_self' | '_blank' | '_parent' | '_top' | '_unfencedTop';
+};
+
+type Action = {
+ label: string;
+ isCompleted: boolean;
+ redirect?: Redirect;
+};
+```
+
+## Getting Started
+
+To begin migrating to `@novu/react`, install the package via npm:
+
+```bash
+npm install @novu/react
+```
+
+## Basic Usage
+
+- Old Implementation
+
+```tsx
+import {
+ NovuProvider,
+ PopoverNotificationCenter,
+ NotificationBell,
+} from "@novu/notification-center";
+
+function Novu() {
+ return (
+
+
+ {({ unseenCount }) => }
+
+
+ );
+}
+export default Novu;
+```
+
+- New Implementation with `@novu/react`
+
+```tsx
+import { Inbox } from "@novu/react";
+
+function Novu() {
+ return (
+
+ );
+}
+
+export default Novu;
+```
+
+## Notification Center without Bell Icon
+
+The @novu/react package introduces a flexible way to display notifications as a list without the default bell icon. Utilize the `Inbox` and `Notifications` components to achieve this functionality.
+
+- Old Implementation
+
+```tsx
+import { NovuProvider, NotificationCenter } from "@novu/notification-center";
+
+function Novu() {
+ return (
+
+
+
+ );
+}
+
+export default Novu;
+```
+
+- New Implementation with `@novu/react`
+
+```tsx
+import { Inbox, Notifications } from "@novu/react";
+
+function Novu() {
+ return (
+
+
+
+ );
+}
+
+export default Novu;
+```
+
+## Custom Bell Icon
+
+Customize the bell icon that triggers the notifications popover using the `renderBell` prop.
+
+- Old Implementation
+
+```tsx
+import {
+ NovuProvider,
+ PopoverNotificationCenter,
+} from "@novu/notification-center";
+import CustomBell from "./CustomBell"; // Your custom bell icon component
+
+function Novu() {
+ return (
+
+
+ {({ unseenCount }) => (
+
+ )}
+
+
+ );
+}
+
+export default Novu;
+```
+
+- New Implementation with `@novu/react`
+
+```tsx
+import { Inbox } from "@novu/react";
+import CustomBell from "./CustomBell"; // Your custom bell icon component
+
+function Novu() {
+ return (
+ }
+ />
+ );
+}
+
+export default Novu;
+```
+
+## Notification Actions
+
+Handle user interactions with notifications effectively using the action handlers provided by `@novu/react`.
+
+### onNotificationClick
+
+Trigger a callback function when a user clicks on a notification item.
+
+- Old Implementation
+
+```tsx
+import {
+ NovuProvider,
+ PopoverNotificationCenter,
+ NotificationBell,
+ IMessage,
+} from "@novu/notification-center";
+
+function Novu() {
+ function handleOnNotificationClick(message: IMessage) {
+ // your logic to handle the notification click
+ if (message?.cta?.data?.url) {
+ window.location.href = message.cta.data.url;
+ }
+ }
+
+ return (
+
+
+ {({ unseenCount }) => }
+
+
+ );
+}
+
+export default Novu;
+```
+
+- New Implementation with `@novu/react`
+
+```tsx
+import { Inbox } from "@novu/react";
+
+function Novu() {
+ const handleNotificationClick = (notification) => {
+ // Your custom logic here
+ console.log("Notification clicked:", notification);
+ };
+
+ return (
+
+ );
+}
+
+export default Novu;
+```
+
+### onPrimaryActionClick and onSecondaryActionClick
+
+Handle primary and secondary actions within a notification explicitly.
+
+- Old Implementation
+
+```tsx
+import {
+ NovuProvider,
+ PopoverNotificationCenter,
+ IMessage,
+ MessageActionStatusEnum,
+ useUpdateAction,
+ ButtonTypeEnum,
+ NotificationBell,
+} from "@novu/notification-center";
+
+function Novu() {
+ const CustomNotificationCenter = () => {
+ const { updateAction } = useUpdateAction();
+
+ const handleOnActionClick = async (
+ templateIdentifier: string,
+ btnType: ButtonTypeEnum,
+ notification: IMessage
+ ) => {
+ if (templateIdentifier === "friend-request") {
+ if (btnType === "primary") {
+ /** Call your API to accept the friend request here **/
+
+ /** And then update Novu that this action has been taken, so the user won't see the button again **/
+ updateAction({
+ messageId: notification._id,
+ actionButtonType: btnType,
+ status: MessageActionStatusEnum.DONE,
+ });
+ }
+ }
+ };
+
+ return (
+
+ {({ unseenCount }) => }
+
+ );
+ };
+
+ return (
+
+
+
+ );
+}
+
+export default Novu;
+```
+
+- New Implementation with `@novu/react`
+
+```tsx
+import { Inbox } from "@novu/react";
+
+function Novu() {
+ const handlePrimaryActionClick = (notification) => {
+ // Handle primary action
+ console.log("Primary action clicked:", notification);
+ };
+
+ const handleSecondaryActionClick = (notification) => {
+ // Handle secondary action
+ console.log("Secondary action clicked:", notification);
+ };
+
+ return (
+
+ );
+}
+
+export default Novu;
+```
+
+## Avatar Icons
+
+Customize the avatar displayed alongside notifications by specifying an avatar `URL` at the workflow definition level using `@novu/framework`.
+
+```ts
+import { workflow } from "@novu/framework";
+
+workflow("welcome-notification", async (step) => {
+ await step.inApp("inbox", async () => ({
+ subject: "Welcome to Our Service!",
+ body: "We are thrilled to have you onboard.",
+ avatar: "https://example.com/path-to-your-avatar-image.png",
+ }));
+});
+```
+
+## Popover Positioning
+
+For advanced positioning and styling of the notifications popover, integrate third-party popover libraries such as Radix UI.
+
+- Old Implementation
+
+```tsx
+import {
+ PopoverNotificationCenter,
+ NotificationBell,
+ NovuProvider,
+} from "@novu/notification-center";
+
+function Novu() {
+ return (
+
+
+ {({ unseenCount }) => }
+
+
+ );
+}
+
+export default Novu;
+```
+
+- New Implementation with `@novu/react` and Radix UI as an example
+
+```tsx
+import React from "react";
+import * as RadixPopover from "@radix-ui/react-popover";
+import { Inbox, Bell, Notifications } from "@novu/react";
+
+function Novu() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default Novu;
+```
+
+## Custom Notification Item
+
+Customize the appearance and structure of individual notification items using the `renderNotification` prop.
+
+- Old Implementation
+
+```tsx
+import {
+ NovuProvider,
+ PopoverNotificationCenter,
+ NotificationBell,
+} from "@novu/notification-center";
+
+function Novu() {
+ return (
+
+ {
+ return (
+ {
+ e.preventDefault();
+ handleNotificationClick();
+ }}
+ >
+ {notification.content}
+
+ );
+ }}
+ >
+ {({ unseenCount }) => {
+ return ;
+ }}
+
+
+ );
+}
+
+export default Novu;
+```
+
+- New Implementation with `@novu/react`
+
+```tsx
+import { Inbox } from "@novu/react";
+
+function Novu() {
+ const renderCustomNotificationItem = (notification) => (
+
notification.read()}
+ >
+
+
+
{notification.subject}
+
{notification.body}
+
+
+ );
+
+ return (
+
+ );
+}
+
+export default Novu;
+```
+
+## Styling with appearance Prop
+
+Customize the overall look and feel of the notification components using the appearance prop, which supports both CSS objects and class names (including Tailwind CSS classes).
+
+- Old Implementation
+
+```tsx
+import {
+ NovuProvider,
+ PopoverNotificationCenter,
+ NotificationBell,
+} from "@novu/notification-center";
+
+function Novu() {
+ return (
+
+
+ {({ unseenCount }) => }
+
+
+ );
+}
+
+export default Novu;
+```
+
+- New Implementation with `@novu/react`
+
+```tsx
+import { Inbox } from "@novu/react";
+
+function Novu() {
+ return (
+
+ );
+}
+
+export default Novu;
+```
+
+For more information on `appearance` customization visit [here](/inbox/react/styling).
+
+## Multiple Tabs Support
+
+Organize notifications into different categories using tabs by leveraging the tags property in workflow definitions and the tabs prop in the Inbox component.
+
+### Create Multiple Tabs
+
+- Old Implementation
+
+After defining the feeds on the workflow UI, you were able to filter notifications based on the feedIdentifier.
+
+```tsx
+import {
+ NovuProvider,
+ PopoverNotificationCenter,
+ NotificationBell,
+} from "@novu/notification-center";
+
+function Novu() {
+ return (
+
+
+ {({ unseenCount }) => {
+ return (
+
+ );
+ }}
+
+
+ );
+}
+
+export default Novu;
+```
+
+- New Implementation with `@novu/react`
+
+1. Define multiple workflows with relevant tags.
+
+```ts
+import { workflow } from "@novu/framework";
+
+workflow(
+ "security-alerts",
+ async (step) => {
+ await step.inApp("inbox", async () => ({
+ subject: "Security Alert",
+ body: "A new login attempt was detected.",
+ }));
+ },
+ { tags: ["security"] }
+);
+
+workflow(
+ "promotional-offers",
+ async (step) => {
+ await step.inApp("inbox", async () => ({
+ subject: "Exclusive Offer!",
+ body: "Get 50% off on your next purchase.",
+ }));
+ },
+ { tags: ["promotions"] }
+);
+```
+
+2. Assign relevant tags to each workflow to categorize notifications appropriately.
+
+```tsx
+import { Inbox } from "@novu/react";
+
+const tabs = [
+ { label: "All Notifications", value: [] },
+ { label: "Security", value: ["security"] },
+ { label: "Promotions", value: ["promotions"] },
+];
+
+function Novu() {
+ return (
+
+ );
+}
+
+export default Novu;
+```
+
+## Localization
+
+Customize the language and text content of the notification components using the localization prop.
+
+```tsx
+import { Inbox } from "@novu/react";
+
+function Novu() {
+ return (
+
+ );
+}
+
+export default Novu;
+```
+
+## HMAC Encryption
+
+The process remains the same as before. See the detailed guide [here](/inbox/react/production#hmac-encryption).
+
+## Handling Notifications
+
+Handle notifications using the methods provided by the notification object.
+
+### Marking Notifications as Read
+
+Mark notifications as read using the `read` method provided by the notification object.
+
+```tsx
+import { Inbox } from "@novu/react";
+
+function Novu() {
+ const handleNotificationClick = (notification) => {
+ notification.read();
+ };
+
+ return (
+
+ );
+}
+
+export default Novu;
+```
+
+### Marking Notifications as Unread
+
+Mark notifications as unread using the `unread` method provided by the notification object.
+
+```tsx
+import { Inbox } from "@novu/react";
+
+function Novu() {
+ const handleNotificationClick = (notification) => {
+ notification.unread();
+ };
+
+ return (
+
+ );
+}
+
+export default Novu;
+```
+
+### Marking Notifications as Archive
+
+Mark notifications as archive using the `archive` method provided by the notification object.
+
+```tsx
+import { Inbox } from "@novu/react";
+
+function Novu() {
+ const handleNotificationClick = (notification) => {
+ notification.archive();
+ };
+
+ return (
+
+ );
+}
+
+export default Novu;
+```
+
+### Marking Notifications as Unarchive
+
+Mark notifications as unarchive using the `unarchive` method provided by the notification object.
+
+```tsx
+import { Inbox } from "@novu/react";
+
+function Novu() {
+ const handleNotificationClick = (notification) => {
+ notification.unarchive();
+ };
+
+ return (
+
+ );
+}
+
+export default Novu;
+```
diff --git a/inbox/react/multiple-tabs.mdx b/inbox/react/multiple-tabs.mdx
new file mode 100644
index 00000000..0b0c156f
--- /dev/null
+++ b/inbox/react/multiple-tabs.mdx
@@ -0,0 +1,54 @@
+---
+title: 'Configuring Multiple Tabs'
+sidebarTitle: 'Tabs'
+---
+
+Novu enables the creation of a multi-tab experience for the Inbox, allowing you to filter and display notification lists within distinct UI tabs.
+
+## Defining tabs
+
+Define the `tabs` prop to display multiple tabs in the Inbox component.
+
+Each tab object should include a `label` and `filter` properties. The `label` property represents the text displayed on the specific tab. The `filter` property is an object that serves as the "filter criteria" for the notifications displayed in the list. The filter should include the `tags` which is an array of strings.
+
+The notifications are filtered and matched by comparing the [workflow](/workflow/overview#tags) `tags` field with the tab `filter.tags` array. Each notification is a part of a specific [workflow](/concepts/workflows). You can combine tags from different workflows to create a customized tab experience.
+
+The example below demonstrates how to define multiple tabs in the Inbox component.
+
+```tsx
+import { Inbox } from '@novu/react';
+
+const tabs = [
+ {
+ label: 'All Notifications',
+ filter: { tags: [] },
+ },
+ {
+ label: 'Newsletter',
+ filter: { tags: ['newsletter'] },
+ },
+ {
+ label: 'React',
+ filter: { tags: ['react'] },
+ },
+];
+
+function InboxNovu() {
+ return (
+
+ );
+}
+```
+
+
+
+
+
+
+ Here tab `filter.tags` are taken from workflow tags. Tags can be configured in
+ options field of [workflow](/framework/typescript/workflow).
+
diff --git a/inbox/react/preference.mdx b/inbox/react/preference.mdx
new file mode 100644
index 00000000..f3a809be
--- /dev/null
+++ b/inbox/react/preference.mdx
@@ -0,0 +1,22 @@
+---
+title: 'Managing Subscriber Preferences in React'
+sidebarTitle: 'Preference'
+description: 'Learn how to manage and customize subscriber notification preferences in your React application.'
+---
+
+By default, Novu will show the subscriber preferences cog icon on the Inbox component.
+
+Users can enable/disable any active channel in the workflow using subscriber preferences or they can update the preference globally for all workflows under the `Global Preferences`.
+
+
+
+### Hide global preferences
+Global preferences can be hidden by using following css style in root css file.
+```css
+.nv-workflowContainer:first-child {
+ display: none;
+}
+```
diff --git a/inbox/react/production.mdx b/inbox/react/production.mdx
new file mode 100644
index 00000000..817b9043
--- /dev/null
+++ b/inbox/react/production.mdx
@@ -0,0 +1,47 @@
+---
+title: 'Production Setup for React'
+sidebarTitle: 'Going to production'
+description: 'Learn how to prepare your React notification inbox for production deployment including HMAC encryption and security best practices.'
+---
+
+
+## HMAC Encryption
+
+When Novu's user adds the Inbox to their application they are required to pass a `subscriberId` which identifies the user's end-customer, and the application Identifier which is acted as a public key to communicate with the notification feed API.
+
+A malicious actor can access the user feed by accessing the API and passing another `subscriberId` using the public application identifier.
+
+HMAC encryption will make sure that a `subscriberId` is encrypted using the secret API key, and those will prevent malicious actors from impersonating users.
+
+### Enabling HMAC Encryption
+
+In order to enable Hash-Based Message Authentication Codes, you need to visit the admin panel In-App settings page and enable HMAC encryption for your environment.
+
+
+
+
+
+1. Next step would be to generate an HMAC encrypted subscriberId on your backend:
+
+```tsx
+import { createHmac } from 'crypto';
+
+const hmacHash = createHmac('sha256', process.env.NOVU_SECRET_KEY)
+ .update(subscriberId)
+ .digest('hex');
+```
+
+2. Then pass the created HMAC to your client side application forward it to the component:
+
+```tsx
+
+```
+
+
+ If HMAC encryption is active in In-App provider settings and `subscriberHash`
+ along with `subscriberId` is not provided, then Inbox will not load
+
diff --git a/inbox/react/styling.mdx b/inbox/react/styling.mdx
new file mode 100644
index 00000000..665c15eb
--- /dev/null
+++ b/inbox/react/styling.mdx
@@ -0,0 +1,308 @@
+---
+title: "Styling the Inbox component"
+sidebarTitle: "Styling"
+description: "Learn how to style the pre built Inbox component"
+---
+
+## Customization Hierarchy
+
+The Inbox component is built to allow for multiple layers of styling, which allows the specificity required to style the Inbox to meet the requirements of your use case.
+
+Depending on the level of customization you need, you can choose to style the inbox using one of the following approaches:
+
+- [Appearance Prop](#appearance-prop)
+ - [Variables](#variables) - Global primitives such as buttons, popovers, dropdowns and etc...
+ - [Elements](#elements) - Style individual elements
+- [Custom notification rendering](#render-notification-component) - Render a custom notification item with complete control
+- [Custom Composition](/inbox/react/components/overview#composition) - Compose our components for custom layouts
+
+You can see Styling in action on the [Inbox Playground](https://inbox.novu.co) with common themes like Notion, Reddit and more!.
+
+## Appearance Prop
+
+The `appearance` prop can be used to customise the Inbox. It has three main keys: `baseTheme`, `variables` and `elements`.
+
+- **Variables**: Global styling variables that apply to multiple elements within the inbox.
+- **Elements**: Elements are the individual UI components that make up the Inbox.
+- **Base theme**: This is the base theme applied to the ``. It has the same keys as `appearance`. Used for applying base themes like `dark`.
+
+### Variables
+
+Variables are used to define global styling properties that can be reused throughout the inbox.
+You might want to use variables to the styling of multiple components at once, for example, if you want to change the border radius of all the components at once, you can do so by updating the `colorPrimary` variable, which will modify the CTA buttons, unseen counter and etc...
+
+
+
+
+ The background color of the inbox component.
+
+
+
+ The primary text color used in the inbox.
+
+
+
+ The main accent color for interactive elements.
+
+
+
+ The text color used on primary-colored elements.
+
+
+
+ A secondary color for less prominent elements.
+
+
+
+ The text color used on secondary-colored elements.
+
+
+
+ The background color of notification counters.
+
+
+
+ The text color used in notification counters.
+
+
+
+ A neutral color used for borders or backgrounds.
+
+
+
+ The color of shadows applied to elements.
+
+
+
+ The base font size for text in the inbox.
+
+
+
+ The border radius applied to various elements.
+
+
+
+
+
+
+
+
+
+#### Styling Variables
+
+You can override the default elements by passing your own styles or CSS classes to the `elements` object.
+
+```tsx
+const appearance = {
+ variables: {
+ colorBackground: "yellow",
+ },
+};
+
+;
+```
+
+### Elements
+
+The `elements` object allows you to define styles for these components. Each key corresponds to an component, and the value is an object containing `style properties` or you can also pass your `css classes`.
+Here's a list of available elements that can be styled using the `elements` object in your appearance configuration:
+
+#### Finding element selectors
+
+You can inspect the elements you want to customize using the browser's dev tools, each element has a unique selector that you can use to style starting with `nv-`.
+
+Strip the `nv-` prefix when and add it to the `elements` object. For example, to style the `nv-notificationPrimaryAction__button` element, you can add the following to the `elements` object:
+
+```tsx
+const appearance = {
+ elements: {
+ notificationPrimaryAction__button: {
+ backgroundColor: "red",
+ },
+ },
+};
+```
+
+
+
+
+
+
+ Any selector that appears before the 🔔 emoji, can be targeted via the
+ elements property in Appearance prop (stripping the `nv-` prefix). You can
+ also use TS autocomplete to find the available elements.
+
+
+### Dark theme
+
+No need to implement a custom dark theme. Just import our premade `dark` theme and use it via the `baseTheme` option in `appearance`.
+
+```tsx
+import { Inbox } from "@novu/react";
+import { dark } from "@novu/react/themes";
+import { useTheme } from "next-themes";
+
+export function Novu() {
+ const { resolvedTheme } = useTheme();
+
+ return (
+
+ );
+}
+```
+
+### Bring your own CSS
+
+You can override the default elements by passing your own styles or CSS classes to the `elements` object.
+
+#### Using Tailwind CSS
+
+You can also use Tailwind CSS classes to style the Inbox components. You can pass the classes
+directly to the `elements` object.
+
+```tsx
+import { Inbox } from "@novu/react";
+
+const appearance = {
+ elements: {
+ bellIcon: "p-4 bg-white rounded-full",
+ notification:
+ "bg-white rounded-lg shadow-sm hover:shadow-md hover:bg-gray-50",
+ },
+};
+
+export function Novu() {
+ return (
+
+ );
+}
+```
+
+#### Using CSS Modules
+
+You can also use `CSS Modules` to style the Inbox components. Here's how you can do it:
+
+- Create a CSS module file `(e.g. styles.module.css)` with the styles you want to apply to the Inbox components.
+
+```css
+.bellIcon {
+ padding: 1rem;
+ background-color: white;
+ border-radius: 50%;
+}
+
+.bellIcon:hover {
+ background-color: #f9fafb;
+}
+
+.notification {
+ background-color: white;
+ border-radius: 0.5rem;
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+}
+
+.notification:hover {
+ background-color: #f9fafb;
+}
+```
+
+- Import the CSS module file and pass the classes to the elements object.
+
+```tsx
+import { Inbox } from "@novu/react";
+import styles from "./styles.module.css";
+
+const appearance = {
+ elements: {
+ bellIcon: styles.bellIcon,
+ notification: styles.notification,
+ },
+};
+
+export function Novu() {
+ return (
+
+ );
+}
+```
+
+#### Using Styles Object
+
+You can also use a styles object to style the Inbox components. You can pass the styles
+directly to the `elements` object.
+
+```tsx
+import { Inbox } from "@novu/react";
+
+const appearance = {
+ elements: {
+ bellIcon: {
+ padding: "1rem",
+ backgroundColor: "white",
+ borderRadius: "50%",
+ },
+ notification: {
+ backgroundColor: "white",
+ borderRadius: "0.5rem",
+ boxShadow: "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
+ },
+ },
+};
+
+export function Novu() {
+ return (
+
+ );
+}
+```
+
+#### Render subject and body with bold text
+
+By default, the Inbox notification text for the subject and body is rendered in a normal font weight.
+To highlight important words or phrases within your notification messages, you can wrap the desired subject or body text in double asterisks (\*\*).
+Here’s an example of how you can do this using the Novu Framework:
+
+```js
+await step.inApp("inbox", async () => {
+ return {
+ subject: "**A new member joined the team!**",
+ body: "**John Doe** joined the team! Say hello and help them feel at home",
+ };
+});
+```
+
+
+ Please note, that when you are rendering the custom notification component you
+ will need to parse the text and apply the bold styling accordingly.
+
+
+Currently we only support bold text. We are working on adding more text formatting options in the future.
+You can learn more about the Novu Framework in-app step [here](/framework/typescript/steps/inApp).
+
+## Render Notification Component
+
+You can render your own custom notification item component by passing a `renderNotification` prop to the `Inbox` or `Notifications` component, this will allow you to style and render more complex notification items.
+
+```tsx
+ {
+ return ;
+ }}
+/>
+```
diff --git a/integrations/overview.mdx b/integrations/overview.mdx
new file mode 100644
index 00000000..c1b0f258
--- /dev/null
+++ b/integrations/overview.mdx
@@ -0,0 +1,49 @@
+---
+title: "Novu Integration Hub"
+sidebarTitle: "Overview"
+description: "Discover how to integrate Novu with your tech stack including delivery providers, content frameworks, and validation libraries."
+---
+
+Novu was designed to be integrated with any part of your tech stack. This includes:
+
+- Delivery providers
+- Content frameworks
+- Validation and schema libraries
+- and more!
+
+## Delivery provider integrations
+
+You can find the list of available integrations for each channel:
+
+
+
+ Configure email providers and settings
+
+
+ Set up SMS messaging capabilities
+
+
+ Enable push notification delivery
+
+
+ Integrate with chat platforms
+
+
+ Manage in-app notification center
+
+
+
+## Framework integrations
+
+Those integrations are available for the [Novu Framework SDK](/framework/typescript/overview), and can be used to build custom notification workflows with your own runtime logic.
+
+
+
+
+
+ Use any content framework to build your notification content
+
+
+ Validate your notification logic with JSON schema
+
+
diff --git a/integrations/providers/chat/adding-chat.mdx b/integrations/providers/chat/adding-chat.mdx
new file mode 100644
index 00000000..74522d28
--- /dev/null
+++ b/integrations/providers/chat/adding-chat.mdx
@@ -0,0 +1,140 @@
+---
+title: "Adding Chat Channel"
+sidebarTitle: "Adding Chat Channel"
+description: "Learn how to add the Chat channel to your application"
+icon: "circle-plus"
+---
+
+import { MissingProvider } from "/snippets/missing-provider.mdx";
+
+Chat channels allow you to deliver instant, contextual messages to your subscribers via their preferred chat platform and apps.
+
+
+
+
+
+The Chat channel is not enabled by default. To use it, configure a provider like Slack, Discord, or others.
+
+
+1. Go to the Novu Dashboard and click **"Integrations"** on the left sidebar
+2. Click **"Add a provider"**
+3. Locate the **Chat** channel and select the provider you want to use and click **"Next"**
+4. Select for which environment you want to add the Provider
+5. (Optional) Add Conditions to activate the provider only under certain conditions, **useful for tenant-based providers**
+6. Click **"Create"**
+7. Add your Chat provider credentials:
+ Each chat provider requires defferent type of credentials. There are few providers which does not require any credentials example: `Discord`
+ - Provider-specific credentials such as API key / Auth token, Client ID, Client Secret, or password
+8. Save the configuration by clicking **"Update"**
+
+
+
+
+
+
+ 1. Go to the Novu Dashboard and click **"Workflows"** on the left sidebar.
+ 2. Click the **"Add a Workflow"** button.
+ 3. Add a step and select **"Chat"** as the channel.
+ 4. Configure the Chat content:
+ - Message body (e.g., `{{subscriber.firstName}}, your order {{orderId}} has shipped.`).
+ - Dynamic placeholders for personalized content.
+ 5. Optionally, set fallback channels to ensure reliable delivery if Chat fails.
+
+
+
+
+
+Novu’s server-side SDKs make integrating Novu’s REST APIs straightforward, letting you focus on implementing workflows without dealing with repetitive code.
+
+
+
+
+
+
+Ensure your Chat configuration is working correctly by testing the setup.
+
+1. Go to the Novu Dashboard, navigate to the **"Workflows"** section, and locate your configured workflow.
+2. Click **"Test Workflow"** and provide sample data, such as a phone number or dynamic variables.
+3. Verify delivery in the Novu Logs or your Chat provider’s dashboard.
+
+
+
+
+
+
+## Sending chat message
+
+Steps to send a chat message using Novu:-
+
+1. Add a chat provider integration to your Novu account from the integration store. Enter credentials if required and save the integration. Follow corresponding provider documentation to get the required credentials.
+2. Create a new subscriber
+
+> Step 2 can be skipped if you already have a subscriber. You can use the `subscriberId` of an existing subscriber.
+
+3. [Update](/integrations/providers/chat/overview#update-credential-webhookurl) the subscriber credential `webhookUrl` for this provider and integration.
+
+4. Create a new [worklow](/workflow/overview) or use an existing workflow. Add chat channel and [write content](/content-creation-design/notification-content-creation#chat) for the message.
+
+5. Trigger this workflow to above `subscriberId` using trigger identifier
+
+6. Check the chat provider if message is received.
+
+## Update credential webhookUrl
+
+
+
+```javaScript
+import {
+ Novu,
+ ChatProviderIdEnum
+} from '@novu/node';
+
+const novu = new Novu("");
+
+await novu.subscribers.setCredentials('subscriberId', ChatProviderIdEnum.Slack, {
+webhookUrl: "",
+});
+
+```
+
+
+```bash
+curl -L -X PUT 'https://api.novu.co/v1/subscribers//credentials' \
+-H 'Content-Type: application/json' \
+-H 'Accept: application/json' \
+-H 'Authorization: ApiKey ' \
+-d '{
+ "providerId": "slack",
+ "credentials": {
+ "webhookUrl": ""
+ },
+ "integrationIdentifier": "slack-MnGLxp8uy"
+}'
+```
+
+
+
+
+Checkout the [API reference](/api-reference/subscribers/update-subscriber-credentials) for more details.
+
+
+ {" "}
+ Integration identifier is similar to Provider identifier but it is different than
+ Provider Id. It is unique for each integration. You can find the `integrationIdentifier`
+ in the integration store page.{" "}
+
+
+## Common errors
+
+Common erros and reason of those errors while sending chat messages using Novu.
+
+1. Subscriber does not have a configured channel.
+ - if the subscriber does not have credentials set up for any of the chat provider.
+2. Webhook URL for the chat channel is missing.
+ - `webhookUrl` field is null, undefined or not set. Check using [get subscriber api](/api-reference/subscribers/get-subscriber).
+3. Subscriber does not have an active integration.
+ - if subscriber have credentials set but integration is either not active or deleted for this credential.
+
+
+
+
diff --git a/integrations/providers/chat/discord.mdx b/integrations/providers/chat/discord.mdx
new file mode 100644
index 00000000..ff4048cd
--- /dev/null
+++ b/integrations/providers/chat/discord.mdx
@@ -0,0 +1,63 @@
+---
+title: "Discord"
+description: "Learn about how to use Discord provider for chat notifications"
+---
+
+When using Discord, you will have to store the integration credentials within the subscriber entity. Discord supports two ways to do this:
+
+1. Using the **Discord Webhook** integration.
+2. Using the **Discord Bot** integration.
+
+Right now, Novu only supports the **Discord Webhook** integration. This approach has been because the easiest way to set it up is when you have a predefined destination for where the notifications should be sent. It's particularly useful for notifying a specific channel about updates.
+
+**Quickstart:**
+
+To get started with using Novu’s Discord Webhook integration, you need a ‘webhook token’. Here’s how you can generate the same for testing purposes:
+
+1. Go to the channel you want to add the webhook to (you need to be an admin of the discord server).
+2. Right-click the channel and select "Edit Channel".
+3. Integrations -> Webhooks -> New Webhook
+4. Copy the webhook URL.
+5. After obtaining the webhook URL in the previous step, you need to store it within the subscriber entity. Doing this ensures that Novu knows where (in which discord channel) to send the notification to. Here’s how to do it:
+
+
+
+```javaScript
+import {
+ Novu,
+ ChatProviderIdEnum
+} from '@novu/node';
+
+const novu = new Novu("");
+
+await novu.subscribers.setCredentials('subscriberId', ChatProviderIdEnum.Discord, {
+webhookUrl: "",
+});
+
+```
+
+
+```bash
+curl -L -X PUT 'https://api.novu.co/v1/subscribers//credentials' \
+-H 'Content-Type: application/json' \
+-H 'Accept: application/json' \
+-H 'Authorization: ApiKey ' \
+-d '{
+ "providerId": "discord",
+ "credentials": {
+ "webhookUrl": ""
+ },
+ "integrationIdentifier": "discord-MnGLxp8uy"
+}'
+```
+
+
+
+
+Checkout the [API reference](/api-reference/subscribers/update-subscriber-credentials) for more details.
+
+ - `subscriberId` is a custom identifier used when identifying your users within the Novu platform.
+ - `providerId` is a unique provider identifier. We recommend using our ChatProviderIdEnum to specify the provider.
+ - The third parameter is the credentials object. In this case, we use the `webhookUrl` property to specify the webhook URL generated in the previous step.
+
+6. You are all set up and ready to send your first chat message via our `@novu/node` package or directly using the REST API in the channel you chose on your discord server.
diff --git a/integrations/providers/chat/mattermost.mdx b/integrations/providers/chat/mattermost.mdx
new file mode 100644
index 00000000..bda96458
--- /dev/null
+++ b/integrations/providers/chat/mattermost.mdx
@@ -0,0 +1,70 @@
+---
+title: "Mattermost"
+description: "Learn about how to use Mattermost provider for chat notifications"
+---
+
+When using Mattermost, you will have to store the integration credentials within the subscriber entity. Mattermost supports two ways to do this:
+
+1. Using the **Mattermost Webhook** integration.
+2. Using the **Mattermost Bot** integration.
+
+### Mattermost Webhook Integration
+
+To integrate Mattermost with your application using the Mattermost Webhook integration, follow these steps:
+
+1. Create an incoming webhook in Mattermost. This can be done by going to the **Integrations** page and clicking on the **Incoming Webhooks** tab.
+2. Click on the **Add Incoming Webhook** button and configure the webhook settings. Be sure to select the channel where you want to receive notifications.
+3. Click on the **Save** button to generate a webhook URL.
+
+Once you have the webhook URL, you can store it in the subscriber entity in your application. This will allow you to send notifications to Mattermost using the following code:
+
+```jsx
+import { Novu, ChatProviderIdEnum } from "@novu/node";
+
+const novu = new Novu("");
+
+await novu.subscribers.setCredentials(
+ "subscriberId",
+ ChatProviderIdEnum.Mattermost,
+ { webhookUrl: "" }
+);
+
+// Send a notification to Mattermost using the subscriber ID and payload.
+await novu.trigger("", {
+ to: {
+ subscriberId: "",
+ },
+ payload: {
+ message: "This is a notification from my application!",
+ },
+});
+```
+
+### Mattermost Bot Integration
+
+To integrate Mattermost with your application using the Mattermost Bot integration, you will need to create a Mattermost bot account and generate an API token for the bot. Once you have the API token, you can store it in the subscriber entity in your application.
+
+Once you have stored the API token in the subscriber entity, you can send notifications to Mattermost using the following code:
+
+```jsx
+import { Novu, ChatProviderIdEnum } from "@novu/node";
+
+const novu = new Novu("");
+
+// Get the Mattermost bot API token for the subscriber.
+await novu.subscribers.setCredentials(
+ "subscriberId",
+ ChatProviderIdEnum.Mattermost,
+ { botApiToken: "" }
+);
+
+// Send a notification to Mattermost using the bot API token.
+await novu.trigger("", {
+ to: {
+ subscriberId: "",
+ },
+ payload: {
+ message: "This is a notification from my application!",
+ },
+});
+```
diff --git a/integrations/providers/chat/ms-teams.mdx b/integrations/providers/chat/ms-teams.mdx
new file mode 100644
index 00000000..059a1eb4
--- /dev/null
+++ b/integrations/providers/chat/ms-teams.mdx
@@ -0,0 +1,70 @@
+---
+title: "MS Teams"
+description: "Learn about how to use MS Teams provider for chat notifications"
+---
+
+import ProviderImplementation from "/snippets/provider-implementation.mdx";
+
+MS Teams does not need any `API Key` or `Client ID` to send notifications. Our current implementation is based on the [Incoming Webhook URL](https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook?tabs=newteams%2Cjavascript). It is a URL that you can use to send messages to a specific channel. This URL needs to be stored on the subscriber entity
+
+Similar to [Discord](https://www.notion.so/Additional-Resources-65ef4aca5d0f4115947030c2f5705101?pvs=21), the credential for this provider needs to be stored in the [subscriber entity](/api-reference/subscribers/update-subscriber-credentials).
+
+## Creating incoming webhook URL
+
+Checkout step by step instructions on microsoft team's documentation on how to [create an incoming webhook url](https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook?tabs=newteams%2Cjavascript).
+
+
+ {" "}
+
+
+## Storing webhook url on subscriber entity
+
+The URL generated above will be the target channel of a subscriber that you want to integrate with. To make this connection, you have to:
+
+1. Copy the URL that you generated above
+2. Update the subscriber credentials with this URL using the MS Teams provider id. You can do this step by using the `@novu/node` library, as shown below:
+
+
+
+```javaScript
+import {
+ Novu,
+ ChatProviderIdEnum
+} from '@novu/node';
+
+const novu = new Novu("");
+
+await novu.subscribers.setCredentials(
+'subscriberId',
+ChatProviderIdEnum.MsTeams, // providerId
+{ webhookUrl: "" },
+'msteams-MnGLxp8uy' // integration identifier
+);
+
+```
+
+
+```bash
+curl -L -X PUT 'https://api.novu.co/v1/subscribers//credentials' \
+-H 'Content-Type: application/json' \
+-H 'Accept: application/json' \
+-H 'Authorization: ApiKey ' \
+-d '{
+ "providerId": "msteams",
+ "credentials": {
+ "webhookUrl": ""
+ },
+ "integrationIdentifier": "msteams-MnGLxp8uy"
+}'
+```
+
+
+
+
+Checkout the [API reference](/api-reference/subscribers/update-subscriber-credentials) for more details.
+
+- `subscriberId` is a custom identifier used when identifying your users within the Novu platform.
+- `providerId` is a unique provider identifier. We recommend using our ChatProviderIdEnum to specify the provider.
+- The third parameter is the credentials object, in this case, we use the `webhookUrl` property to specify the MS Teams channel webhook URL or by calling the `Update Subscriber Credentials` endpoint on Novu API. Check endpoint details [here](/api-reference/subscribers/update-subscriber-preference).
+
+
diff --git a/integrations/providers/chat/overview.mdx b/integrations/providers/chat/overview.mdx
new file mode 100644
index 00000000..e636c21f
--- /dev/null
+++ b/integrations/providers/chat/overview.mdx
@@ -0,0 +1,37 @@
+---
+title: "Chat Providers Integration"
+sidebarTitle: "Overview"
+description: "Learn how to configure and use chat providers like Slack, Microsoft Teams, WhatsApp, and Discord with Novu's notification infrastructure."
+icon: "circle-info"
+---
+
+Novu brings chat notifications into your development workflow, giving you a unified way to manage messaging across platforms and apps. Whether you're working with tools like Slack or Microsoft Teams or apps like WhatsApp, Telegram, and Discord, Novu lets you integrate, manage, and scale chat notifications without unnecessary complexity.
+
+The Chat channel provides:
+
+- **Cross-Platform Integration**: A single system to handle messaging platforms (e.g., Slack, Teams) and apps (e.g., WhatsApp, Telegram, Discord)
+- **Scalability at Its Core**: Designed to handle high-frequency messaging without breaking a sweat
+- **Effortless Configuration**: Minimal setup, maximum control over your chat notification workflows
+
+## Key Features
+
+- **Platform and App Agnostic**: Supports workplace messaging platforms (Slack, Teams) and consumer apps (Discord, WhatsApp, Telegram) without additional overhead
+- **Dynamic Content Handling**: Inject real-time, user-specific data into messages with simple APIs
+- **Provider Independence**: Easily switch between chat providers or use multiple simultaneously
+- **Delivery Monitoring**: Full visibility into delivery status, failures, and message engagement
+- **Fallback Logic**: Automate retries and backup providers to ensure messages are delivered
+- **Reusable Message Templates**: Standardize message structure across your channels
+- **Developer-Centric APIs**: Clean, intuitive endpoints to plug notifications directly into your backend
+
+## Messaging Platforms vs. Messaging Apps
+
+- **Messaging Platforms** (e.g., Slack, Teams): Ideal for structured, workplace communication. Use these to notify teams or users in collaborative environments
+- **Messaging Apps** (e.g., WhatsApp, Telegram, Discord): Best for consumer-facing messaging. Engage users directly through personal or group chats
+
+## Common Use Cases
+
+- **Team Notifications**: Deliver updates to Slack channels or Teams users without needing complex integrations
+- **Customer Engagement**: Notify users on apps like Telegram or WhatsApp with real-time updates or alerts
+- **System Alerts**: Push critical system or workflow notifications to technical teams via chat platforms
+- **Event Reminders**: Send timely reminders for meetings, webinars, or deadlines
+- **Community Notifications**: Manage announcements, updates, or discussions in platforms like Discord
diff --git a/integrations/providers/chat/slack.mdx b/integrations/providers/chat/slack.mdx
new file mode 100644
index 00000000..2236df1a
--- /dev/null
+++ b/integrations/providers/chat/slack.mdx
@@ -0,0 +1,141 @@
+---
+title: "Slack"
+description: "Learn about how to use Slack provider for chat notifications"
+---
+
+When using Slack you will have to save the integration credentials in the subscriber entity.
+
+This guide will walk you through the steps needed to obtain the `webhookUrl` that Novu requires to send chat messages to your customers.
+
+We will provide the basic flow that the user needs to perform, to successfully send notifications via the Slack integration.
+
+## Creating application
+
+This step is optional, if you already have a Slack application you can reuse it.
+
+1. Go to Slack's developer dashboard https://api.slack.com/apps.
+2. Create a new application.
+
+## Integrating Novu with Slack
+
+### Manually managed
+
+To use the manually managed option, you need to generate a `webhookUrl` and plug it into your backend.
+
+1. Goto 'Incoming Webhooks' in your Slack app settings and turn it on.
+
+ {" "}
+
+2. Click on the 'Add New Webhook to Workspace':
+
+ {" "}
+
+3. Now, go ahead and select the channel in which you want to send notifications and click 'allow'.
+
+ {" "}
+
+4. Then, copy the 'webhookUrl' from Slack.
+
+ {" "}
+
+5. Now, you need to save the `webhookUrl` on the relevant subscriber entity in Novu. Here's an example to do the same using our Node SDK:
+
+## Writing Slack content (Blocks API)
+
+Novu Framework supports using [blocks](https://api.slack.com/block-kit) as part of the delivered messages using the provider overrides of the `chat` channel:
+
+You can use the [Blocks Playground](https://app.slack.com/block-kit-builder/T02QYEPHZMM#%7B%22blocks%22:%5B%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22Hello,%20Assistant%20to%20the%20Regional%20Manager%20Dwight!%20*Michael%20Scott*%20wants%20to%20know%20where%20you'd%20like%20to%20take%20the%20Paper%20Company%20investors%20to%20dinner%20tonight.%5Cn%5Cn%20*Please%20select%20a%20restaurant:*%22%7D%7D,%7B%22type%22:%22divider%22%7D,%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22*Farmhouse%20Thai%20Cuisine*%5Cn:star::star::star::star:%201528%20reviews%5Cn%20They%20do%20have%20some%20vegan%20options,%20like%20the%20roti%20and%20curry,%20plus%20they%20have%20a%20ton%20of%20salad%20stuff%20and%20noodles%20can%20be%20ordered%20without%20meat!!%20They%20have%20something%20for%20everyone%20here%22%7D,%22accessory%22:%7B%22type%22:%22image%22,%22image_url%22:%22https://s3-media3.fl.yelpcdn.com/bphoto/c7ed05m9lC2EmA3Aruue7A/o.jpg%22,%22alt_text%22:%22alt%20text%20for%20image%22%7D%7D,%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22*Kin%20Khao*%5Cn:star::star::star::star:%201638%20reviews%5Cn%20The%20sticky%20rice%20also%20goes%20wonderfully%20with%20the%20caramelized%20pork%20belly,%20which%20is%20absolutely%20melt-in-your-mouth%20and%20so%20soft.%22%7D,%22accessory%22:%7B%22type%22:%22image%22,%22image_url%22:%22https://s3-media2.fl.yelpcdn.com/bphoto/korel-1YjNtFtJlMTaC26A/o.jpg%22,%22alt_text%22:%22alt%20text%20for%20image%22%7D%7D,%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22*Ler%20Ros*%5Cn:star::star::star::star:%202082%20reviews%5Cn%20I%20would%20really%20recommend%20the%20%20Yum%20Koh%20Moo%20Yang%20-%20Spicy%20lime%20dressing%20and%20roasted%20quick%20marinated%20pork%20shoulder,%20basil%20leaves,%20chili%20&%20rice%20powder.%22%7D,%22accessory%22:%7B%22type%22:%22image%22,%22image_url%22:%22https://s3-media2.fl.yelpcdn.com/bphoto/DawwNigKJ2ckPeDeDM7jAg/o.jpg%22,%22alt_text%22:%22alt%20text%20for%20image%22%7D%7D,%7B%22type%22:%22divider%22%7D,%7B%22type%22:%22actions%22,%22elements%22:%5B%7B%22type%22:%22button%22,%22text%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Farmhouse%22,%22emoji%22:true%7D,%22value%22:%22click_me_123%22%7D,%7B%22type%22:%22button%22,%22text%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Kin%20Khao%22,%22emoji%22:true%7D,%22value%22:%22click_me_123%22,%22url%22:%22https://google.com%22%7D,%7B%22type%22:%22button%22,%22text%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Ler%20Ros%22,%22emoji%22:true%7D,%22value%22:%22click_me_123%22,%22url%22:%22https://google.com%22%7D%5D%7D%5D%7D) from Slack to learn more about the diffrent blocks available.
+
+```typescript
+await step.chat('send-chat', async () => {
+ return {
+ // This will be used as a fallback for the chat provider if other than Slack provider is used
+ body: 'A new post has been created',
+ };
+}, {
+ providers: {
+ slack: async ({ inputs }) => ({
+ text: 'A new post has been created',
+ blocks: [
+ {
+ type: 'section',
+ text: {
+ type: 'mrkdwn',
+ text: 'A new post has been created',
+ },
+ },
+ ],
+ }),
+ }
+});
+```
+
+
+## Update credential webhookUrl
+
+
+
+```javaScript
+import {
+ Novu,
+ ChatProviderIdEnum
+} from '@novu/node';
+
+const novu = new Novu("");
+
+await novu.subscribers.setCredentials('subscriberId', ChatProviderIdEnum.Slack, {
+ webhookUrl: "",
+});
+
+```
+
+
+```bash
+curl -L -X PUT 'https://api.novu.co/v1/subscribers//credentials' \
+-H 'Content-Type: application/json' \
+-H 'Accept: application/json' \
+-H 'Authorization: ApiKey ' \
+-d '{
+ "providerId": "slack",
+ "credentials": {
+ "webhookUrl": ""
+ },
+ "integrationIdentifier": "slack-MnGLxp8uy"
+}'
+```
+
+
+
+Checkout the [API reference](/api-reference/subscribers/update-subscriber-credentials) for more details.
+
+- `subscriberId` is a custom identifier used when identifying your users within the Novu platform.
+- `providerId` is a unique provider identifier. We recommend using our ChatProviderIdEnum.Slack if you’re using Node, else string of Slack to specify the provider.
+- The third parameter is the credentials object. In this case, we use the `webhookUrl` property to specify the webhook URL generated in the previous step.
+
+6. You are all set up and ready to send your first chat message via our @novu/node package or directly using the REST API.
+
+## Configuring Slack application
+
+1. Go to OAuth & Permissions on Slack's Developer Dashboard and add your REDIRECT_URL in Redirect URLs.
+ - If you use a manual Management solution, add the API endpoint you created in Step 1.
+2. Go to Incoming Webhooks from the left menu and Activate Incoming Webhooks.
+3. Go to Manage Distribution and at the bottom of the page, tick Remove Hard Coded Information and Activate Public Distribution.
+
+### Enabling HMAC Encryption
+
+To enable Hash-Based Message Authentication Codes, you need to do the following steps:
+
+1. Visit the integration store page and enable HMAC encryption under your chat provider.
+2. The next step would be to generate an HMAC encrypted subscriberId on your backend:
+
+ ```jsx
+ import { createHmac } from "crypto";
+
+ const hmacHash = createHmac("sha256", process.env.NOVU_SECRET_KEY)
+ .update(subscriberId)
+ .digest("hex");
+ ```
+
+3. Add the newly created hash HMAC to the Sharable URL as a query.
+
+This concludes the Slack provider guide.
diff --git a/integrations/providers/chat/whats-app.mdx b/integrations/providers/chat/whats-app.mdx
new file mode 100644
index 00000000..542c62d7
--- /dev/null
+++ b/integrations/providers/chat/whats-app.mdx
@@ -0,0 +1,108 @@
+---
+title: "How to send WhatsApp Business notifications with Novu"
+sidebarTitle: "WhatsApp Business"
+description: "Learn about how to use WhatsApp for chat notifications"
+---
+
+## Getting Started
+
+To integrate WhatsApp Business with Novu, You will have to create a facebook developer app and obtain the necessary credentials.
+
+### Step 1: Create a Facebook developer app
+
+Visit the [Facebook Developer Portal](https://developers.facebook.com/apps/) and create a new app.
+
+Select "Other" for "What do you want your app to do?" and select "Business" for "Select an app type".
+
+### Step 2: Setup WhatsApp product
+
+On the App Setup page, click on "Set Up" under the "WhatsApp" product. You will need to create or add a Facebook Business Account to your app.
+
+### Step 3: Send a sandbox message
+
+Copy the following pieces and paste them in the Novu WhatsApp Business integration settings:
+
+- Temporary access token - Access API token field
+- Phone Number ID - Phone number identification field
+
+
+ It's important to note that the test credentials for whatsapp cannot be used
+ in production and will expire in 24 hours. You will need to submit your app
+ for review to obtain production credentials.
+
+
+### Step 4: Add a test phone number
+
+You can add a test phone number to the sandbox by clicking on the **"Add Phone Number"** button.
+This number can be used to test your integration with Novu before submitting for a review.
+
+### Step 5: Send a test notification from Novu
+
+You can now create a new workflow with a "chat" node, and add your content. Save your workflow, and click on **"Trigger Notification"** button.
+
+In the to field, specify the phone number you added in the sandbox, and click on "Send Notification".
+
+```json
+{
+ "subscriberId": "TEST_SUBSCRIBER_ID",
+ "phone": "+11111111111"
+}
+```
+
+and in the `overrides` field, add the following:
+
+```json
+{
+ "chat": {
+ "template": {
+ "name": "hello_world",
+ "language": {
+ "code": "en_US"
+ }
+ }
+ }
+}
+```
+
+
+ For test credentials you can only used the built in Whats App Template.
+
+
+## Going to production
+
+### Register a business phone number
+
+To go live you will need to add a real business phone number and submit your app for review.
+Follow the [Facebook Instructions](https://developers.facebook.com/docs/whatsapp/cloud-api/get-started/add-a-phone-number) on how to proceed.
+
+### Generate a permanent access token
+
+Follow the [Facebook Instructions](https://developers.facebook.com/docs/whatsapp/business-management-api/get-started/) on how to generate a permanent access token.
+Depending on your use case.
+
+### Create a WhatsApp template
+
+You will need to create a WhatsApp Template to send notifications to your customers. Create a template in the [Business Manager](https://business.facebook.com/wa/manage/message-templates) and submit it for review.
+After your template is approved, you can use the `template_name` to send notifications to your customers.
+
+To send a notification with a template, you will need to add the following to the `overrides` field:
+
+```typescript
+novu.trigger("workflow-id", {
+ to: {
+ subscriberId: "SUBSCRIBER_ID",
+ phone: "+11111111111",
+ },
+ payload: {},
+ overrides: {
+ chat: {
+ template: {
+ name: "template_name",
+ language: {
+ code: "en_US",
+ },
+ },
+ },
+ },
+});
+```
diff --git a/integrations/providers/chat/zulip.mdx b/integrations/providers/chat/zulip.mdx
new file mode 100644
index 00000000..ed9fae29
--- /dev/null
+++ b/integrations/providers/chat/zulip.mdx
@@ -0,0 +1,86 @@
+---
+title: "Zulip"
+description: "Learn about how to use Zulip provider for chat notifications"
+---
+
+Zulip does not need any API Key or client ID to push messages in it. All it needs is the webhook URL of the channel you want to send messages to. That itself acts as the credential.
+
+Similar to Discord, the credential for this provider needs to be stored in the subscriber entity.
+
+## Getting a Zulip webhook URL
+
+- Sign up or Login to your Zulip account.
+
+- Click on the Settings icon in the top right corner of the screen, and then click "Personal settings" from the drop-down menu.
+
+
+
+
+
+- Click "Add a new bot" button in "Bots" tab.
+
+
+ {" "}
+
+
+- Set bot type as "Incoming webhook".
+
+
+ {" "}
+
+
+- Click the small link icon to generate webhook URL for provider. Set Integration as `Slack compatible webhook`, choose your channel and copy webhook URL.
+
+
+ {" "}
+
+
+## Connecting our subscribers to Zulip
+
+The URL generated above will be the target channel of a subscriber that you want to integrate with. To make this connection, you have to:
+
+1. Copy the URL that you generated above
+
+2. Update the subscriber credentials with this URL using the Zulip provider id. You can do this step by using the `@novu/node` library, as shown below:
+
+## Update credential webhookUrl
+
+
+
+```javaScript
+import {
+ Novu,
+ ChatProviderIdEnum
+} from '@novu/node';
+
+const novu = new Novu("");
+
+await novu.subscribers.setCredentials('subscriberId', ChatProviderIdEnum.Zulip, {
+webhookUrl: "",
+});
+
+```
+
+
+```bash
+curl -L -X PUT 'https://api.novu.co/v1/subscribers//credentials' \
+-H 'Content-Type: application/json' \
+-H 'Accept: application/json' \
+-H 'Authorization: ApiKey ' \
+-d '{
+ "providerId": "zulip",
+ "credentials": {
+ "webhookUrl": ""
+ },
+ "integrationIdentifier": "zulip-MnGLxp8uy"
+}'
+```
+
+
+
+
+- `subscriberId` is a custom identifier used when identifying your users within the Novu platform.
+- `providerId` is a unique provider identifier. We recommend using our ChatProviderIdEnum to specify the provider.
+- The third parameter is the credentials object, in this case, we use the `webhookUrl` property to specify the MS Teams channel webhook URL or by calling the `Update Subscriber Credentials` endpoint on Novu API. Check endpoint details [here](/api-reference/subscribers/update-subscriber-preference).
+
+3. You are all set up and ready to send your first chat message via our `@novu/node` package or directly using the REST API.
diff --git a/integrations/providers/default-providers.mdx b/integrations/providers/default-providers.mdx
new file mode 100644
index 00000000..dacc0e18
--- /dev/null
+++ b/integrations/providers/default-providers.mdx
@@ -0,0 +1,55 @@
+---
+title: "Novu Providers"
+description: "Learn about the default Novu email and sms providers"
+---
+
+import CloudOnlyFeature from "/snippets/cloud-only-feature.mdx";
+
+## Novu Providers
+
+To help you evaluate our services better, Novu provides an email and sms provider by default for every account. After signing up, you can go to the [Integrations store](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-default-providers) on the Novu web dashboard to see this.
+
+
+
+## Novu Email Provider
+
+If you've a newly signed-up account on Novu, it will look like this:
+
+
+ {" "}
+
+
+## Novu SMS Provider
+
+And this is what the default Novu SMS provider looks like:
+
+
+ {" "}
+
+
+
+ The default email and sms providers are for evaluation purposes only and their
+ use in production-grade apps is not recommended. You should switch to any of
+ our [numerous other providers](/additional-resources/glossary#3-providers) for
+ production apps. Also, there are two modes for default Novu providers -
+ Development and Production.
+
+
+## Novu In-app Provider
+
+In addition to email and sms, we also have in-app provider:
+
+
+ {" "}
+
+
+It is the only provider for the in-app channel and it _can be used in production apps_. Unlike sms and email, it is not active by default and needs to be turned on separately. It can be scaled as per your need and it is a 'pay as you go' offering. You can check [this](https://novu.co/pricing/?utm_campaign=docs-default-providers) for more details.
+
+## Limits of the Novu Providers
+
+Following are the limits for our email and sms providers:
+
+1. Email: 300 emails per organization per month
+2. SMS: 20 messages per organization per month
+
+To send more than these limits, you can configure a different provider for a specific channel.
diff --git a/integrations/providers/email/adding-email.mdx b/integrations/providers/email/adding-email.mdx
new file mode 100644
index 00000000..322d00cf
--- /dev/null
+++ b/integrations/providers/email/adding-email.mdx
@@ -0,0 +1,55 @@
+---
+title: "Adding Email Channel"
+sidebarTitle: "Adding Email Channel"
+description: "Learn how to add the Email channel to your application"
+icon: "circle-plus"
+---
+
+The Email channel enables you to send email notifications to users for events like password resets, onboarding, or system alerts.
+
+
+
+
+
+By default, the Email channel is enabled and cofigured with Novu's default provider. If it is disabled, notifications sent to this channel will not be processed.
+
+
+1. Go to the Novu Dashboard and click **"Integrations"** on the left sidebar
+2. Click **"Add a provider"**
+3. Locate the **Email** channel and select the provider you want to use and click **"Next"**
+4. Select for which environment you want to add the Provider
+5. (Optional) Add Conditions to activate the provider only under certain conditions, **useful for tenant-based providers**
+6. Click **"Create"**
+7. Add your Email provider credentials:
+ - **From**: Displayed as the sender of the email (ensure compliance with local regulations)
+ - **Sender Name**: The name that will be used to send the email
+ - Provider-specific credentials such as API key / Auth token, Account SID, username, or password
+8. Save the configuration by clicking **"Update"**
+
+
+
+
+
+
+ 1. Go to the Novu Dashboard and click **"Workflows"** on the left sidebar.
+ 2. Click the **"Add a Workflow"** button.
+ 3. Add a step and select **"Email"** as the channel.
+ 4. Configure the email content, such as subject, message body, and any dynamic variables.
+
+
+
+
+
+Novu’s server-side SDKs make integrating Novu’s REST APIs straightforward, letting you focus on implementing workflows without dealing with repetitive code.
+
+
+
+
+
+Ensure your configuration is working by sending a test notification.
+
+ 1. Go to the Novu Dashboard, navigate to the "Workflows" section, and locate your configured workflow.
+ 2. Click **"Test Workflow"** and provide sample data (e.g., user ID, email address).
+ 3. Verify the email delivery in the Novu Logs or your email provider dashboard.
+
+
\ No newline at end of file
diff --git a/integrations/providers/email/amazon-ses.mdx b/integrations/providers/email/amazon-ses.mdx
new file mode 100644
index 00000000..5fe4794d
--- /dev/null
+++ b/integrations/providers/email/amazon-ses.mdx
@@ -0,0 +1,50 @@
+---
+title: "Amazon SES"
+description: "Learn how to use the Amazon SES provider to send email notifications using Novu"
+---
+
+You can use the [Amazon SES](https://aws.amazon.com/ses/) provider to send transactional emails to your customers using the Novu Platform with a single API to create multi-channel experiences.
+
+## Getting Started
+
+To use the Amazon SES provider in the email channel, you will need to create an SES account and add your credentials to the Amazon SES integration on the Novu platform.
+
+## Setting up SES in AWS?
+
+- Either use a root AWS account or create a new IAM account with appropriate permission policies.
+ Example policy rule `arn:aws:ses:::identity/*`
+- Create a new access key and save generated `ACCESS_KEY_ID` and `ACCESS_SECRET_KEY` carefully
+- Choose Amazon Simple Email Service.
+- Create a new identity.
+- Either choose domain or email.
+- Verify your domain (by adding a few DNS records as mentioned in SES instructions) or email (AWS sends a verification email to your email).
+- Verify the recipient email also by creating a new email identity type [only in sandbox mode].
+- Test if your SES is set up correctly using the test email feature.
+
+## Creating an SES integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-integrations-ses) page on Novu.
+- Click on Add a Provider.
+- Select Amazon SES service.
+- Enter previously saved `ACCESS_KEY_ID` and `ACCESS_SECRET_KEY`.
+- Fill in the `From email address` field using the authenticated sender email id in the previous step.
+- Enter `region` and `Sender name` also.
+
+
+ Example region format:- `us-east-1`. By default Novu uses `us-east-1` region.
+
+
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Update** button.
+- You should now be able to send notifications using Amazon SES in Novu.
+
+## FAQs
+
+
+
+ Possible reasons: 1. You have not verified the subscriber's email address in
+ SES (if you are in a sandbox environment). 2. Your daily sending limit has
+ been reached (if you are in a sandbox environment). 3. You have entered the
+ wrong aws region in the integration form.
+
+
diff --git a/integrations/providers/email/braze.mdx b/integrations/providers/email/braze.mdx
new file mode 100644
index 00000000..7a789873
--- /dev/null
+++ b/integrations/providers/email/braze.mdx
@@ -0,0 +1,34 @@
+---
+title: "Braze"
+description: "Learn how to use the Braze provider to send email notifications using Novu"
+---
+
+You can use the [Braze](https://braze.com/) provider to send transactional emails to your customers using the Novu Platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+To use the Braze provider in the email channel, you will need to create a [Braze account](https://braze.com/) and add your API key and other credentials to the Braze integration on the Novu platform.
+
+# Generating an API Key
+
+To generate a new API key in Braze, you can follow these steps:
+
+- [Sign up](https://braze.com) or [Log in](https://braze.com) to your Braze account.
+- Go to **Settings > API Keys**.
+- Click **+ Create New API Key**.
+- Give your new key a name for identification at a glance.
+- Select the right permission you want to be associated with the new API key.
+
+# Creating a Braze integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-braze) page on Novu.
+- Click on `Add a Provider`.
+- Locate **Braze** under the Email section and click on the **Next** button.
+- Select Your Environment add condition (Optional).
+- Click on the `Create` button.
+- Click on the `Disabled` button and mark it as `Active`.
+- Enter the `API Key`.
+- Enter the `Base URL`.
+- Enter the `From email address`.
+- Enter the `Sender name`.
+- Click on the **Update** button.
diff --git a/integrations/providers/email/custom-smtp.mdx b/integrations/providers/email/custom-smtp.mdx
new file mode 100644
index 00000000..931e46e1
--- /dev/null
+++ b/integrations/providers/email/custom-smtp.mdx
@@ -0,0 +1,39 @@
+---
+title: "Custom SMTP"
+description: "Learn how to use the Custom SMTP provider to send email notifications using Novu"
+---
+
+You can use a Custom SMTP provider like [Nodemailer](https://nodemailer.com/about/) to send transactional emails through your custom SMTP server to your customers using the Novu Platform with a single API.
+
+# Getting Started
+
+To use the Custom SMTP provider in the email channel, you will need to have your personal SMTP server configured and add `host`, `port`, `user`, and `password` to the Custom SMTP integration on the Novu platform.
+
+You can also provide value **`true`** for the `secure` field if you want the connection to be secure, and if not, leave it empty.
+
+### DKIM (DomainKeys Identified Mail)
+
+DKIM options can be used in order to sign messages sent using Custom SMTP with DKIM keys.
+
+Those options are:
+
+- `DKIM Domain`
+- `DKIM Private Key`
+- `DKIM Key Selector`
+
+# Creating a Custom SMTP integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-customsmtp) page on Novu.
+- Click on Add a Provider.
+- Select Custom SMTP service.
+- Enter your SMTP credentials
+ - `host`
+ - `port`
+ - `username`
+ - `password`
+ - `secure` (on demand)
+ - And `DKIM` options if you want to sign messages with *DKIM*
+- Fill in the `From email address` field using the authenticated email from the previous step.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Update** button.
+- You should now be able to send notifications using Custom SMTP in Novu.
diff --git a/integrations/providers/email/infobip.mdx b/integrations/providers/email/infobip.mdx
new file mode 100644
index 00000000..00948d28
--- /dev/null
+++ b/integrations/providers/email/infobip.mdx
@@ -0,0 +1,31 @@
+---
+title: "Infobip - email"
+description: "Learn how to use the Infobip provider to send email notifications using Novu"
+---
+
+You can use the [Infobip](https://www.infobip.com/developers/) provider to send transactional emails to your customers using the Novu Platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+To use the Infobip provider in the email channel, you will need to create an Infobip account and add your API key and Base URL to the Infobip integration on the Novu platform.
+
+# Generating an API Key
+
+To generate a new API key in Infobip, you can follow these steps:
+
+- [Sign up](https://www.infobip.com/signup) or [Log in](https://portal.infobip.com/login/) to your Infobip account.
+- Navigate to the [Settings](https://portal.infobip.com/settings/accounts/api-keys) of your account and look for the API Keys section at the top of the settings page.
+
+# Creating an Infobip integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-infobip) page on Novu.
+- Click on Add a Provider.
+- Select Infobip service.
+- Enter your Infobip API Key.
+- Enter your Base URL.
+ - To see your base URL, log in to the account. Once logged in, on all pages, you should see your base URL in this format: `xxxxx.api.infobip.com`.
+- Fill in the `From email address` field using the authenticated email from the previous step.
+- Fill in the `Sender's name`.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Update** button.
+- You should now be able to send notifications using Infobip in Novu.
diff --git a/integrations/providers/email/mailersend.mdx b/integrations/providers/email/mailersend.mdx
new file mode 100644
index 00000000..40f550ed
--- /dev/null
+++ b/integrations/providers/email/mailersend.mdx
@@ -0,0 +1,133 @@
+---
+title: "Mailersend"
+description: "Learn how to use the MailerSend provider to send email notifications using Novu"
+---
+
+[MailerSend](https://www.mailersend.com/) is an email delivery service that allows you to send emails from your application.
+
+## Getting Started
+
+To use the MailerSend provider in the email channel, you will need to create a MailerSend account and add your API key to the MailerSend integration on the Novu platform. To generate the API token go visit the [MailerSend API Tokens](https://www.mailersend.com/help/managing-api-tokens) page.
+
+## Creating the MailerSend integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-mailersend) page on Novu.
+- Click on Add a Provider.
+- Select MailerSend service.
+- Enter the API key.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Update** button.
+- You should now be able to send notifications using MailerSend in Novu.
+
+## Using MailerSend template
+
+Novu has its own email editor for writing email template. If you want to use pre made template from MailerSend, you can use `customData` filed of email overrides to send template details. Make sure your `Api Key` has enough permission to read and process the template.
+
+
+
+```jsx
+import {
+ Novu
+} from '@novu/node';
+
+const novu = new Novu('');
+
+await novu.subscribers.trigger("workflowIdentifier", {
+to: "subscriberId",
+payload: {},
+overrides: {
+email: {
+customData: {
+// mailersend template templateId
+templateId: 'mailersend-template-id',
+// mailersend template variables
+personalization: [{
+email: 'recipient@email.com',
+data: {
+items: {
+price: '',
+product: '',
+quantity: '',
+},
+order: {
+date: '',
+order_number: '',
+billing_address: '',
+customer_message: '',
+},
+store: {
+name: '',
+},
+invoice: {
+total: '',
+subtotal: '',
+pay_method: '',
+},
+customer: {
+name: '',
+email: '',
+phone: '',
+},
+},
+}, ],
+},
+}
+},
+// actorId is subscriberId of actor
+actor: "actorId"
+tenant: "tenantIdentifier"
+});
+
+```
+
+
+```bash
+curl --location 'https://api.novu.co/v1/events/trigger' \
+--header 'Content-Type: application/json' \
+--header 'Accept: application/json' \
+--header 'Authorization: ApiKey ' \
+--data '{
+ "name": "workflowIdentifier",
+ "to": ["subscriberId"],
+ "payload": {},
+ "overrides": {
+ "email": {
+ "customData": {
+ "templateId": "mailersend-template-id",
+ "personalization": [{
+ "email": "recipient@email.com",
+ "data": {
+ "items": {
+ "price": "",
+ "product": "",
+ "quantity": ""
+ },
+ "order": {
+ "date": "",
+ "order_number": "",
+ "billing_address": "",
+ "customer_message": ""
+ },
+ "store": {
+ "name": ""
+ },
+ "invoice": {
+ "total": "",
+ "subtotal": "",
+ "pay_method": ""
+ },
+ "customer": {
+ "name": "",
+ "email": "",
+ "phone": ""
+ }
+ }
+ }]
+ }
+ }
+ }
+}'
+```
+
+
+
diff --git a/integrations/providers/email/mailgun.mdx b/integrations/providers/email/mailgun.mdx
new file mode 100644
index 00000000..d063858c
--- /dev/null
+++ b/integrations/providers/email/mailgun.mdx
@@ -0,0 +1,43 @@
+---
+title: "Mailgun"
+description: "Learn how to use the Mailgun provider to send email notifications using Novu"
+---
+
+You can use the [Mailgun](https://mailgun.com/) provider to send transactional emails to your customers using the Novu Platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+To use the Mailgun provider in the email channel, you will need to create a Mailgun account and add your API key and domain name to the Mailgun integration on the Novu platform.
+
+# Generating an API Key
+
+To generate a new API key in Mailgun, you can follow these steps:
+
+- [Sign up](https://signup.mailgun.com/new/signup) or [Log in](https://login.mailgun.com/login/) to your Mailgun account.
+- Click on the **Profile** section in the top right corner of the screen, and then click "API Keys" from the drop-down menu.
+- On the [API Keys](https://app.mailgun.com/app/account/security/api_keys) page, copy the generated **Private API Key**
+
+# Adding a new domain name
+
+Mailgun recommends that you add a subdomain as a domain name. To do so:
+
+- Visit the page to add a [domain name](https://app.mailgun.com/app/sending/domains/new).
+ - During this process, you will need to choose a region for the domain name which is between `US` and `EU`. The default is `US`.
+- Follow the [instructions](https://documentation.mailgun.com/en/latest/user_manual.html#verifying-your-domain-1) to verify the domain name.
+
+# Creating a Mailgun integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-mailgun) page on Novu.
+- Click on Add a Provider.
+- Select Mailgun service.
+- Enter your Mailgun API Key.
+- Enter your Base URL.
+ - For domains created in the EU region, the base URL is: `https://api.eu.mailgun.net/`
+ - Otherwise, leave the base URL blank.
+- Fill in the `Username`.
+- Fill in the `Domain name` registered on Mailgun.
+- Fill in the `From email address` field using the authenticated email from the previous step.
+- Fill in the `Sender's name`.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Update** button.
+- You should now be able to send notifications using Mailgun in Novu.
diff --git a/integrations/providers/email/mailjet.mdx b/integrations/providers/email/mailjet.mdx
new file mode 100644
index 00000000..ae143b70
--- /dev/null
+++ b/integrations/providers/email/mailjet.mdx
@@ -0,0 +1,40 @@
+---
+title: "Mailjet"
+description: "Learn how to use the Mailjet provider to send email notifications using Novu"
+---
+
+You can use the [Mailjet](https://mailjet.com/) provider to send transactional emails to your customers using the Novu Platform with a single API.
+
+# Getting Started
+
+To use the Mailjet provider in the email channel, you will need to create a Mailjet account and add your API key to the Mailjet integration on the Novu platform.
+
+# Generating an API Key
+
+To generate a new API key in Mailjet, you can follow these steps:
+
+- Log in to your Mailjet account.
+- Click on **Settings** in the top-right corner of the screen, and then click **API KEYS & TRACKING** from the drop-down menu.
+- On the API Keys page, click the **Create an API Key** button.
+- Give the API key a name and choose the access level **Write and Read**
+- Click the **Generate Key** button to create the new key. Once generated you can see the key but it will be hidden after the refresh
+
+# Authenticating your sender identity
+
+Before you can send emails on a large scale, you will need to authenticate your Sender identity. This is due to the latest regulatory changes regarding SPAM rules and email fraud. Most of the providers including Mailjet require you to authenticate your Sender identity before you can send emails.
+
+Mailjet allows you to authenticate your sender identity using one of the following methods:
+
+- [Single Sender Verification](https://dev.mailjet.com/email/guides/senders-and-domains/#sender-validation) - This is the easiest way to authenticate your sender identity.
+- [Entire Domain Authentication](https://dev.mailjet.com/email/guides/senders-and-domains/#spf-and-dkim-validation) - This is recommended if you are sending emails from multiple accounts under your domain.
+
+# Creating a Mailjet integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-mailjet) page on Novu.
+- Click on Add a Provider.
+- Select Mailjet service.
+- Enter your Mailjet API key.
+- Fill in the `From email address` field using the authenticated email from the previous step.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Update** button.
+- You should now be able to send notifications using Mailjet in Novu.
diff --git a/integrations/providers/email/mailtrap.mdx b/integrations/providers/email/mailtrap.mdx
new file mode 100644
index 00000000..aa2881df
--- /dev/null
+++ b/integrations/providers/email/mailtrap.mdx
@@ -0,0 +1,34 @@
+---
+title: "Mailtrap"
+description: "Learn how to use the Mailtrap provider to send email notifications using Novu"
+---
+
+You can use the [Mailtrap](https://mailtrap.io/email-sending/) provider to send transactional emails to your customers using the Novu Platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+To use the Mailtrap provider in the email channel, you will need to create a Mailtrap account and add your API key to the Mailtrap integration on the Novu platform.
+
+# Generating an API Key
+
+To generate a new API key in Mailtrap, you can follow these steps:
+
+- [Sign Up](https://mailtrap.io/register/signup) or [Log in](https://mailtrap.io/signin) to your Mailtrap account.
+- Click on the **Email Sending** link on the sidebar, and then click the "Sending Domains" link that pops up from the available options.
+- On the [Sending Domains](https://mailtrap.io/sending/domains) page, type your domain name and confirm with the `Add Your Domain` button. Then, proceed to copy DNS records Mailtrap provides to your domain’s DNS.
+- Go to [API Keys](https://mailtrap.io/api-tokens) page and copy token with `Domain Admin` access level for your registered domain.
+
+# Authenticating your Sender Identity
+
+Before you can send emails, you will need to [verify your sending domain ownership](https://help.mailtrap.io/article/69-sending-domain-setup). Mailtrap rejects sending emails from unverified domains to prevent spam and email fraud.
+
+# Creating a Mailtrap integration with Novu
+
+- Visit the [Integrations store](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-mailtrap) on the Novu web dashboard.
+- Click on Add a Provider.
+- Select Mailtrap service.
+- Enter your Mailtrap API Key.
+- Fill in the `From email address` field using the authenticated email from the previous step.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Update** button.
+- You should now be able to send notifications through Mailtrap using Novu.
diff --git a/integrations/providers/email/mandrill.mdx b/integrations/providers/email/mandrill.mdx
new file mode 100644
index 00000000..28babd96
--- /dev/null
+++ b/integrations/providers/email/mandrill.mdx
@@ -0,0 +1,38 @@
+---
+title: "Mandrill"
+description: "Learn how to use the Mandrill provider to send email notifications using Novu"
+---
+
+You can use the [Mandrill by Mailchimp](https://mandrillapp.com/) provider to send transactional emails to your customers using the Novu Platform with a single API.
+
+# Getting Started
+
+To use the Mandrill provider in the email channel, you will need to create a Mandrill account and add your API key to the Mandrill integration on the Novu platform.
+
+# Generating an API Key
+
+To generate a new API key in Mandrill, you can follow these steps:
+
+- [Sign up](https://login.mailchimp.com/signup/) or [Log in](https://login.mailchimp.com/) to your Mandrill account.
+- Navigate to the [Settings](https://mandrillapp.com/settings) of your account and look for the API Keys section at the bottom of the settings page.
+- Click on the **+ Add API Key** button to create an API key. Copy the generated key immediately and store it in a secure location. You won’t be able to see or copy the key once you finish generating it.
+
+# Adding a sending domain
+
+To get started, you’ll need to add the domain that you want to send messages from.
+
+- Navigate to the [Settings page](https://mandrillapp.com/settings/sending-domains) and choose Domains
+- Type a new domain in the domain input and click Add
+- Follow the instructions to [verify ownership](https://mailchimp.com/developer/transactional/docs/authentication-delivery/#authentication) of your sending domain and [update your DNS records](https://mailchimp.com/developer/transactional/docs/authentication-delivery/#configure-your-dns).
+
+# Creating the Mandrill integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-mandrill) page on Novu.
+- Click on Add a Provider.
+- Select Mandrill service.
+- Enter your Mandrill API key.
+- Fill in the `From email address` field using the authenticated email from the previous step.
+- Fill in the `Sender's name`.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Update** button.
+- You should now be able to send notifications using Mandrill in Novu.
diff --git a/integrations/providers/email/maqsam.mdx b/integrations/providers/email/maqsam.mdx
new file mode 100644
index 00000000..2778394c
--- /dev/null
+++ b/integrations/providers/email/maqsam.mdx
@@ -0,0 +1,22 @@
+---
+title: "Maqsam"
+description: "Learn how to use the Maqsam provider to send sms notifications using Novu"
+---
+
+You can use the [Maqsam](https://maqsam.com/) provider to send SMS messages to your customers using the Novu platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+To use the Maqsam provider in the sms channel, you will need to create a Maqsam account and add your access key & secret to the Maqsam integration on the Novu platform.
+Contact `support@maqsam.com` to enable the API feature for your account, then you can generate `access_key_id` and `access_secret` combination from the account settings.
+
+# Creating the Maqsam integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-maqsam) page on Novu.
+- Click the "Add a provider" button.
+- Select Maqsam service
+- Click `Next`
+- Choose your preferred deployment environment: `Development` or `Production`. Then Click the `Create` button.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Update** button.
+- You should now be able to send notifications using Maqsam in Novu.
diff --git a/integrations/providers/email/netcore.mdx b/integrations/providers/email/netcore.mdx
new file mode 100644
index 00000000..cd2d9ca6
--- /dev/null
+++ b/integrations/providers/email/netcore.mdx
@@ -0,0 +1,36 @@
+---
+title: "Netcore"
+description: "Learn how to use the Netcore provider to send email notifications using Novu"
+---
+
+You can use the [Netcore](https://netcorecloud.com/) provider to send transactional emails to your customers using the Novu Platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+To use the Netcore provider in the email channel, you will need to create a Netcore account and add your API key to the Netcore integration on the Novu platform.
+
+# Generating an API Key
+
+To generate a new API key in Netcore, you can follow these steps:
+
+- [Log in](https://email.netcorecloud.com/) to your Netcore account.
+- Go to the **Integration** page under the **Settings** menu and click on the **API** tab.
+- The API Key is hidden for security purposes. Click Show. The system will prompt you to enter your account password. Once you enter the password, the API key will be accessible.
+
+# Setting up sending domains
+
+To start sending emails, you need to add and verify your sending domains. You can either use your top-level domain (e.g. my-company.com) or a sub-domain like email.my-company.com. The verification is done to ensure your sending domain’s security.
+
+Follow the instructions on this [page](https://emaildocs.netcorecloud.com/docs/what-is-a-sending-domain-how-to-set-up-sending-domains) to get started
+
+# Creating a Netcore integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-netcore) page on Novu.
+- Click on Add a Provider.
+- Select Netcore service.
+- Enter your Netcore API Key.
+- Fill in the `From email address` field using the authenticated email from the previous step.
+- Fill in the `Sender's name`.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Update** button.
+- You should now be able to send notifications using Netcore in Novu.
diff --git a/integrations/providers/email/outlook365.mdx b/integrations/providers/email/outlook365.mdx
new file mode 100644
index 00000000..090cc761
--- /dev/null
+++ b/integrations/providers/email/outlook365.mdx
@@ -0,0 +1,29 @@
+---
+title: "Outlook 365"
+description: "Learn how to use the Outlook 365 provider to send email notifications using Novu"
+---
+
+You can use the [Outlook 365](https://office.com/) provider to send transactional emails through your instance of Office 365 to your customers using the Novu Platform with a single API.
+
+## Getting Started
+
+To use the Outlook 365 provider in the email channel, you will need to have the sender's email (user) and the password for the account. This account cannot be a shared mailbox or distribution list. It will need to be properly licensed to send email via Office 365.
+
+## Creating the Outlook 365 integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-office365) page on Novu.
+- Click on Add a Provider.
+- Select Outlook service.
+- Enter your SMTP credentials
+ - `from`: The Complete email address of the sending user. (e.g. [jdoe@mycompany.com](mailto:jdoe@mycompany.com))
+ - `senderName`: Sender Name should be the same email address of the sending user. (e.g. [jdoe@mycompany.com](mailto:jdoe@mycompany.com))
+ - `password`: Password used to sign in with the email account.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Update** button.
+- You should now be able to send notifications using Outlook 365 in Novu.
+
+
+ {" "}
+ In order to create outlook integration, turn off multi factor authentication from
+ account security settings.
+
diff --git a/integrations/providers/email/overview.mdx b/integrations/providers/email/overview.mdx
new file mode 100644
index 00000000..f296c769
--- /dev/null
+++ b/integrations/providers/email/overview.mdx
@@ -0,0 +1,179 @@
+---
+title: "Email Providers Integration"
+sidebarTitle: "Overview"
+description: "Learn how to configure the Email channel"
+icon: "circle-info"
+---
+
+The Email Channel is a critical component for delivering notifications reliably. Whether it's a password reset, an onboarding email, or an alert about account activity, email remains a trusted medium for reaching users.
+Novu simplifies this process, allowing you to focus on implementation rather than infrastructure.
+
+## Key Features
+
+- **Multi-Provider Support**: Integrate any major provider like SendGrid, SES, or Mailgun.
+- **Failover Mechanisms**: Automatically retry with a backup provider to ensure reliability.
+- **Customizable Templates**: Leverage templates with dynamic placeholders to personalize messages.
+- **Delivery Insights (Coming Soon)**: Track delivery status, open rates, and more in the Novu dashboard.
+
+## Common Use Cases
+
+- **Transactional Emails**: Password resets, account verification, purchase confirmations
+- **System Alerts**: Security notifications, system updates
+- **Engagement Emails**: Onboarding, reminders, promotional updates
+
+
+
+import { MissingProvider } from "/snippets/missing-provider.mdx";
+
+Novu can be used to deliver email messages to your subscribers using a unified delivery API. You can easily integrate your favorite email provider using the built-in integration store.
+
+## Configuring email providers
+
+When creating an email provider integration you will be asked to provide additional fields alongside the provider-specific credentials:
+
+- **Sender name** - Will be displayed as the sender of the message
+- **From email address** - Emails sent using Novu will be sent using this address
+
+For some email providers including SendGrid you will have to authenticate the **From email address** to make sure you will send email messages using an authorized address.
+
+## Sending Email Overrides
+
+The overrides field supports an email property. The email overrides field have properties like `to`, `from`, `senderName` etc
+
+
+
+```javascript
+import { Novu } from '@novu/node';
+
+const novu = new Novu('');
+
+novu.trigger('', {
+ to: {
+ subscriberId: '',
+ },
+ overrides: {
+ email: {
+ to: ['to@novu.co'],
+ from: 'from@novu.co',
+ senderName: 'Novu Team',
+ text: 'text version of email using overrides',
+ replyTo: 'no-reply@novu.co',
+ cc: ['1@novu.co'],
+ bcc: ['2@novu.co'],
+ },
+ },
+});
+```
+
+
+
+It's very important to know that Novu merges the `to` field in the email overrides with the subscriber email. It DOES NOT REPLACE IT.
+
+## Sending Email attachments
+
+You can easily send attachments with the Novu API by passing the attachments array when triggering an Email based workflow. Attachment file can either be in the `buffer` or `base64` format.
+
+
+
+
+```javascript
+import { Novu } from '@novu/node';
+
+const novu = new Novu('');
+
+novu.trigger('', {
+ to: {
+ subscriberId: '',
+ },
+ payload: {
+ attachments: [
+ {
+ // buffer format
+ file: fs.readFileSync(__dirname + '/data/novu.jpeg'),
+ name: 'novu.jpeg',
+ mime: 'image/jpeg',
+ },
+ {
+ // base64 format
+ file: 'iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFUlEQVR42mNkYPhfz0AEYBxVSF+FAP5FDvcfRYWgAAAAAElFTkSuQmCC',
+ name: 'blue.png',
+ mime: 'image/png',
+ }
+ ],
+ },
+});
+```
+
+
+ Use https://eu.api.novu.co/v1/events/trigger api endpoint for the EU region.
+```bash
+curl -L -X POST 'https://api.novu.co/v1/events/trigger' \
+-H 'Content-Type: application/json' \
+-H 'Accept: application/json' \
+-H 'Authorization: ApiKey ' \
+--data-raw '{
+ "name": "workflow_trigger_identifier",
+ "to": [
+ {
+ "subscriberId": "subscriber_id",
+ "email": "email_address"
+ }
+ ],
+ "payload": {
+ "attachments": [
+ {
+ "file": "iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII",
+ "name": "transparent.png",
+ "mime": "image/png"
+ },
+ {
+ "file": "iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFUlEQVR42mNkYPhfz0AEYBxVSF+FAP5FDvcfRYWgAAAAAElFTkSuQmCC",
+ "name": "blue.png",
+ "mime": "image/png"
+ }
+ ]
+ }
+}'
+```
+
+
+
+## Using different email integration
+
+In Novu integration store, multiple email channel type provider integrations can be active at the same time. But only one provider integration can be primary at a time. This primary integration will be used as a provider to deliver the email by default. If you want to use a different active provider integration then you can use the `integrationIdentifier` email overrides field.
+
+If there are 4 active email integrations with these identifiers:-
+
+1. sendgrid-abcdef
+2. sendgrid-ghijkl
+3. brevo-abcdef
+4. mailersend-abcdef
+
+Here, if `sendgrid-abcdef` is primary integration and you want to use `brevo-abcdef` with this trigger then you can use `integrationIdentifier` email overrides field as below:-
+
+
+
+```javascript
+import { Novu } from '@novu/node';
+
+const novu = new Novu('');
+
+novu.trigger('', {
+ to: {
+ subscriberId: '',
+ },
+ overrides: {
+ email: {
+ integrationIdentifier: "brevo-abcdef"
+ },
+ },
+});
+```
+
+
+
+ Integration identifier is similar to Provider identifier but it is different than Provider Id. It is unique for each integration.You can find the `integrationIdentifier` in the integration store page.
+
+
+
+
diff --git a/integrations/providers/email/plunk.mdx b/integrations/providers/email/plunk.mdx
new file mode 100644
index 00000000..e4bc116d
--- /dev/null
+++ b/integrations/providers/email/plunk.mdx
@@ -0,0 +1,33 @@
+---
+title: "Plunk"
+description: "Learn how to use the Plunk provider to send email notifications using Novu"
+---
+
+You can use the [Plunk](https://useplunk.com/) provider to send transactional emails to your customers using the Novu Platform with a single API to create multi-channel experiences.
+
+## Getting Started
+
+To use the Plunk provider in the email channel, you will need to create a Plunk account and add your API key to the Plunk integration on the Novu platform.
+
+## Get API Key
+
+To generate a new API key in Plunk, you can follow these steps:
+
+- Log in to your Plunk account.
+- Click on `Project Settings` on the side bar and then `API keys` on the tab.
+- On the API Keys page, click on `Secret API key` to copy.
+
+## Authenticate your sender identity
+
+Before you can send emails on a large scale, you will need to authenticate your Sender Identity. Plunk allows you to authenticate your sender identity using [Domain Authentication](https://app.useplunk.com/settings/identity)
+
+## Create a Plunk integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-plunk) page on Novu.
+- Click on Add a Provider.
+- Select Plunk service.
+- Enter your Plunk secret API Key.
+- Fill the `From email address` field using the authenticated email from the previous step.
+- Click on the `Disabled` button and mark as `Active`.
+- Click on the **Update** button.
+- You should now be able to send notifications using Plunk in Novu.
diff --git a/integrations/providers/email/postmark.mdx b/integrations/providers/email/postmark.mdx
new file mode 100644
index 00000000..7022ed0d
--- /dev/null
+++ b/integrations/providers/email/postmark.mdx
@@ -0,0 +1,35 @@
+---
+title: "Postmark"
+description: "Learn how to use the Postmark provider to send email notifications using Novu"
+---
+
+It is possible to use [Postmark](https://postmarkapp.com/) as a provider to send transactional emails to your customers using the Novu Platform with a single API.
+
+# Getting Started
+
+The first step to use the Postmark provider in the email channel is to create a Postmark account and add the personal API key to the Postmark integration on the Novu platform.
+
+# Getting the API Key
+
+- To find the Postmark API key, log into the personal Postmark account and navigate to the servers page.
+- After selecting the server to use, the API key (referred to as "Server API tokens") will be in the "API Tokens" section of the server chosen.
+
+# Authenticating the sender's identity
+
+Due to the latest regulatory changes regarding SPAM rules and email fraud, it is needed to authenticate the sender's identity before sending emails on a large scale. Most of the providers, including Postmark, require authentication to unlock the possibility of sending emails.
+
+Postmark allows the authentication of the sender's identity using one of the following methods:
+
+- [Single Sender Verification](https://account.postmarkapp.com/signatures/new) - This is the easiest way to authenticate the sender's identity.
+- [Entire Domain Authentication](https://postmarkapp.com/support/article/1046-how-do-i-verify-a-domain#:~:text=be%20verified%20automatically.-,Navigate%20to%20Sender%20Signatures.,to%20your%20DNS%2C%20choose%20Verify.) - This is recommended for sending emails from multiple accounts under the same domain.
+
+# Create a Postmark integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-postmark) page on Novu.
+- Click on Add a Provider.
+- Select Postmark service.
+- Enter the Postmark API key.
+- Fill in the `From email address` field using the authenticated email from the previous step.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Update** button.
+- Now is possible to send notifications using Postmark in Novu.
diff --git a/integrations/providers/email/resend.mdx b/integrations/providers/email/resend.mdx
new file mode 100644
index 00000000..ebf2bb5d
--- /dev/null
+++ b/integrations/providers/email/resend.mdx
@@ -0,0 +1,39 @@
+---
+title: "Resend"
+description: "Learn how to use the Resend provider to send email notifications using Novu"
+---
+
+You can use the [Resend](https://resend.com/) provider to send transactional emails to your customers using the Novu Platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+To use the Resend provider in the email channel, you will need to create a Resend account and add your API key to the Resend integration on the Novu platform.
+
+# Generating an API Key
+
+To generate a new API key in Resend, you can follow these steps:
+
+- [Sign up](https://resend.com/secret) or [Log in](https://resend.com/login) to your Resend account.
+- Click on the **API Keys** link in the left sidebar, and then click the "Create API Key" button on the top right part of the page.
+- On the [API Keys](https://resend.com/api-keys) page, click the **Create API Key** button.
+- Give the API key a name and click on the **Add** button.
+- Copy the generated API Key.
+
+# Authenticating your Sender Identity
+
+Before you can send emails on a large scale, you will need to authenticate your Sender Identity.
+
+Resend allows you to authenticate your sender identity using [Domain Authentication](https://resend.com/docs/dashboard/domains/introduction).
+
+# Creating a Resend integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-resend) page on Novu.
+- Click on Add a Provider.
+- Select Resend service.
+- Enter your Resend API Key.
+- Fill in the `From email address` field using the authenticated email from the previous step.
+ - For testing, you can use `onboarding@resend.dev` if you have not authenticated your sender identity.
+- Fill in the `Sender's name`.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Update** button.
+- You should now be able to send notifications using Resend in Novu.
diff --git a/integrations/providers/email/sendgrid.mdx b/integrations/providers/email/sendgrid.mdx
new file mode 100644
index 00000000..d2ab6aca
--- /dev/null
+++ b/integrations/providers/email/sendgrid.mdx
@@ -0,0 +1,213 @@
+---
+title: "SendGrid"
+description: "Learn how to use the Sendgrid provider to send email notifications using Novu"
+---
+
+You can use the [SendGrid](https://sendgrid.com/) provider to send transactional emails to your customers using the Novu Platform with a single API to create multi-channel experiences.
+
+### Getting Started
+
+To use the Sendgrid provider in the email channel, you will need to create a Sendgrid account and add your API key to the SendGrid integration on the Novu platform.
+
+### Generating an API Key
+
+To generate a new API key in SendGrid, follow these steps:
+
+- Log in to your SendGrid account.
+- Click on the **Settings** gear icon in the top right corner of the screen, and then click "API Keys" from the drop-down menu.
+- On the API Keys page, click the **Create API Key** button.
+- Give the API key a name and select the following permissions
+- **Mail Send** - Full Access
+- (Optional) Template Engine - Read Only
+- Click the **Create & View** button to generate the API key. The key will be displayed on the screen, but you will only be able to view it once, so make sure to save it in a safe place.
+
+NOTE
+
+The access level of the key will determine what actions the API Key can take, so please choose the correct one.
+
+- **Mail Send** - Full Access
+- (Optional) Template Engine - Read Only
+
+### Authenticating your [Sender Identity](https://docs.sendgrid.com/for-developers/sending-email/sender-identity)
+
+Before you can send emails on a large scale, you will need to authenticate your Sender Identity. This is due to the latest regulatory changes regarding SPAM rules and email fraud. Most of the providers including Sendgrid require you to authenticate your Sender Identity before you can send emails.
+
+SendGrid allows you to authenticate your sender identity using one of the following methods:
+
+- [Single Sender Verification](https://docs.sendgrid.com/ui/sending-email/sender-verification) - This is the easiest way to authenticate your sender identity.
+- [Entire Domain Authentication](https://docs.sendgrid.com/ui/account-and-settings/how-to-set-up-domain-authentication) - This is recommended if you are sending emails from multiple accounts under your domain.
+
+### SendGrid integration with Novu
+
+- Visit the [Integrations store](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sendgrid) on the Novu web dashboard.
+- Click on Add a Provider.
+- Select SendGrid service.
+- Enter your SendGrid API Key.
+- Fill in the `From email address` field using the authenticated email from the previous step.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Update** button.
+- You should now be able to send notifications through SendGrid using Novu.
+
+### Using SendGrid template
+
+Novu has its own email editor for writing email template. To send pre-made template in SendGrid, `customData` filed of email overrides can be used to send template details. Make sure sendgrid `Api Key` has enough permission to read and process the template. This `customData` field can be used to send custom args and extra data in the form of key-value pairs.
+
+customData override is available from version 0.21.0
+
+
+
+```typescript
+import { Novu } from "@novu/node";
+
+const novu = new Novu("");
+
+await novu.subscribers.trigger("workflowIdentifier", {
+ to: "subscriberId",
+ payload: {},
+ overrides: {
+ email: {
+ customData: {
+ // sendgrid template templateId
+ templateId: "sendgrid-template-id",
+ // sendgrid template variables
+ dynamicTemplateData: {
+ total: "$ 239.85",
+ items: [
+ {
+ text: "New Line Sneakers",
+ image:
+ "https://marketing-image-production.s3.amazonaws.com/uploads/8dda1131320a6d978b515cc04ed479df259a458d5d45d58b6b381cae0bf9588113e80ef912f69e8c4cc1ef1a0297e8eefdb7b270064cc046b79a44e21b811802.png",
+ price: "$ 79.95",
+ },
+ {
+ text: "Old Line Sneakers rlfjrjrh4hr4rh4",
+ image:
+ "https://marketing-image-production.s3.amazonaws.com/uploads/3629f54390ead663d4eb7c53702e492de63299d7c5f7239efdc693b09b9b28c82c924225dcd8dcb65732d5ca7b7b753c5f17e056405bbd4596e4e63a96ae5018.png",
+ price: "$ 79.95",
+ },
+ ],
+ receipt: true,
+ name: "Sample Name",
+ address01: "1234 Fake St.",
+ address02: "Apt. 123",
+ city: "Place",
+ state: "CO",
+ zip: "80202",
+ },
+ },
+ },
+ },
+ // actorId is subscriberId of actor
+ actor: "actorId",
+ tenant: "tenantIdentifier",
+});
+```
+
+
+```bash
+curl --location 'https://api.novu.co/v1/events/trigger' \
+--header 'Content-Type: application/json' \
+--header 'Accept: application/json' \
+--header 'Authorization: ApiKey ' \
+--data '{
+ "name": "workflowIdentifier",
+ "to": ["subscriberId"],
+ "payload": {},
+ "overrides": {
+ "email": {
+ "customData": {
+ "templateId": "sendgrid-template-id",
+ "dynamicTemplateData": {
+ "total": "$ 239.85",
+ "items": [
+ {
+ "text": "New Line Sneakers",
+ "image": "https://marketing-image-production.s3.amazonaws.com/uploads/8dda1131320a6d978b515cc04ed479df259a458d5d45d58b6b381cae0bf9588113e80ef912f69e8c4cc1ef1a0297e8eefdb7b270064cc046b79a44e21b811802.png",
+ "price": "$ 79.95"
+ },
+ {
+ "text": "Old Line Sneakers rlfjrjrh4hr4rh4",
+ "image": "https://marketing-image-production.s3.amazonaws.com/uploads/3629f54390ead663d4eb7c53702e492de63299d7c5f7239efdc693b09b9b28c82c924225dcd8dcb65732d5ca7b7b753c5f17e056405bbd4596e4e63a96ae5018.png",
+ "price": "$ 79.95"
+ }
+ ],
+ "receipt": true,
+ "name": "Sample Name",
+ "address01": "1234 Fake St.",
+ "address02": "Apt. 123",
+ "city": "Place",
+ "state": "CO",
+ "zip": "80202"
+ }
+ }
+ }
+ }
+}'
+```
+
+
+```typescript
+import { workflow } from "@novu/framework";
+import { z } from "zod";
+
+export const sendgridOverridesEmailExample = workflow(
+ "sendgrid-email-overrides",
+ async ({ step, payload }) => {
+ await step.email(
+ "sendgrid-email-overrides",
+ async () => {
+ return {
+ subject: "SendGrid Overrides Example",
+ body: "This body content should be overridden by the SendGrid template",
+ };
+ },
+ {
+ providers: {
+ sendgrid: ({}) => ({
+ _passthrough: {
+ body: {
+ templateId: payload.dynamicTemplateId,
+ dynamicTemplateData: {
+ // Example variables to be used in the template
+ total: "$ 239.85",
+ items: [
+ {
+ text: payload.itemName,
+ image:
+ "https://marketing-image-production.s3.amazonaws.com/uploads/8dda1131320a6d978b515cc04ed479df259a458d5d45d58b6b381cae0bf9588113e80ef912f69e8c4cc1ef1a0297e8eefdb7b270064cc046b79a44e21b811802.png",
+ price: "$ 79.95",
+ },
+ {
+ text: "Old Line Sneakers rlfjrjrh4hr4rh4",
+ image:
+ "https://marketing-image-production.s3.amazonaws.com/uploads/3629f54390ead663d4eb7c53702e492de63299d7c5f7239efdc693b09b9b28c82c924225dcd8dcb65732d5ca7b7b753c5f17e056405bbd4596e4e63a96ae5018.png",
+ price: "$ 79.95",
+ },
+ ],
+ receipt: true,
+ name: "Sample Name",
+ address01: "1234 Fake St.",
+ address02: "Apt. 123",
+ city: "Place",
+ state: "CO",
+ zip: "80202",
+ },
+ },
+ },
+ }),
+ },
+ }
+ );
+ },
+ {
+ payloadSchema: z.object({
+ itemName: z.string().default("New Line Sneakers"),
+ dynamicTemplateId: z
+ .string()
+ .default("d-d965b02b1b5d4856bf332a5e98c7470c"),
+ }),
+ }
+);
+```
+
+
diff --git a/integrations/providers/email/sendinblue.mdx b/integrations/providers/email/sendinblue.mdx
new file mode 100644
index 00000000..be5bda26
--- /dev/null
+++ b/integrations/providers/email/sendinblue.mdx
@@ -0,0 +1,132 @@
+---
+title: "Brevo"
+description: "Learn how to use the Brevo provider to send email notifications using Novu"
+---
+
+You can use the [Brevo](https://www.brevo.com/) provider to send transactional emails to your customers using the Novu Platform with a single API.
+
+# Getting Started
+
+To use the Brevo provider in the email channel, you will need to create a Brevo account and add your API key to the Brevo integration on the Novu platform.
+
+# Finding the API Key
+
+- To find your Brevo API key, log into your Brevo account and navigate to the [API Keys](https://account.brevo.com/advanced/api) page.
+
+# Authenticating your sender identity
+
+Before you can send emails on a large scale, you will need to authenticate your sender's identity. This is due to the latest regulatory changes regarding SPAM rules and email fraud. Most of the providers including Brevo require you to authenticate your sender identity before you can send emails.
+
+Brevo allows you to authenticate your sender identity using one of the following methods:
+
+- [Single Sender Verification](https://account.brevo.com/senders) - This is the easiest way to authenticate your sender identity.
+- [Entire Domain Authentication](https://help.brevo.com/hc/en-us/articles/12163873383186-Authenticate-your-domain-with-Brevo-Brevo-code-DKIM-record-DMARC-record) - This is recommended if you are sending emails from multiple accounts under your domain.
+
+# Creating a Brevo integration with Novu
+
+- Visit the [Integrations store](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-brevo) on the Novu web dashboard.
+- Click on Add a Provider.
+- Select Brevo service.
+- Enter your Brevo API key.
+- Fill in the `From email address` field using the authenticated email from the previous step.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Update** button.
+- You should now be able to send notifications using Brevo in Novu.
+
+## Using Brevo template
+
+Novu has its own email editor for writing email template. If you want to use pre made template from Brevo, you can use `customData` filed of email overrides to send template details. Make sure your `Api Key` has enough permission to read and process the template.
+
+customData override is available from version 0.21.0
+
+
+
+```jsx
+import { Novu } from '@novu/node';
+
+const novu = new Novu('');
+
+await novu.subscribers.trigger("workflowIdentifier", {
+to: "subscriberId",
+payload: {},
+overrides: {
+email: {
+customData: {
+// sendinblue template templateId
+templateId: 1,
+// sendinblue template variables
+templateParams: {
+total: '$ 239.85',
+items: [{
+text: 'New Line Sneakers',
+image: 'https://marketing-image-production.s3.amazonaws.com/uploads/8dda1131320a6d978b515cc04ed479df259a458d5d45d58b6b381cae0bf9588113e80ef912f69e8c4cc1ef1a0297e8eefdb7b270064cc046b79a44e21b811802.png',
+price: '$ 79.95',
+},
+{
+text: 'Old Line Sneakers rlfjrjrh4hr4rh4',
+image: 'https://marketing-image-production.s3.amazonaws.com/uploads/3629f54390ead663d4eb7c53702e492de63299d7c5f7239efdc693b09b9b28c82c924225dcd8dcb65732d5ca7b7b753c5f17e056405bbd4596e4e63a96ae5018.png',
+price: '$ 79.95',
+},
+],
+receipt: true,
+name: 'Sample Name',
+address01: '1234 Fake St.',
+address02: 'Apt. 123',
+city: 'Place',
+state: 'CO',
+zip: '80202',
+},
+},
+}
+},
+// actorId is subscriberId of actor
+actor: "actorId",
+tenant: "tenantIdentifier"
+});
+
+```
+
+
+```bash
+curl --location 'https://api.novu.co/v1/events/trigger' \
+--header 'Content-Type: application/json' \
+--header 'Accept: application/json' \
+--header 'Authorization: ApiKey ' \
+--data '{
+ "name": "workflowIdentifier",
+ "to": ["subscriberId"],
+ "payload": {},
+ "overrides": {
+ "email": {
+ "customData": {
+ "templateId": 1,
+ "templateParams": {
+ "total": "$ 239.85",
+ "items": [
+ {
+ "text": "New Line Sneakers",
+ "image": "https://marketing-image-production.s3.amazonaws.com/uploads/8dda1131320a6d978b515cc04ed479df259a458d5d45d58b6b381cae0bf9588113e80ef912f69e8c4cc1ef1a0297e8eefdb7b270064cc046b79a44e21b811802.png",
+ "price": "$ 79.95"
+ },
+ {
+ "text": "Old Line Sneakers rlfjrjrh4hr4rh4",
+ "image": "https://marketing-image-production.s3.amazonaws.com/uploads/3629f54390ead663d4eb7c53702e492de63299d7c5f7239efdc693b09b9b28c82c924225dcd8dcb65732d5ca7b7b753c5f17e056405bbd4596e4e63a96ae5018.png",
+ "price": "$ 79.95"
+ }
+ ],
+ "receipt": true,
+ "name": "Sample Name",
+ "address01": "1234 Fake St.",
+ "address02": "Apt. 123",
+ "city": "Place",
+ "state": "CO",
+ "zip": "80202"
+ }
+ }
+ }
+ }
+}'
+```
+
+
+
diff --git a/integrations/providers/email/sparkpost.mdx b/integrations/providers/email/sparkpost.mdx
new file mode 100644
index 00000000..9b7e503b
--- /dev/null
+++ b/integrations/providers/email/sparkpost.mdx
@@ -0,0 +1,42 @@
+---
+title: "Sparkpost"
+description: "Learn how to use the Sparkpost provider to send email notifications using Novu"
+---
+
+You can use the [SparkPost](https://messagebird.com/email/cloud-sending) provider to send transactional emails to your customers using the Novu Platform with a single API.
+
+# Getting Started
+
+To use the SparkPost provider in the email channel, you will need to create a SparkPost account and add your API key to the SparkPost integration on the Novu platform.
+
+# Generating an API Key
+
+To generate a new API key in SparkPost, you can follow these steps:
+
+- [Sign up](https://app.sparkpost.com/join) or [Log in](https://app.sparkpost.com/auth) to your SparkPost account.
+ > During sign up, note that SparkPost is available in multiple regions. "SparkPost" refers to the SparkPost service hosted in North America. "SparkPost EU" refers to the SparkPost service hosted in Western Europe. An account created with SparkPost cannot be used with SparkPost EU, and vice-versa. You may use accounts in both regions.
+ >
+ > ~ *[SparkPost Documentation](https://support.sparkpost.com/docs/getting-started/getting-started-sparkpost/)*
+- Click on the **Configuration** link on the navbar, and then click the "API Keys" link that pops up from the available options.
+- On the [API Keys](https://app.sparkpost.com/account/api-keys) page, click the **Create API Key** button.
+- Give the API key a name and click on the **Create API key** button.
+- Copy the generated API Key.
+
+# Authenticating your Sender Identity
+
+Before you can send emails on a large scale, you will need to authenticate your Sender Identity.
+
+SparkPost allows you to authenticate your sender identity using [Sending Domains](https://app.sparkpost.com/domains/list/sending).
+
+# Creating a SparkPost integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sparkpost) page on Novu.
+- Click on Add a Provider.
+- Select SparkPost service.
+- Enter your SparkPost API Key.
+- Fill in the `From email address` field using the authenticated email from the previous step.
+- Fill in the `Sender's name`.
+- Toggle the `eu` switch to true if you're in Western Europe
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Update** button.
+- You should now be able to send notifications using SparkPost in Novu.
diff --git a/integrations/providers/email/webhook.mdx b/integrations/providers/email/webhook.mdx
new file mode 100644
index 00000000..f51c4b4b
--- /dev/null
+++ b/integrations/providers/email/webhook.mdx
@@ -0,0 +1,23 @@
+---
+title: "Email Webhook"
+description: "Learn how to use the Email Webhook provider to send email notifications using Novu"
+---
+
+[Email Webhooks](https://www.socketlabs.com/blog/what-is-a-webhook/#:~:text=Email%20webhooks%20are%20an%20extremely,%2C%20successful%20messages%2C%20and%20bounces.) are an extremely flexible way for developers to monitor the health of their mail stream in real time
+
+# Getting Started
+
+First go to [Email Settings](https://dashboard-v2.novu.co/settings/email?utm_campaign=docs-webhook) in Settings menu to configure Email Webhook.
+
+# Creating the Email Webhook integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-webhook) page on Novu.
+- Click the "Add a provider" button.
+- Select Email Webhook service
+- Click `Next`
+- Choose your preferred deployment environment: `Development` or `Production`. Then Click the `Create` button.
+- Add `Webhook URL` & `Secret Hmac Key` to sign off the email
+- Finally add `From email address` & `Sender name` to identify email sender
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Update** button.
+- You should now be able to send notifications using Email Webhook in Novu.
diff --git a/integrations/providers/email/writing-email-template.mdx b/integrations/providers/email/writing-email-template.mdx
new file mode 100644
index 00000000..3e34d522
--- /dev/null
+++ b/integrations/providers/email/writing-email-template.mdx
@@ -0,0 +1,78 @@
+---
+title: "Writing Email Template"
+sidebarTitle: "Writing Email Template"
+description: "Learn how to write an email template in Novu. This document will guide you through the process of creating an email template using the Novu email editor, all supported blocks and how to use them"
+icon: "file-pen"
+---
+
+## Email Editor
+
+The Email Editor is a `WYSIWYG` editor that allows you to create and edit email templates. It has two fields: `Subject` and `Body`.
+
+
+
+
+
+- **Subject** - Title of the email. It supports variables and can be customized based on the subscriber properties and payload variables.
+- **Body** - Main content of the email. It is made of blocks.
+
+## Email Editor Blocks
+
+Email editor body is made of blocks. A block can be added by clicking on the plus icon on the top left corner of the editor or by adding a forward slash `/`. In both the cases, a popover will appear with the list of supported blocks. Click on the desired block to add it to the editor. Menu option besides plus (+) icon can be used to **duplicate** or **delete** the block.
+
+
+
+
+
+The Email Editor supports the following blocks:
+
+- **Text** - Regular text
+- **Heading 1** - Large heading (H1)
+- **Heading 2** - Medium heading (H2)
+- **Heading 3** - Small heading (H3)
+- **Bullet List** - Bullet list (bullet points like •)
+- **Numbered List** - Numbered list (numeric digits like 1,2,3)
+- **Image** - Full widh image with absolute url, image position can be customized
+- **Section** - Create a section to group content together
+- **Column** - Creates columns to group content together, useful for responsive design
+- **Divider** - Separates the content, adds a line to highlight the separation
+- **Spacer** - Spacer to add space between two blocks (available in sm, lg and xl sizes)
+- **Button** - Call to action button to link to a page or url (can be customised with text, url, color and size and background color)
+- **Hard Break** - Adds a line break
+- **Blockquote** - Adds a blockquote
+- **Repeat** - Can be used for interation on array of data
+- **Show** - Can be used to conditionally show content based on a condition, use eye icon to toggle the visibility of the content
+
+### Repeat Block
+
+`Repeat` block is synonym with the javascript language `for` loop. It can be used to iterate over an array of data and render a block for each item in the array. Use + icon or / to add a `Repeat` block. Checkout the below video on how to use `Repeat` block
+
+
+
+In above video:
+- `{{payload.order.items}}` is array of items in the order and can be used to iterate over the items
+
+then each item in the iteration has following properties:
+- `{{payload.order.items.name}}` is the name of the item
+- `{{payload.order.items.price}}` is the price of the item
+
+
+### Show Block
+
+Show block can be used to conditionally show content based on a condition. Use the eye icon to toggle the visibility of the content. Few components supports eye icon. Conditions could be based on subscribr properties, payload variables.
+
+
+
+
+
+In above example, if subscriber has `showTracking` field set to `true` in custom `data` attribute, then the `Track your order` button will be shown in the email sent to the subscriber. Similarly, paylaod variables can be used to conditionally show the content.
+
+
diff --git a/integrations/providers/in-app/adding-in-app.mdx b/integrations/providers/in-app/adding-in-app.mdx
new file mode 100644
index 00000000..900a3ba1
--- /dev/null
+++ b/integrations/providers/in-app/adding-in-app.mdx
@@ -0,0 +1,45 @@
+---
+title: "Adding In-App Channel"
+sidebarTitle: "Adding In-App Channel"
+description: "Learn how to add the In-App channel to your application"
+icon: "circle-plus"
+---
+
+The In-App channel allows you to display notifications, messages, and other content directly within your application's interface.
+
+
+
+
+
+By default, the In-App channel is enabled. If it is disabled, you will not be able to send notifications to this channel.
+
+
+1. Go to the Novu Dashboard and click "Integrations" on the left sidebar
+2. Click "Channels" on the left sidebar
+3. Enable the In-App channel
+
+
+
+ 1. Go to the Novu Dashboard and click "Workflows" on the left sidebar
+2. Click **"Add a Workflow"** button
+3. Select "In-App" as the channel
+
+
+Novu’s server-side SDKs streamline the integration of Novu’s REST APIs, eliminating boilerplate code and reducing development effort when adding Novu to your server-side application.
+
+
+
+
+
+
+
+
+Novu's client-side SDKs simplify integration into your applications, offering powerful tools to customize and scale in-app notification experiences effortlessly, all while supporting multi-channel capabilities.
+
+
+
+
+
+
+
+
diff --git a/integrations/providers/in-app/media-assets/component_composition.png b/integrations/providers/in-app/media-assets/component_composition.png
new file mode 100644
index 00000000..8b88dac2
Binary files /dev/null and b/integrations/providers/in-app/media-assets/component_composition.png differ
diff --git a/integrations/providers/in-app/overview.mdx b/integrations/providers/in-app/overview.mdx
new file mode 100644
index 00000000..8b2d1ba7
--- /dev/null
+++ b/integrations/providers/in-app/overview.mdx
@@ -0,0 +1,53 @@
+---
+title: "In-App Notification Integration"
+sidebarTitle: "Overview"
+description: "Learn how to configure the in-app channel"
+icon: "circle-info"
+---
+
+Novu extends beyond traditional notification channels like email, SMS, and push by providing a robust framework for in-app notifications. With Novu, you can build reliable, stateful systems that integrate seamlessly into your applications.
+
+The inbox component and SDKs give you tools to create fully customizable, in-app notification experiences, including:
+
+- Inboxes
+- Floating feeds
+- Toasts
+- Banners
+
+These components are designed to integrate cleanly into your architecture while being flexible enough to adapt to any design system or interaction model.
+
+
+
+## Key features
+
+- **Real-time updates:** Use WebSockets to push notifications instantly
+- **Stateful Management:** Handle read/unread states, archiving, and user interaction seamlessly
+- **Developer-first customization:** Components are built to be modified at every layer, from UI to behavior
+- **Multi-platform support:** Consistent APIs across web and mobile SDKs
+- **Integration-ready:** Plug into your existing backend with minimal effort
+- **Actionable notifications:** Add clickable actions to notifications to drive user engagement
+
+## Common use cases
+
+- **User activity notifications:** Display updates like mentions, comments, or approvals in real time
+- **System alerts:** Notify users of status changes, outages, or important updates directly in your application
+- **Workflow tracking:** Keep users informed on task progress, deadlines, or assigned work
+- **Collaboration tools:** Enable notification feeds for shared projects or team activities
+
+## Available SDKs
+
+
+
+ Build notification interfaces with Novu's prebuilt UI components and React hooks for web applications
+
+
+ Build fully custom notification UIs for iOS and Android apps using Novu's React Native hooks SDK
+
+
+ A lightweight, standalone package for building custom notification interfaces with essential API methods and real-time WebSocket connections
+
+
+
+## Try It Out
+
+Explore the [Inbox Playground →](https://inbox.novu.co)
\ No newline at end of file
diff --git a/integrations/providers/push/adding-push.mdx b/integrations/providers/push/adding-push.mdx
new file mode 100644
index 00000000..6399f84c
--- /dev/null
+++ b/integrations/providers/push/adding-push.mdx
@@ -0,0 +1,148 @@
+---
+title: "Adding Push Channel"
+sidebarTitle: "Adding Push Channel"
+description: "Learn how to add the push channel to your application"
+icon: "circle-plus"
+---
+
+import { MissingProvider } from "/snippets/missing-provider.mdx";
+
+
+
+ To send push notifications using Novu, you need to set up a provider in the integration store.
+
+ 1. Go to the Novu Dashboard and click **"Integrations"** on the left sidebar
+ 2. Locate your desired push provider and configure it with the required credentials
+ 3. Ensure the provider is enabled
+
+
+
+
+ Add push notifications to a new or existing workflow.
+
+ 1. Navigate to the **"Workflows"** section in the Novu Dashboard
+ 2. Click **"Add a Workflow"** or select an existing workflow
+ 3. Add a step and choose **"Push"** as the channel
+ 4. Configure the push step by adding static or dynamic content such as title, message body, and variables
+
+
+
+
+ For push notifications to reach the right subscribers, store provider-specific device tokens or identifiers.
+
+ - Follow your provider’s documentation to obtain device tokens
+ - Use Novu’s subscriber management features to add these tokens to the subscriber profiles
+
+
+
+
+ Before triggering workflows, ensure your provider configuration is complete.
+
+ - Refer to your push provider's documentation to confirm all required steps are correctly set up
+ - Double-check any provider-specific settings in the integration store
+
+
+
+
+ Test the push workflow to ensure everything is working as expected
+
+ 1. Go to the **"Workflows"** section in the Novu Dashboard and select your configured workflow
+ 2. Use the **"Test Workflow"** option
+ 3. Verify the push notification delivery in Novu Logs or the push provider’s dashboard
+
+
+
+
+
+## Supported providers
+
+- [Firebase Cloud Messaging (FCM)](/integrations/providers/push/fcm)
+- [Expo push](/integrations/providers/push/expo-push)
+- [Apple push notification Service](/integrations/providers/push/apns)
+- [OneSignal](/integrations/providers/push/onesignal)
+- [Pushpad](/integrations/providers/push/pushpad)
+- [Push webhook](/integrations/providers/push/push-webhook)
+
+Novu supports multiple active providers for push channel.
+
+
+
+
+
+## Managing push device tokens
+
+To send push notifications to subscribers, you need to store device tokens or identifiers in subscriber profiles. These tokens are unique identifiers that help push notification providers deliver messages to the correct devices. Each provider has its own method for obtaining and storing device tokens.
+
+Novu offers to ways of keeping your device tokens in sync with subscriber profiles:
+
+- Just-in-time: Pass device tokens in the payload when triggering a workflow. Novu will automatically update subscriber profiles with the new device tokens.
+- Manual: Update subscriber profiles with device tokens using the Novu set credentials API.
+
+### Just-in-time
+
+When triggering a workflow, you can pass the `channels` array on the subscriber object with the device tokens for the push provider of your choice. Here is an example with fcm:
+
+```typescript
+novu.trigger("workflow-id", {
+ to: {
+ subscriberId: "subscriber-id",
+ channels: [
+ {
+ providerId: "fcm",
+ credentials: {
+ deviceTokens: ["token-1", "token-2"],
+ },
+ },
+ ],
+ },
+ payload: {},
+});
+```
+
+### Manual
+
+Use the Novu Set Credentials API to update subscriber profiles with device tokens. You can read more about the API in the [API Reference](/api-reference/subscribers/update-subscriber-credentials) or the [FCM Example](/integrations/providers/push/fcm#setting-device-token)
+
+## Frequently Asked Questions
+
+### How to remove one device token from subscriber credentials?
+
+To remove a device token from subscriber credentials, you need to get the current device tokens from subscriber credentials, remove all `deviceTokens`, remove the token you want to remove and then update the subscriber credentials with new device tokens.
+
+
+
+```ts
+import { Novu, PushProviderIdEnum } from '@novu/node';
+
+const novu = new Novu('');
+
+// fetch subscriber details
+const subscriber = await novu.subscribers.get('subscriberId');
+
+// get current device tokens from subscriber credentials for the provider
+const currentDeviceTokens = subsciber.data.data.channels.find(
+// \_integrationId can also be checked in place of providerId ;
+(channel) => channel.providerId === PushProviderIdEnum.FCM,
+).credentials.deviceTokens;
+
+// remove all device tokens
+await this.novu.subscribers.setCredentials(
+'subscriberId',
+PushProviderIdEnum.FCM,
+{ deviceTokens: [] },
+);
+
+// remove the token you want to remove
+const newDeviceTokens = currentDeviceTokens.filter(
+(token) => token !== 'token-to-be-removed',
+);
+
+// update subscriber credentials with new device tokens
+await this.novu.subscribers.setCredentials(
+'subscriberId',
+PushProviderIdEnum.FCM,
+{ deviceTokens: newDeviceTokens },
+);
+```
+
+
diff --git a/integrations/providers/push/apns.mdx b/integrations/providers/push/apns.mdx
new file mode 100644
index 00000000..93bfb0ce
--- /dev/null
+++ b/integrations/providers/push/apns.mdx
@@ -0,0 +1,124 @@
+---
+title: "Apple Push Notification Service (APNS)"
+sidebarTitle: "APNS"
+description: "Learn how to use the Apple Push Notification Service (APNS) provider to send push notifications using Novu"
+---
+
+[Apple Push Notification Service](https://docs.expo.dev/push-notifications/overview/), as the name suggests, is a notification delivery service provided by Apple.
+
+Apple provides two authentication methods to make a secure connection to APNs. The first is Certificate-Based Authentication (using a .p12 certificate). The second is Token-Based Authentication (using a .p8 key). We'll make use of the **.p8** key.
+
+To enable APNS integration, you need to create an [Apple Developer](https://developer.apple.com/) account with an [Admin role](https://appstoreconnect.apple.com/access/users).
+
+To generate the p8 key for your account:
+
+1. Head over to **Certificates, Identifiers & Profiles > Keys**.
+2. Register a new key and give it a name.
+3. Enable the Apple Push Notifications service (APNs) checkbox by selecting it.
+4. Click the **Continue** button and on the next page, select **Register**.
+5. Download the **.p8** key file.
+
+You also need the following to connect to APNs:
+
+1. **Key ID** - This is a 10-character unique identifier for the authentication key. You can find it in the key details section of the newly created key in your Apple developer account.
+2. **Team ID** - This is available in your Apple developer account.
+3. **Bundle ID** - This is the ID of your app. You can find it in the app info section of your Apple developer account.
+
+The overrides field supports all [Notification payload](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification?language=objc) values, as shown below:
+
+```typescript
+import { Novu } from "@novu/node";
+
+const novu = new Novu("");
+
+novu.trigger("", {
+ to: {
+ subscriberId: "",
+ },
+ payload: {
+ abc: "def", // If the notification is a data notification, the payload will be sent as the data
+ },
+ overrides: {
+ apns: {
+ payload: {
+ aps: {
+ notification: {
+ title: "Test",
+ body: "Test push",
+ },
+ data: {
+ key: "value",
+ },
+ },
+ },
+ },
+ },
+});
+```
+
+```typescript
+import { Novu } from "@novu/node";
+
+const novu = new Novu("");
+
+novu.trigger("", {
+ to: {
+ subscriberId: "",
+ },
+ payload: {
+ key1: "val1",
+ key2: "val2", // If the notification is a data notification, the payload will be sent as the data
+ },
+ overrides: {
+ type: "data",
+ apns: {
+ headers: {
+ "apns-priority": "5",
+ },
+ payload: {
+ aps: {
+ alert: {
+ "loc-key": "GAME_PLAY_REQUEST_FORMAT",
+ "loc-args": ["Shelly", "Rick"],
+ },
+ sound: "demo.wav",
+ },
+ },
+ },
+ },
+});
+```
+
+Before triggering the notification to a subscriber(user) with push as a step in the workflow, make sure you have added the subscriber's device token as follows:
+
+
+
+```javaScript
+import {
+ Novu,
+ ChatProviderIdEnum
+} from '@novu/node';
+
+const novu = new Novu("");
+
+await novu.subscribers.setCredentials('subscriberId', PushProviderIdEnum.APNS, {
+ deviceTokens: ['token1', 'token2'],
+});
+```
+
+
+```bash
+curl -L -X PUT 'https://api.novu.co/v1/subscribers//credentials' \
+-H 'Content-Type: application/json' \
+-H 'Accept: application/json' \
+-H 'Authorization: ApiKey ' \
+-d '{
+ "providerId": "apns",
+ "deviceTokens": ["token1", "token2"],
+ "integrationIdentifier": "apns-MnGLxp8uy"
+}'
+```
+
+
+
+Checkout the [API reference](/api-reference/subscribers/update-subscriber-credentials) for more details.
diff --git a/integrations/providers/push/expo-push.mdx b/integrations/providers/push/expo-push.mdx
new file mode 100644
index 00000000..023731c8
--- /dev/null
+++ b/integrations/providers/push/expo-push.mdx
@@ -0,0 +1,59 @@
+---
+title: "Expo Push"
+description: "Learn how to use the Expo push provider to send push notifications using Novu"
+---
+
+[Expo Push](https://docs.expo.dev/push-notifications/overview/) is a notification delivery service provided by Expo.
+
+To enable Expo Push integration, you need to create an [Expo Application Services (EAS)](https://expo.dev/)account and generate an access token in the EAS dashboard.
+
+The overrides field supports all [Message Request](https://docs.expo.dev/push-notifications/sending-notifications/#message-request-format) values. An example of the same follows:
+
+```typescript
+import { Novu } from "@novu/node";
+
+const novu = new Novu("");
+
+novu.trigger("", {
+ to: {
+ subscriberId: "",
+ },
+ payload: {
+ abc: "def",
+ },
+});
+```
+
+Before triggering the notification to a subscriber(user) with push as a step in the workflow, make sure you have added the subscriber's device token as follows:
+
+
+
+```javaScript
+import {
+ Novu,PushProviderIdEnum
+} from '@novu/node';
+
+const novu = new Novu("");
+
+await novu.subscribers.setCredentials('subscriberId', PushProviderIdEnum.EXPO, {
+ deviceTokens: ['token1', 'token2'],
+});
+```
+
+
+```bash
+curl -L -X PUT 'https://api.novu.co/v1/subscribers//credentials' \
+-H 'Content-Type: application/json' \
+-H 'Accept: application/json' \
+-H 'Authorization: ApiKey ' \
+-d '{
+ "providerId": "expo",
+ "deviceTokens": ["token1", "token2"],
+ "integrationIdentifier": "expo-MnGLxp8uy"
+}'
+```
+
+
+
+
+Checkout the [API reference](/api-reference/subscribers/update-subscriber-credentials) for more details.
diff --git a/integrations/providers/push/fcm.mdx b/integrations/providers/push/fcm.mdx
new file mode 100644
index 00000000..4f7d6b8e
--- /dev/null
+++ b/integrations/providers/push/fcm.mdx
@@ -0,0 +1,221 @@
+---
+title: "Firebase Cloud Messaging (FCM)"
+sidebarTitle: "FCM"
+description: "Learn how to use the Firebase Cloud Messaging (FCM) provider to send push notifications using Novu"
+---
+
+[Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) is a free notification delivery service provided by Google Firebase.
+
+To enable the FCM integration, you need to get your service account key from the [Firebase Console](https://console.firebase.google.com/).
+
+## Generating a Service Account Key JSON
+
+To acquire the account key JSON file for your service account
+
+1. Select your project, and click the gear icon on the top of the sidebar.
+2. Head to project settings.
+3. Navigate to the service account tab.
+4. Click **Generate New Private Key,** then confirm by clicking **Generate Key.**
+5. Clicking **Generate Key** downloads the JSON file.
+
+After that, paste the entire JSON file content in the Service Account field of the FCM provider in the integration store on Novu’s web dashboard.
+
+Make sure your service account json content contains these fields
+
+```json
+{
+ "type": "service_account",
+ "project_id": "PROJECT_ID",
+ "private_key_id": "PRIVATE_KEY_ID",
+ "private_key": "PRIVATE_KEY",
+ "client_email": "FIREBASE_ADMIN_SDK_EMAIL",
+ "client_id": "CLIENT_ID",
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+ "token_uri": "https://oauth2.googleapis.com/token",
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+ "client_x509_cert_url": "CLIENT_X509_CERT_URL"
+}
+```
+
+## FCM Overrides
+
+The overrides field supports apns, android, webpush and fcmOptions overrides
+
+| Override Field | Type / Interface | Link |
+| -------------- | ---------------- | -------------------------------------------------------------------------------------------- |
+| android | AndroidConfig | https://firebase.google.com/docs/reference/admin/node/firebase-admin.messaging.androidconfig |
+| apns | ApnsConfig | https://firebase.google.com/docs/reference/admin/node/firebase-admin.messaging.apnsconfig |
+| webPush | WebpushConfig | https://firebase.google.com/docs/reference/admin/node/firebase-admin.messaging.webpushconfig |
+| fcmOptions | FcmOptions | https://firebase.google.com/docs/reference/admin/node/firebase-admin.messaging.fcmoptions |
+
+## Setting Device Token
+
+Before triggering the notification to a subscriber(user) with push as a step in the workflow, make sure you have added the subscriber's device token as follows:
+
+
+
+```javaScript
+import {
+ Novu,
+ PushProviderIdEnum
+} from '@novu/node';
+
+const novu = new Novu("");
+
+await novu.subscribers.setCredentials(
+ 'subscriberId',
+ PushProviderIdEnum.FCM, {
+ deviceTokens: ['token1', 'token2']
+ },
+ 'fcm-MnGLxp8uy'
+);
+```
+
+
+
+```bash
+curl -L -X PUT 'https://api.novu.co/v1/subscribers//credentials' \
+-H 'Content-Type: application/json' \
+-H 'Accept: application/json' \
+-H 'Authorization: ApiKey ' \
+-d '{
+ "providerId": "fcm",
+ "credentials": {
+ "deviceTokens" : [
+ "token1",
+ "token2"
+ ]
+ },
+ "integrationIdentifier": "fcm-MnGLxp8uy"
+}'
+```
+
+
+
+
+Checkout the [API reference](/api-reference/subscribers/update-subscriber-credentials) for more details.
+
+## SDK Trigger Example
+
+```typescript
+import { Novu } from "@novu/node";
+
+const novu = new Novu("");
+
+novu.trigger("", {
+ to: {
+ subscriberId: "",
+ },
+ payload: {
+ abc: "def", // If the notification is a data notification, the payload will be sent as the data
+ },
+ overrides: {
+ fcm: {
+ // type: 'data' => will turn this into an FCM data notification, where the payload is sent as a data notification. If the type is not set, you can use the "data" override to send notification messages with optional data payload
+ type: "data",
+
+ // URL of an image to be displayed in the notification.
+ imageUrl: "https://domain.com/image.png",
+
+ // If type is not set, you can use the "data" override to send notification messages with optional data payload
+ data: {
+ key: "value",
+ },
+
+ // Check FCM Overrides section above for these types
+ android: {},
+ apns: {},
+ webPush: {},
+ fcmOptions: {},
+ },
+ },
+});
+```
+
+Device/notification identifiers can be set by using [setCredentials](#set-device-token)
+
+ Novu uses FCM version V1
+
+## Relative Link in Webpush
+
+Suppose you’re using the Firebase (FCM) provider to send push notifications to web browsers via Novu and want users to be returned to the website after clicking the notification.
+
+In that case, you must use the `link` property with a relative URL.
+
+
+
+
+```javascript
+import { Novu } from "@novu/node";
+
+const novu = new Novu("");
+
+novu.trigger("", {
+ to: {
+ subscriberId: "",
+ },
+ payload: {
+ abc: "def", // If the notification is a data notification, the payload will be sent as the data
+ },
+ overrides: {
+ fcm: {
+ webPush: {
+ fcmOptions: {
+ link: "/foo",
+ },
+ },
+ },
+ },
+});
+```
+
+
+
+ ```bash
+ curl --location --request POST 'https://url.to.our.selfhosted.novu' \
+ --header 'Authorization: ApiKey KEY' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "name": "workflow-name",
+ "to": {
+ ...
+ },
+ "overrides": {
+ "fcm": {
+ "webPush": {
+ "fcm_options": {
+ "link": "/foo"
+ }
+ }
+ }
+ }
+ }'
+ ```
+
+
+
+## FCM Cost
+
+As per Firebase [pricing](https://firebase.google.com/pricing), **Cloud Messaging** product is free of cost to use. If other Firebase products are used, the cost will be charged as per the product.
+
+## FAQs
+
+
+
+ You may come across an error like so:
+
+ ""Sending message failed due to "The registration token is not a valid FCM registration token""".
+
+ This error happens because of invalid or stale token. The fix for this is to remove old tokens, generate a new token and save it into user subscribers.
+
+
+
+ Try to generate a new token after clearing device cache and retry with this fresh token.
+
+
+ This error occurs when your token is no longer valid. To fix this, generate a new token and use it.
+
+
+ This error occurs if the fcm integration is active but subscriber is missing from the fcm credentials (deviceTokens). The credentials (deviceTokens) for the subscriber needs to be set.
+
+
diff --git a/integrations/providers/push/onesignal.mdx b/integrations/providers/push/onesignal.mdx
new file mode 100644
index 00000000..4a0ce50f
--- /dev/null
+++ b/integrations/providers/push/onesignal.mdx
@@ -0,0 +1,87 @@
+---
+title: "Onesignal"
+description: "Learn how to use the Onesignal provider to send push notifications using Novu"
+---
+
+[OneSignal](https://onesignal.com/) is a paid push notification service that supports sending messages via both [Apple Push Notification Service](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server) (APNs) as well as [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) (FCM).
+
+To configure the OneSignal integration, you will need an active account which has credentials for APNs, FCM or both, and have access to the `OneSignal App ID` and `Rest API Key` available via your [application's settings page](https://documentation.onesignal.com/docs/keys-and-ids).
+
+## Setting the Device Token
+
+Once OneSignal has been configured with your credentials for APNs/FCM, and the OneSignal SDK has been [set up and configured](https://documentation.onesignal.com/docs/onboarding-with-onesignal#step-1-setup-onesignal-sdk) for your application, your users will begin to be automatically assigned a unique OneSignal [player_id](https://documentation.onesignal.com/docs/users#player-id) identifier by the SDK.
+
+This identifier allows targeting your user when sending push notifications without having to retrieve the specific Android or iOS device tokens - which are managed by OneSignal.
+
+In order to target the OneSignal user from Novu, you must register the OneSignal `player_id`as the `deviceToken` for your Novu subscriber. This value can be retrieved via the [OneSignal SDK](https://documentation.onesignal.com/docs/users-and-devices#finding-users) for your platform.
+
+Once you have the user's `player_id` value, the `deviceToken` for your Novu subscriber can be set via:
+
+
+
+```javaScript
+import {
+ Novu,
+ ChatProviderIdEnum
+} from '@novu/node';
+
+const novu = new Novu("");
+
+await novu.subscribers.setCredentials('subscriberId', PushProviderIdEnum.OneSignal, {
+ // Your user's unique 'player_id' from OneSignal
+ deviceTokens: [ 'ad0452ca-3ca7-43b5-bf9b-fa93fd322035' ],
+});
+```
+
+
+```bash
+curl -L -X PUT 'https://api.novu.co/v1/subscribers//credentials' \
+-H 'Content-Type: application/json' \
+-H 'Accept: application/json' \
+-H 'Authorization: ApiKey ' \
+-d '{
+ "providerId": "one-signal",
+ "deviceTokens": ["ad0452ca-3ca7-43b5-bf9b-fa93fd322035"],
+ "integrationIdentifier": "one-signal-MnGLxp8uy"
+}'
+```
+
+
+
+
+Checkout the [API reference](/api-reference/subscribers/update-subscriber-credentials) for more details.
+
+## SDK Trigger Example
+
+```typescript
+novu.trigger("", {
+ to: {
+ subscriberId: "",
+ },
+ payload: {
+ abc: "def", // If the notification is a data notification, the payload will be sent as the data
+ },
+ overrides: {
+ subtitle: "This is subtitle value",
+ mutableContent: "Mutable content value",
+ // for android notification categories
+ channelId: "category_id",
+ // for ios notification categories
+ categoryId: "Category id",
+ // same value is used for all sizes and browsers
+ icon: "https://image.com/icon.png",
+ // used for both android and ios
+ sound: "sound file url"
+ }
+});
+```
+
+Device/notification identifiers can be set by using [setCredentials](/integrations/providers/push/fcm#setting-device-token)
+
+## Using external user id
+
+By default, Novu uses player id to send notifications, `External ID` option can be selected in the onesignal integration settings. If `External ID` option is selected, `deviceTokens` stored in subscriber credentials for onesignal provider, will be used as external user ids.
+
+
+
+
\ No newline at end of file
diff --git a/integrations/providers/push/overview.mdx b/integrations/providers/push/overview.mdx
new file mode 100644
index 00000000..3ff6f434
--- /dev/null
+++ b/integrations/providers/push/overview.mdx
@@ -0,0 +1,133 @@
+---
+title: "Push Providers Integration"
+sidebarTitle: "Overview"
+description: "Learn how to configure the Push channel"
+icon: "circle-info"
+---
+import { MissingProvider } from "/snippets/missing-provider.mdx";
+
+Push notifications are a powerful way to deliver real-time updates, reminders, and personalized messages to your users across mobile and web platforms.
+Whether it's a promotional alert, a system notification, or a critical update, push notifications are key to enhancing engagement and retention.
+
+Novu simplifies the process by offering a unified API that supports multiple push notification providers, enabling reliable and efficient message delivery.
+
+## Key Features
+
+- **Multi-Provider Support**: Integrate with providers like Firebase Cloud Messaging (FCM), OneSignal, or Apple Push Notification Service (APNS)
+- **Unified Delivery**: Streamline your push notifications with a single API for mobile and web platforms
+- **Dynamic Content**: Customize notifications with variables for personalized user experiences
+- **Device Management**: Keep subscriber device tokens in sync using just-in-time or manual updates
+
+## Common Use Cases
+- **Transactional Notifications**: Payment updates, delivery alerts, appointment reminders
+- **Engagement Notifications**: Promotions, re-engagement campaigns, social media interactions
+- **System Alerts**: Security warnings, downtime alerts, account activity updates
+
+
+## How to send push notifications
+To send a push notification to subscribers (users) using Novu:
+
+- Add a push channel provider in integration store
+- Add push channel as a step in existing or new workflow.
+- Add static or dynamic content using variables in push step fields
+- Store provider specific device tokens/identifiers in subscriber profile. Read provider specific documentation on how to obtain and store device tokens.
+- Make sure all provider specific steps are configured properly before triggering workflow. Read provider related documentation for all required steps.
+- Trigger the workflow.
+
+## Supported providers
+
+- [Firebase Cloud Messaging (FCM)](/channels-and-providers/push/fcm)
+- [Expo Push](/channels-and-providers/push/expo-push)
+- [Apple Push Notification Service](/channels-and-providers/push/apns)
+- [OneSignal](/channels-and-providers/push/onesignal)
+- [Pushpad](/channels-and-providers/push/pushpad)
+- [Push Webhook](/channels-and-providers/push/push-webhook)
+
+Novu supports multiple active providers for push channel.
+
+## Managing push device tokens
+
+To send push notifications to subscribers, you need to store device tokens or identifiers in subscriber profiles. These tokens are unique identifiers that help push notification providers deliver messages to the correct devices. Each provider has its own method for obtaining and storing device tokens.
+
+Novu offers to ways of keeping your device tokens in sync with subscriber profiles:
+
+- Just-in-time: Pass device tokens in the payload when triggering a workflow. Novu will automatically update subscriber profiles with the new device tokens.
+- Manual: Update subscriber profiles with device tokens using the Novu Set Credentials API.
+
+### Just-in-time
+
+When triggering a workflow, you can pass the `channels` array on the subscriber object with the device tokens for the push provider of your choice. Here is an example with fcm:
+
+```typescript
+novu.trigger("workflow-id", {
+ to: {
+ subscriberId: "subscriber-id",
+ channels: [
+ {
+ providerId: "fcm",
+ credentials: {
+ deviceTokens: ["token-1", "token-2"],
+ },
+ },
+ ],
+ },
+ payload: {},
+});
+```
+
+### Manual
+
+Use the Novu Set Credentials API to update subscriber profiles with device tokens. You can read more about the API in the [API Reference](/api-reference/subscribers/update-subscriber-credentials) or the [FCM Example](/integrations/providers/push/fcm#setting-device-token)
+
+## Frequently Asked Questions
+
+### How to remove one device token from subscriber credentials?
+
+To remove a device token from subscriber credentials, you need to get the current device tokens from subscriber credentials, remove all deviceTokens, remove the token you want to remove and then update the subscriber credentials with new device tokens.
+
+
+
+```ts
+import {
+ Novu,
+ PushProviderIdEnum
+} from '@novu/node';
+
+const novu = new Novu('');
+
+// fetch subscriber details
+const subscriber = await novu.subscribers.get('subscriberId');
+
+// get current device tokens from subscriber credentials for the provider
+const currentDeviceTokens = subsciber.data.data.channels.find(
+ // _integrationId can also be checked in place of providerId ;
+ (channel) => channel.providerId === PushProviderIdEnum.FCM,
+).credentials.deviceTokens;
+
+// remove all device tokens
+await this.novu.subscribers.setCredentials(
+ 'subscriberId',
+ PushProviderIdEnum.FCM, {
+ deviceTokens: []
+ },
+);
+
+// remove the token you want to remove
+const newDeviceTokens = currentDeviceTokens.filter(
+ (token) => token !== 'token-to-be-removed',
+);
+
+// update subscriber credentials with new device tokens
+await this.novu.subscribers.setCredentials(
+ 'subscriberId',
+ PushProviderIdEnum.FCM, {
+ deviceTokens: newDeviceTokens
+ },
+);
+```
+
+
+
+
+
+
diff --git a/integrations/providers/push/push-webhook.mdx b/integrations/providers/push/push-webhook.mdx
new file mode 100644
index 00000000..d9b6961f
--- /dev/null
+++ b/integrations/providers/push/push-webhook.mdx
@@ -0,0 +1,137 @@
+---
+title: "Push Webhook"
+description: "Learn how to use the Push Webhook provider to send notifications using Novu"
+---
+
+Push Webhook provider is a bit different different from other push providers as it does not depend on other third party services.
+Users can use their own api url as webhook url and novu will make a post request on that webhook url.
+
+## Steps To Configure
+
+1. Go to [integration store](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-push-webhook) and click on `Add a provider` button. Choose `Push` channel and then `Push Webhook` provider.
+2. Enter your Webhook URL. For quick testing use [this](https://webhook.site/) website.
+3. Enter Secret Hmac Key. Novu will use this secret hmac key to encrypt the data using `HMAC SHA256` algorithm and send the hash as value of `x-novu-signature` header. User can use `x-novu-signature` header to test authenticity of the request. Read more [here](#checking-authenticity)
+4. Click on the update button.
+5. Update the subscriber credentials using SDK or API. Read more [here](#set-device-token)
+
+Your webhook url should accept `POST` request.
+
+## Set Device Token
+
+This step is a mandatory step. Other push providers have third party dependencies where a device token can be generated. But in case of push webhook provider, there is no any way to generate device token. Any random string can be used as device token.
+
+
+
+```javaScript
+import {
+ Novu,
+ ChatProviderIdEnum
+} from '@novu/node';
+
+const novu = new Novu("");
+
+// PushProviderIdEnum.PushWebhook = push-webhook
+await novu.subscribers.setCredentials('subscriberId', PushProviderIdEnum.PushWebhook, {
+ deviceTokens: ['ANY_RANDOM_STRING'],
+});
+```
+
+
+```bash
+curl -L -X PUT 'https://api.novu.co/v1/subscribers//credentials' \
+-H 'Content-Type: application/json' \
+-H 'Accept: application/json' \
+-H 'Authorization: ApiKey ' \
+-d '{
+ "providerId": "push-webhook",
+ "deviceTokens": ['ANY_RANDOM_STRING'],
+ "integrationIdentifier": "push-webhook-MnGLxp8uy"
+}'
+```
+
+
+
+
+Checkout the [API reference](/api-reference/subscribers/update-subscriber-credentials) for more details.
+
+## Example paylod sent by novu to webhook url
+
+```json
+{
+ "target": ["subscriber-token-for-push-webhook-provider"],
+ "title": "Push Webhook message title",
+ "content": "push Webhook content body",
+ "overrides": {
+ "data": {
+ "custom_message": "this is custom message from payload push webhook demo"
+ }
+ },
+ "payload": {
+ "custom_message": "this is custom message from payload push webhook demo",
+ "__source": "test-workflow",
+ "subscriber": {
+ // subscriber fields
+ "_id": "65c0d71c0959a38e8857b131",
+ "_organizationId": "organizationId",
+ "_environmentId": "environmentId",
+ "firstName": "Pawan",
+ "lastName": "Jain",
+ "phone": "+123456789",
+ "subscriberId": "push-webhook-demo-subscriber-id",
+ "email": "pawan+push+web+hook+demo@domain.com",
+ "channels": [
+ {
+ "credentials": {
+ "deviceTokens": ["subscriber-token-for-push-webhook-provider"]
+ },
+ "_integrationId": "integrationId",
+ "providerId": "push-webhook"
+ }
+ ],
+ "data": {
+ // custom data field of subscriber
+ "isDeveloper": "true"
+ },
+ "deleted": false,
+ "createdAt": "2024-02-05T12:39:56.379Z",
+ "updatedAt": "2024-02-05T12:54:08.684Z",
+ "__v": 0,
+ "id": "65c0d71c0959a38e8857b131"
+ },
+ "step": {
+ // digest variables
+ "digest": false,
+ "events": [],
+ "total_count": 0
+ }
+ }
+}
+```
+
+## Checking Authenticity
+
+```typescript
+import crypto from "crypto"
+
+// secret key added in step 3
+const secretKey = "YOUR_HMAC_SECRET_KEY"
+
+// function to handle webhook url route request
+async acceptNovuPushWebHookRequest(request, response){
+
+ const payloadSentByNovu = request.body
+ const hmacHashSentByNovu = request.headers['x-novu-signature']
+
+ const actualHashValue = crypto
+ .createHmac('sha256', secretKey)
+ .update(payloadSentByNovu, 'utf-8')
+ .digest('hex');
+
+ if(hmacHashSentByNovu === actualHashValue){
+ // handle the notification
+ console.log("Request sent by Novu")
+ } else {
+ throw new Error("Not a valid request")
+ }
+}
+```
diff --git a/integrations/providers/push/pusher-beams.mdx b/integrations/providers/push/pusher-beams.mdx
new file mode 100644
index 00000000..d67e3fc2
--- /dev/null
+++ b/integrations/providers/push/pusher-beams.mdx
@@ -0,0 +1,69 @@
+---
+title: "Pusher Beams"
+description: "Learn how to use the Pusher Beams provider to send push notifications using Novu"
+---
+
+[Pusher Beams](https://pusher.com/beams/) is a cross-platform push notification API service provided by Pusher.
+
+To enable Pusher Beams integration, you need to create a Pusher Beams Instance and use both `Instance ID` and `Secret Key` from the Instance [dashboard](https://dashboard.pusher.com/beams/).
+
+## Setting the Device Token
+
+Once Pusher Beams instance has been created, and the Pusher Beams SDK has been [set up and configured](https://pusher.com/docs/beams/reference/all-libraries/) for your application, you can associate users with their devices using [Authenticated Users](https://pusher.com/docs/beams/guides/publish-to-specific-user/web/).
+
+This identifier allows targeting your user when sending push notifications without having to retrieve the specific Web, Android or iOS device tokens - which are managed by Pusher Beams.
+
+In order to target the Pusher Beams user from Novu, you must register the Pusher Beams Authenticated Users with their `userId` as the `deviceToken` for your Novu subscriber. This value can be retrieved via [getUserId()](https://pusher.com/docs/beams/reference/web/#getuserid) method from the SDK for your platform.
+
+Once you have the user's `userId` value, the `deviceToken` for your Novu subscriber can be set via:
+
+
+
+```javaScript
+import {
+ Novu,
+ ChatProviderIdEnum
+} from '@novu/node';
+
+const novu = new Novu("");
+
+await novu.subscribers.setCredentials('subscriberId', PushProviderIdEnum.PusherBeams, {
+ // Your user's unique 'userId' from Pusher Beams
+ deviceTokens: ['userId-from-pusher-beams'],
+});
+```
+
+
+```bash
+curl -L -X PUT 'https://api.novu.co/v1/subscribers//credentials' \
+-H 'Content-Type: application/json' \
+-H 'Accept: application/json' \
+-H 'Authorization: ApiKey ' \
+-d '{
+ "providerId": "pusher-beams",
+ "deviceTokens": ['userId-from-pusher-beams'],
+ "integrationIdentifier": "pusher-beams-MnGLxp8uy"
+}'
+```
+
+
+
+
+Checkout the [API reference](/api-reference/subscribers/update-subscriber-credentials) for more details.
+
+## SDK Trigger Example
+
+```typescript
+import { Novu } from "@novu/node";
+
+const novu = new Novu("");
+
+novu.trigger("", {
+ to: {
+ subscriberId: "",
+ },
+ payload: {
+ custom_data: "custom_data", // the payload will be sent as notification data object. Cannot contain the key “pusher”
+ },
+});
+```
diff --git a/integrations/providers/push/pushpad.mdx b/integrations/providers/push/pushpad.mdx
new file mode 100644
index 00000000..265780b9
--- /dev/null
+++ b/integrations/providers/push/pushpad.mdx
@@ -0,0 +1,67 @@
+---
+title: "Pushpad"
+description: "Learn how to use the Pushpad provider to send web push notifications using Novu"
+---
+
+[Pushpad](https://pushpad.xyz) is a web push service that supports sending notifications to all major browsers (Chrome, Firefox, Edge, Safari, etc.) via FCM, Mozilla autopush, Windows Push Notification Services and Apple Push Notification service, with just one simple API.
+
+To configure the Pushpad integration, you will need an active account and you need to have a `Pushpad Auth Token` (from the [account settings](https://pushpad.xyz/access_tokens)) and the `Pushpad Project ID` (from the project settings).
+
+## Setting the Device Token
+
+Once Pushpad has been configured with your credentials and the Pushpad SDK has been [set up and installed](https://pushpad.xyz/docs/pushpad_pro_getting_started) on your website, you can [assign a user ID (uid)](https://pushpad.xyz/docs/identifying_users) to the push subscriptions.
+
+This identifier (`uid` or `user ID`) allows targeting a specific browser when sending push notifications.
+
+In order to target the Pushpad user from Novu, you must register the Pushpad `uid` as the `deviceToken` for your Novu subscriber. For example, if you invoked the [Pushpad JavaScript SDK](https://pushpad.xyz/docs/javascript_sdk_reference) with `pushpad('uid', 'user123')`, then `user123` is the user ID for that browser.
+
+Once you have the `uid` value, the `deviceToken` for your Novu subscriber can be set using this code:
+
+
+
+```javaScript
+import {
+ Novu,
+ ChatProviderIdEnum
+} from '@novu/node';
+
+const novu = new Novu("");
+
+await novu.subscribers.setCredentials('subscriberId', PushProviderIdEnum.Pushpad, {
+ // the user ID (uid) that you used for Pushpad
+ deviceTokens: ['user123'],
+});
+```
+
+
+```bash
+curl -L -X PUT 'https://api.novu.co/v1/subscribers//credentials' \
+-H 'Content-Type: application/json' \
+-H 'Accept: application/json' \
+-H 'Authorization: ApiKey ' \
+-d '{
+ "providerId": "pushpad",
+ "deviceTokens": ['user123'],
+ "integrationIdentifier": "pushpad-MnGLxp8uy"
+}'
+```
+
+
+
+
+Checkout the [API reference](/api-reference/subscribers/update-subscriber-credentials) for more details.
+
+# SDK Trigger Example
+
+```typescript
+import { Novu } from "@novu/node";
+
+const novu = new Novu("");
+
+novu.trigger("", {
+ to: {
+ subscriberId: "",
+ },
+ payload: {},
+});
+```
\ No newline at end of file
diff --git a/integrations/providers/sms/46elks.mdx b/integrations/providers/sms/46elks.mdx
new file mode 100644
index 00000000..3ad4df7d
--- /dev/null
+++ b/integrations/providers/sms/46elks.mdx
@@ -0,0 +1,21 @@
+---
+title: "46elks"
+description: "Learn how to use the 46elks provider to send sms notifications using Novu"
+---
+
+You can use the [46elks](https://46elks.com/) provider to send SMS messages to your customers using the Novu platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+To use the 46elks provider in the sms channel, you will need to create a 46elks account and add your `API Username` & `API Password` to the 46elks integration on the Novu platform.
+
+# Creating the 46elks integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-46elks) page on Novu.
+- Click the "Add a provider" button.
+- Select 46elks service
+- Click `Next`
+- Choose your preferred deployment environment: `Development` or `Production`. Then Click the `Create` button.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Update** button.
+- You should now be able to send SMS, MMS, using 46elks in Novu.
diff --git a/integrations/providers/sms/adding-sms.mdx b/integrations/providers/sms/adding-sms.mdx
new file mode 100644
index 00000000..7eba3c20
--- /dev/null
+++ b/integrations/providers/sms/adding-sms.mdx
@@ -0,0 +1,135 @@
+---
+title: "Adding SMS Channel"
+sidebarTitle: "Adding SMS Channel"
+description: "Learn how to add the SMS channel to your application"
+icon: "circle-plus"
+---
+
+import { MissingProvider } from "/snippets/missing-provider.mdx";
+
+
+
+
+
+The SMS channel is not enabled by default. To use it, configure a provider like Twilio, Nexmo, or others, and ensure compliance with country-specific restrictions for sender IDs (`from`).
+
+
+1. Go to the Novu Dashboard and click **"Integrations"** on the left sidebar
+2. Click **"Add a provider"**
+3. Locate the **SMS** channel and select the provider you want to use and click **"Next"**
+4. Select for which environment you want to add the Provider
+5. (Optional) Add Conditions to activate the provider only under certain conditions, **useful for tenant-based providers**
+6. Click **"Create"**
+7. Add your SMS provider credentials:
+ - **From**: Displayed as the sender of the SMS (ensure compliance with local regulations)
+ - Provider-specific credentials such as API key / Auth token, Account SID, username, or password
+8. Save the configuration by clicking **"Update"**
+
+
+
+
+
+
+ 1. Go to the Novu Dashboard and click **"Workflows"** on the left sidebar.
+ 2. Click the **"Add a Workflow"** button.
+ 3. Add a step and select **"SMS"** as the channel.
+ 4. Configure the SMS content:
+ - Message body (e.g., `{{userName}}, your order {{orderId}} has shipped.`).
+ - Dynamic placeholders for personalized content.
+ 5. Optionally, set fallback channels to ensure reliable delivery if SMS fails.
+
+
+
+
+
+Novu’s server-side SDKs make integrating Novu’s REST APIs straightforward, letting you focus on implementing workflows without dealing with repetitive code.
+
+
+
+
+
+
+Ensure your SMS configuration is working correctly by testing the setup.
+
+1. Go to the Novu Dashboard, navigate to the **"Workflows"** section, and locate your configured workflow.
+2. Click **"Test Workflow"** and provide sample data, such as a phone number or dynamic variables.
+3. Verify delivery in the Novu Logs or your SMS provider’s dashboard.
+
+
+
+
+
+ Some countries have strict restriction of using verified `from` sender id
+ (name). Kindly check country and provider specific requirements first.
+
+
+## Sending SMS overrides
+
+The overrides field supports a `sms` property and `from`, `to`, `content` field overrides. This allows you to send a message to a different recipient, from a different sender, or with a different content.
+
+```javascript
+import { Novu } from '@novu/node';
+
+const novu = new Novu('');
+
+novu.trigger('', {
+ to: {
+ subscriberId: '',
+ }
+ overrides: {
+ sms: {
+ to: '+123012345678',
+ from: 'Novu Team',
+ content: 'This SMS message is from overrides'
+ },
+ },
+});
+```
+
+## Using different SMS integration
+
+In Novu integration store, multiple SMS channel type provider integrations can be active at the same time. But only one provider integration can be primary at a time. This primary integration will be used as a provider to deliver the SMS by default. If you want to use a different active provider integration then you can use the `integrationIdentifier` sms overrides field.
+
+If there are 4 active SMS integrations with these identifiers:-
+
+1. twilio-abcdef
+2. twilio-ghijkl
+3. firetext-abcdef
+4. infobip-abcdef
+
+Here, if `twilio-abcdef` is primary integration and you want to use `infobip-abcdef` with this trigger then you can use `integrationIdentifier` sms overrides field as below:-
+
+
+
+```javascript
+import { Novu } from '@novu/node';
+
+const novu = new Novu('');
+
+novu.trigger('', {
+ to: {
+ subscriberId: '',
+ },
+ overrides: {
+ sms: {
+ integrationIdentifier: 'infobip-abcdef',
+ },
+ },
+});
+
+```
+
+
+
+ Integration identifier is similar to Provider identifier but it is different than Provider Id. It is unique for each integration. You can find the `integrationIdentifier` in the integration store page.
+
+## Common errors
+
+Common errors and reason for these errors while sending sms messages using Novu.
+
+1. Subscriber does not have a configured channel.
+ - if the `from` field is missing / null / undefined.
+
+
+
+
diff --git a/integrations/providers/sms/africas-talking.mdx b/integrations/providers/sms/africas-talking.mdx
new file mode 100644
index 00000000..4a4393c4
--- /dev/null
+++ b/integrations/providers/sms/africas-talking.mdx
@@ -0,0 +1,97 @@
+---
+title: "Africa's Talking"
+description: "Learn how to use the Africa's Talking provider to send sms notifications using Novu"
+---
+
+You can use [Africa's Talking](https://africastalking.com/) provider to send SMS messages to your customers using the Novu platform with a single API to create multi-channel experiences.
+
+## Getting Started
+
+To use Africa's Talking provider in the SMS channel, the first step is to create an Africa's Talking account and add your `API Key`, `username` and `Sender's ID` to Africa's Talking integration on the Novu platform.
+
+## Creating a username
+
+You’ll need to create an application to create a username in Africa’s Talking. To do so, follow the steps below:
+
+- [Sign up](https://account.africastalking.com/auth/register) or [Log in](https://account.africastalking.com/auth/login) to your Africa's Talking account.
+- Select the team you want your app to be in.
+
+ {" "}
+
+- If you're a new user and don't have a team yet, you'll have to do so by clicking on New Team and entering your team name. Click on Save when you're done.
+
+ {" "}
+
+- On the page that appears, click on the Create App button.
+- On the pop-up that appears, enter your application name, username and select a country. Then click Save. The `username` is what you will use on the Novu platform.
+
+ {" "}
+
+
+## Generating an API key
+
+To generate a new API key, you can follow these steps:
+
+Ensure you have created an app in your team.- Click on the app you created.
+
+ {" "}
+
+- On the page that appears, click on Settings(on the menu on your left). This will
+display a dropdown. Click on API Key from the dropdown options.
+
+ {" "}
+
+- On the page that appears, enter your password and click Generate.
+
+ {" "}
+
+- Copy the API Key generated and paste it into the Novu platform or record it somewhere
+safe for later use because you will not see it from the dashboard on subsequent visits.
+
+
+ Once you've generated your API Key, wait about 3 minutes before testing it.
+
+
+## Getting your Sender's ID
+
+Sender IDs allow you to brand your messages as you send them to your customers. There are two kinds of sender IDs, **shortcodes** and **alphanumerics**. The difference is that you can send and receive messages with shortcodes but only send messages with an alphanumeric.
+
+To create a Short Code:
+
+- On your app dashboard, click on SMS (on the menu on your left). This will display a dropdown. Click on `Shortcodes` from the dropdown options. This will also display a dropdown from which you can then click on `My Shortcodes` to view your codes.
+
+ {" "}
+
+- If you have not created one yet, on the `Shortcodes` dropdown option, click on `Request`.
+ On the page that appears, fill in the form and submit.
+
+ {" "}
+
+
+## To create an Alphanumeric:
+
+- On your app dashboard, click on SMS (on the menu on your left). This will display a dropdown. Click on `Alphanumerics` from the dropdown options. This will also display a dropdown from which you can then click on `My Alphanumerics` to view your codes.
+
+
+ {" "}
+
+
+- If you have not created one yet, on the `Alphanumerics` dropdown option, click on `Request`.
+ On the page that appears, fill in the form and submit
+
+ {" "}
+
+- Once you're done, add either your short code or alphanumeric to the `from` field on the Novu platform.
+
+## Creating an Africa's Talking integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-africastalking) page on Novu.
+- Click the "Add a provider" button.
+- Locate **Africa's Talking** under the SMS section and click on the **Connect** button.
+- Enter the `API key`.
+- Fill in the `username` field.
+- Fill in the `from` field. This is your registered `short code` or `alphanumeric` value.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Connect** button.
+
+Now it is possible to send SMS notifications using Africa's Talking in Novu.
diff --git a/integrations/providers/sms/aws-sns.mdx b/integrations/providers/sms/aws-sns.mdx
new file mode 100644
index 00000000..ad977382
--- /dev/null
+++ b/integrations/providers/sms/aws-sns.mdx
@@ -0,0 +1,30 @@
+---
+title: "AWS SNS"
+description: "Learn how to use the AWS SNS provider to send sms notifications using Novu"
+---
+
+You can use the [AWS SNS](https://aws.amazon.com/sns/) provider to send transactional emails to your customers using the Novu Platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+To integrate AWS SNS on the Novu platform, you will need to have in AWS, an IAM user who has the `sns:Publish` permission.
+
+# Create User
+
+To create a user, log in to AWS Console and follow these steps:
+
+- Go to the `IAM` service page.
+- Create a new user with `sns:Publish` permission, or add `sns:Publish` permission to an existing user.
+- Add the `Access Key` credential to the user and copy the `Access key ID` and the `Secret access key`.
+
+For security reasons, it is suggested that you create a new User to use with Novu.
+
+# Create an AWS SNS integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sms-sns) page on Novu.
+- Click the "Add a provider" button.
+- Locate **Amazon SNS** and click on the **Connect** button.
+- Enter your `Access Key ID`, `Secret Access key`, and `AWS region`.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Save** button.
+- You should now be able to send SMS notifications using **Amazon SNS** in Novu.
diff --git a/integrations/providers/sms/azure.mdx b/integrations/providers/sms/azure.mdx
new file mode 100644
index 00000000..2dc0e7df
--- /dev/null
+++ b/integrations/providers/sms/azure.mdx
@@ -0,0 +1,24 @@
+---
+title: "Azure SMS"
+description: "Learn how to use the Azure SMS provider to send sms notifications using Novu"
+---
+
+You can use the [Azure SMS](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/sms/send?tabs=windows&pivots=platform-azcli) SMS provider to send SMS messages to the customers using the Novu Platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+To use Azure SMS provider in the SMS channel, the first step is to create an Azure account and grab the communication services connection string from your account dashboard.
+
+# Creating a Azure SMS integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sms-azure) page on Novu.
+- Click the "Add a provider" button.
+- Locate **Azure Sms** under the SMS section and click on the **Next** button.
+- Select Your Environment add condition (Optional).
+- Click on the `Create` button.
+- Click on the `Disabled` button and mark it as `Active`.
+- Enter the `Connection string`.
+- Enter the `From` value.
+- Click on the **Update** button.
+
+Now it is possible to send SMS notifications using **Azure Sms** in Novu.
diff --git a/integrations/providers/sms/bulk-sms.mdx b/integrations/providers/sms/bulk-sms.mdx
new file mode 100644
index 00000000..d4a490ac
--- /dev/null
+++ b/integrations/providers/sms/bulk-sms.mdx
@@ -0,0 +1,23 @@
+---
+title: "BulkSMS"
+description: "Learn how to use the BulkSMS provider to send sms notifications using Novu"
+---
+
+You can use the [BurstSMS](https://bulksms.com/) provider to send SMS messages to the customers using the Novu Platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+To use BulkSMS provider in the SMS channel, the first step is to create a BulkSMS account and create an [API token](https://www.bulksms.com/account/#!/advanced-settings/api-tokens).
+
+# Creating a BulkSMS integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co.co/integrations?utm_campaign=docs-sms-bulksms) page on Novu.
+- Click the "Add a provider" button.
+- Locate **BulkSMS** under the SMS section and click on the **Next** button.
+- Select Your Environment add condition (Optional).
+- Click on the `Create` button.
+- Click on the `Disabled` button and mark it as `Active`.
+- Enter the `API Token`.
+- Click on the **Update** button.
+
+Now it is possible to send SMS notifications using **BulkSMS** in Novu.
diff --git a/integrations/providers/sms/burst-sms.mdx b/integrations/providers/sms/burst-sms.mdx
new file mode 100644
index 00000000..ec659199
--- /dev/null
+++ b/integrations/providers/sms/burst-sms.mdx
@@ -0,0 +1,25 @@
+---
+title: "BurstSMS"
+description: "Learn how to use the BurstSMS provider to send sms notifications using Novu"
+---
+
+You can use the [BurstSMS](https://burstsms.com/) provider to send SMS messages to the customers using the Novu Platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+To use BurstSMS provider in the SMS channel, the first step is to create an BurstSMS account and add the personal BurstSMS API key and your BurstSMS API Secret to the BurstSMS integration on the Novu platform.
+
+# Find the API and Secret key
+
+To find the BurstSMS API and Secret key, log into the personal BurstSMS account and navigate to the API Keys section tab present at the end of the Navbar. It is suggested to create a new API and Secret key for use with Novu. Copy the newly created API and Secret key.
+
+# Creating a BurstSMS integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sms-burstsms) page on Novu.
+- Click the "Add a provider" button.
+- Locate **BurstSMS** under the SMS section and click on the **Next** button.
+- Select Your Environment add condition (Optional)
+- Click on the `Disabled` button and mark it as `Active`.
+- Enter the `API key` and `API Secret`.
+- Click on the **Upload** button.
+- Now it is possible to send SMS notifications using **BurstSMS** in Novu.
diff --git a/integrations/providers/sms/clickatell.mdx b/integrations/providers/sms/clickatell.mdx
new file mode 100644
index 00000000..fa21da39
--- /dev/null
+++ b/integrations/providers/sms/clickatell.mdx
@@ -0,0 +1,62 @@
+---
+title: "Clickatell"
+description: "Learn how to use the Clickatell provider to send sms notifications using Novu"
+---
+
+You can use the [Clickatell](https://www.clickatell.com/) provider to send SMS messages to your customers using the Novu Platform.
+Let's see how to do it:
+
+## Getting Started
+
+To use the [Clickatell](https://www.clickatell.com/) provider in the SMS channel, the first step is to [create a Clickatell account](https://www.clickatell.com/sign-up/) and add your API key to the Clickatell integration on the [Novu web dashboard](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sms-clickatell).
+
+## Retrieving your API Key
+
+To find your Clickatell API key:
+
+- [Sign up](https://www.clickatell.com/sign-up/) or [Login](https://app.clickatell.com/signin) to your Clickatell account.
+- Click on the `My Workspace`, and then click `SMS` in Channels Section.
+
+
+ {" "}
+
+
+- Click on the `New SMS Setup`.
+
+
+ {" "}
+
+
+- Select `API` then `Messaging Type` and click Next.
+
+
+ {" "}
+
+
+- Select `Basic HTTP API` then `create a new HTTP API` and click Next.
+
+
+ {" "}
+
+
+- Now fill in all details and Click Next.
+
+
+ {" "}
+
+
+- Copy the API key and click complete.
+
+ {" "}
+
+
+## Create a Clickatell integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sms-clickatell) page on Novu.
+- Click the "Add a provider" button.
+- Locate **Clickatell** under the SMS section and click on the **Connect** button.
+- Enter your Clickatell API Key.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Connect** button.
+
+Now it is possible to send SMS notifications using **Clickatell** in Novu.
diff --git a/integrations/providers/sms/clicksend.mdx b/integrations/providers/sms/clicksend.mdx
new file mode 100644
index 00000000..bd7659fe
--- /dev/null
+++ b/integrations/providers/sms/clicksend.mdx
@@ -0,0 +1,31 @@
+---
+title: "Clicksend"
+description: "Learn how to use the Clicsend provider to send sms notifications using Novu"
+---
+
+You can use the [Clicksend](https://www.clicksend.com/in/) provider to send SMS messages to your customers using the Novu Platform.
+Let's see how to do it:
+
+# Getting Started
+
+To use the [Clicksend](https://www.clicksend.com/in/) provider in the SMS channel, the first step is to [create a Clicksend account](https://dashboard.clicksend.com/signup/step1) and add your API key to the Clicksend integration on the [Novu web dashboard](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sms-clicksend).
+
+# Retrieving your API Key
+
+- **Generate an API Key**: After logging into your ClickSend account, navigate to the API Keys section. This is usually located in your account settings or developer dashboard.
+- **Create a New API Key**: It's recommended to create a new API key specifically for your integration with Novu. Click on the option to create a new API key, and it will be generated for you.
+- **Copy the API Key**: Once your API key is created, make sure to copy it to your clipboard. You will need this key to connect ClickSend with Novu.
+
+# Create a Clicksend integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sms-clicksend) page on Novu.
+- Click on Add a Provider.
+- Select Clicksend service.
+- Choose your preferred deployment environment: `Development` or `Production`. Then Click the `Create` button.
+- Enter your Clicksend Username.
+- Enter your Clicksend API Key.
+- Fill in the `From` field.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Update** button.
+
+Now it is possible to send SMS notifications using **Clicksend** in Novu.
diff --git a/integrations/providers/sms/firetext.mdx b/integrations/providers/sms/firetext.mdx
new file mode 100644
index 00000000..a54f6d2a
--- /dev/null
+++ b/integrations/providers/sms/firetext.mdx
@@ -0,0 +1,46 @@
+---
+title: "Firetext"
+description: "Learn how to use the firetext provider to send sms notifications using Novu"
+---
+
+You can use the [firetext](https://www.firetext.co.uk) provider to send SMS messages to your customers using the Novu Platform with a single API to create multi-channel experiences.
+
+## Getting Started
+
+To use the firetext provider in the SMS channel, the first step is to create a firetext account and add your API key and Sender ID to the firetext integration on the Novu platform.
+
+- Note : Firetext is only available in uk region. Others may require additional verification.
+
+## Retrieving your API Key
+
+To find your firetext API key:
+
+- [Sign up](https://app.firetext.co.uk/signup) or [Login](https://app.firetext.co.uk/) to your firetext account.
+- Click on the `Settings` icon in the top right corner of the screen.
+
+
+ {" "}
+
+
+- Now Click on the `API` in `My Settings` Section.
+
+
+ {" "}
+
+
+- On the API Keys page, copy the `API key`.
+
+ {" "}
+
+
+## Create a firetext integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sms-firetxt) page on Novu.
+- Click the "Add a provider" button.
+- Locate **firetext** under the SMS section and click on the **Connect** button.
+- Enter your `firetext` API Key.
+- Fill in the `From` field.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Connect** button.
+
+Now it is possible to send SMS notifications using **firetext** in Novu.
diff --git a/integrations/providers/sms/gupshup.mdx b/integrations/providers/sms/gupshup.mdx
new file mode 100644
index 00000000..14d82b33
--- /dev/null
+++ b/integrations/providers/sms/gupshup.mdx
@@ -0,0 +1,27 @@
+---
+title: "gupshup"
+description: "Learn how to use the gupshup provider to send sms notifications using Novu"
+---
+
+You can use the [gupshup](https://www.gupshup.io/) provider to send SMS messages to your customers using the Novu Platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+To use the gupshup provider in the SMS channel, the first step is to create a gupshup account and add the Account Username and Password to the gupshup integration on the Novu platform.
+
+# What is User id and Password?
+
+- `userid` : The account number provided by the Enterprise SMS GupShup.
+- `Password` : Password is provided by Gupshup for authentication of user id. The password must be the same as used to log on to the Enterprise SMS GupShup website.
+
+# Creating a gupshup integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sms-gupshup) page on Novu.
+- Click the "Add a provider" button.
+- Locate **gupshup** under the SMS section and click on the **Connect** button.
+- Enter the `User id`.
+- Enter the `Password`.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Connect** button.
+
+Now it is possible to send SMS notifications using **gupshup** in Novu.
diff --git a/integrations/providers/sms/infobip.mdx b/integrations/providers/sms/infobip.mdx
new file mode 100644
index 00000000..d168b13f
--- /dev/null
+++ b/integrations/providers/sms/infobip.mdx
@@ -0,0 +1,59 @@
+---
+title: "Infobip - SMS"
+description: "Learn how to use the Infobip provider to send sms notifications using Novu"
+---
+
+You can use the [Infobip](https://www.infobip.com/developers/) provider to send SMS messages to your customers using the Novu Platform with a single API to create multi-channel experiences.
+
+## Getting Started
+
+To use the Infobip provider in the SMS channel, you will need to create an Infobip account and add your API key and Base URL to the Infobip integration on the Novu platform.
+
+## Retrieving your API Key
+
+To Find a new API key in Infobip, you can follow these steps:
+
+- [Sign up](https://www.infobip.com/signup) or [Log in](https://portal.infobip.com/login/) to your Infobip account.
+- On the [Homepage](https://portal.infobip.com/homepage/), you'll see your API key.
+
+
+ {" "}
+
+Alternatively, you can find the API key on the Developer Tools page.
+
+- On the sidebar (on the left), click on `Developer Tools`.
+- This will display a dropdown from which you can then click on `API Ksys` to view your API key.
+
+
+ {" "}
+
+
+If you want to create an API key:
+
+- Click on the `CREATE API KEY` button
+- Fill the form
+- Click on `GENERATE`
+
+ {" "}
+
+
+## Finding the Base URL
+
+To see your base URL, log in to the account. Once logged in, on the Homepage, you should see your base URL in this format: `xxxxx.api.infobip.com`.
+
+
+ {" "}
+
+
+## Create an Infobip integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sms-infobip) page on Novu.
+- Click the "Add a provider" button.
+- Locate **Infobip** under the SMS section and click on the **Connect** button.
+- Enter your Infobip API Key.
+- Enter your Base URL.
+- Fill in the `From` field.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Connect** button.
+
+You should now be able to send SMS notifications using Infobip in Novu.
diff --git a/integrations/providers/sms/kannel.mdx b/integrations/providers/sms/kannel.mdx
new file mode 100644
index 00000000..2707c68e
--- /dev/null
+++ b/integrations/providers/sms/kannel.mdx
@@ -0,0 +1,79 @@
+---
+title: "Kannel"
+description: "Learn how to use the Kannel sms provider to send sms notifications using Novu"
+---
+
+Before integrating Kannel with Novu, you should have Kannel set up and configured as an SMS gateway on your server. Let’s look at how you can do that:
+
+# Setting up Kannel
+
+1. First, you need to install Kannel on your server. You can download it from the [official-website](https://www.kannel.org/download.shtml) or use a package manager specific to your operating system (e.g., apt-
+ get for Ubuntu, yum for CentOS, etc.).
+2. Kannel's configuration is done through a file called kannel.conf. You need to edit this file to specify your SMS provider settings. Here's a basic configuration example:
+
+ ```bash
+ group = smsc
+ smsc = smpp
+ smsc-id = YourSMSCID
+ host = SMSC_Hostname_or_IP
+ port = SMSC_Port
+ system-type = SMSC_System_Type
+ smsc-username = YourUsername
+ smsc-password = YourPassword
+ max-pending-submits = 10
+ allow-ip = "127.0.0.1"
+ ```
+
+ Replace the placeholders (e.g., **YourSMSCID**, **SMSC_Hostname_or_IP**, **SMSC_Port**, **SMSC_System_Type**, **YourUsername**, and **YourPassword**) with the actual values provided by your SMS provider.
+
+3. You can define services that will handle incoming and outgoing SMS messages. These services specify how Kannel should process SMS requests. Here's an example of an SMS service configuration:
+
+ ```bash
+ group = sendsms-user
+ username = YourUsername
+ password = YourPassword
+ concatenation = true
+ max-messages = 3
+ ```
+
+ Adjust the settings to your needs.
+
+4. Start Kannel with the following command:
+
+ ```bash
+ bearerbox /path/to/kannel.conf
+ ```
+
+ Make sure to replace /path/to/kannel.conf with the actual path to your Kannel configuration file.
+
+# Creating a Kannel integration with Novu
+
+After setting up your Kannel, follow these steps to integrate with novu:
+
+- Visit the [Integrations Store](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sms-kannel) on Novu.
+- Click the "Add a provider" button.
+- Select Kannel service.
+- Choose your preferred deployment environment: `Development` or `Production`. Then Click the `Create` button.
+- Once the integration is active, you'll need to configure it. This configuration usually involves providing the necessary details for Novu to connect to your Kannel SMS gateway. These details typically include:
+
+ - **Kannel Gateway URL or IP Address**: If your Kannel SMS gateway is hosted on your server, you should use the server's IP address or hostname.
+ - **Port**: The port number through which Novu should communicate with Kannel (usually 13013, or a custom port you've configured in Kannel).
+ - **Username and Password**: If you've set up authentication for your Kannel SMS gateway, provide the username and password required for authentication:
+
+ ```bash
+ group = smsc
+ smsc = smpp
+ smsc-id = YourSMSCID
+ host = SMSC_Hostname_or_IP
+ port = SMSC_Port
+ smsc-username = YourUsername
+ smsc-password = YourPassword
+ max-pending-submits = 10
+ allow-ip = "127.0.0.1"
+ ```
+
+ In this example, **YourUsername** and **YourPassword** are the credentials you'd use for authentication.
+
+- Fill in the `From` field.
+- Click on the `Update` button.
+- You should now be able to send notifications using Kannel in Novu.
diff --git a/integrations/providers/sms/messagebird.mdx b/integrations/providers/sms/messagebird.mdx
new file mode 100644
index 00000000..0d018dd9
--- /dev/null
+++ b/integrations/providers/sms/messagebird.mdx
@@ -0,0 +1,23 @@
+---
+title: "MessageBird"
+description: "Learn how to use the messagebird provider to send sms notifications using Novu"
+---
+
+You can use the [MessageBird](https://www.messagebird.com) provider to send SMS messages to your customers using the Novu Platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+To use the MessageBird provider in the SMS channel, the first step is to create a [MessageBird account](https://www.messagebird.com/en/sign-up) and then generate an `Access Key` within the developers' section section of your MessageBird account. This access key is essential for authenticating your requests to the MessageBird API.
+
+# Creating a MessageBird integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sms-messagebird) page on Novu.
+- Click the "Add a provider" button.
+- Locate **MessageBird** under the SMS section. Click it and the **Next** button.
+- Select Your Environment add condition (Optional).
+- Click on the `Disabled` button and mark it as `Active`.
+- Enter the `Access Key` value.
+- Enter the `From` value.
+- Click on the **Update** button.
+
+Now it is possible to send SMS notifications using **MessageBird** in Novu.
diff --git a/integrations/providers/sms/nexmo.mdx b/integrations/providers/sms/nexmo.mdx
new file mode 100644
index 00000000..bb503668
--- /dev/null
+++ b/integrations/providers/sms/nexmo.mdx
@@ -0,0 +1,25 @@
+---
+title: "Nexmo"
+description: "Learn how to use the Nexmo provider to send sms notifications using Novu"
+---
+
+You can use the [Nexmo By Vonage](https://www.vonage.com/) provider to send SMS messages to your customers using the Novu Platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+To use the Nexmo provider in the SMS channel, the first step is to create a Nexmo account and add the API key and API Secret to the Nexmo integration on the Novu platform.
+
+# Finding the API key and secret
+
+First, [sign up for a Vonage account](https://ui.idp.vonage.com/ui/auth/login) if you don't already have one, and make a note of your API key and secret on the [dashboard getting started page](https://dashboard.nexmo.com/getting-started-guide).
+
+# Create a Nexmo integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sms-nexmo) page on Novu.
+- Click the "Add a provider" button.
+- Locate **Nexmo** under the SMS section and click on the **Connect** button.
+- Enter the `API key` and `API Secret`.
+- Enter a valid `From` email address.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Connect** button.
+- Now it is possible to send SMS notifications using **Nexmo** in Novu.
diff --git a/integrations/providers/sms/overview.mdx b/integrations/providers/sms/overview.mdx
new file mode 100644
index 00000000..c3d54bdf
--- /dev/null
+++ b/integrations/providers/sms/overview.mdx
@@ -0,0 +1,30 @@
+---
+title: "SMS Providers Integration"
+sidebarTitle: "Overview"
+description: "Configure and manage SMS notification providers like Twilio, Nexmo, and others with Novu's notification infrastructure."
+icon: "circle-info"
+---
+
+Novu makes SMS notifications simple, scalable, and reliable, enabling seamless integration with your communication stack. Whether you're sending OTPs, updates, or transactional messages, Novu ensures your SMS notifications are delivered efficiently and effectively.
+
+With the SMS channel, you can:
+
+- **Switch Providers Effortlessly:** Integrate popular services like Twilio, Nexmo, or a custom provider
+- **Deliver at Scale:** Handle high-volume messaging with confidence
+- **Customize and Track:** Tailor SMS content dynamically and monitor delivery status in real time
+
+## Key Features
+
+- **Dynamic Messaging:** Inject user-specific data into messages for personalization
+- **Multi-Provider Support:** Switch or combine providers to maximize reliability
+- **Delivery Insights:** Track message delivery, failures, and user engagement
+- **Fallback Mechanisms:** Ensure reliable messaging with backup providers
+- **Template Management:** Simplify content creation with reusable SMS templates
+- **Streamlined API Integration:** Easily connect your backend for automated messaging workflows
+
+## Common Use Cases
+
+- **Transactional Notifications:** Send OTPs, receipts, or order updates instantly
+- **Marketing Campaigns:** Deliver promotional offers and updates to your audience
+- **Critical Alerts:** Notify users of urgent events, like security breaches or system outages
+- **Reminders and Scheduling:** Automate reminders for appointments, events, or deadlines
\ No newline at end of file
diff --git a/integrations/providers/sms/plivo.mdx b/integrations/providers/sms/plivo.mdx
new file mode 100644
index 00000000..9266c96d
--- /dev/null
+++ b/integrations/providers/sms/plivo.mdx
@@ -0,0 +1,27 @@
+---
+title: "Plivo"
+description: "Learn how to use the Plivo provider to send sms notifications using Novu"
+---
+
+You can use the [Plivo](https://www.plivo.com/) provider to send SMS messages to your customers using the Novu Platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+To use the Plivo provider in the SMS channel, the first step is to create a Plivo account and add the Account SID and Auth token to the Plivo integration on the Novu platform.
+
+# Finding the Account SID and Auth token
+
+[Sign up](https://console.plivo.com/accounts/register/) or [Login](https://console.plivo.com/accounts/login/) to your Plivo account, and make a note of your Account SID and Auth token on the [Plivo console page](https://console.plivo.com/dashboard/).
+
+# Creating a Plivo integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sms-plivo) page on Novu.
+- Click the "Add a provider" button.
+- Locate **Plivo** under the SMS section and click on the **Connect** button.
+- Enter the `Account SID`.
+- Enter the `Auth token`.
+- Fill in the `From` field.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Connect** button.
+
+Now it is possible to send SMS notifications using **Plivo** in Novu.
diff --git a/integrations/providers/sms/sendchamp.mdx b/integrations/providers/sms/sendchamp.mdx
new file mode 100644
index 00000000..003de338
--- /dev/null
+++ b/integrations/providers/sms/sendchamp.mdx
@@ -0,0 +1,67 @@
+---
+title: "Sendchamp"
+description: "Learn how to use the Sendchamp provider to send sms notifications using Novu"
+---
+
+You can use the [Sendchamp](https://www.sendchamp.com/) provider to send SMS messages to your customers using the Novu Platform with a single API to create multi-channel experiences.
+
+## Getting Started
+
+To use the Sendchamp provider in the SMS channel, the first step is to create a Sendchamp account and add your API key and Sender ID to the Sendchamp integration on the Novu platform.
+
+## Retrieving your API Key
+
+To find your Sendchamp API key:
+
+- [Sign up](https://my.sendchamp.com/signup) or [Login](https://my.sendchamp.com/login) to your Sendchamp account.
+- Click on the Avatar icon in the top right corner of the screen, and then click `API & Integrations` from the drop-down menu.
+
+
+ {" "}
+
+ Alternatively, you can access the API key from the Accounts menu.
+
+- Scroll to the bottom of the sidebar (on the left) and click on `Accounts`.
+- This will display a dropdown from which you can then click on `API keys & Webhooks` to view your API key.
+
+ {" "}
+
+- On the API Keys page, copy the Public access key.
+
+ {" "}
+
+
+## Get your Sender's ID
+
+The Sender ID represents the sender of the message to your customers.
+
+To get your Sender's ID:
+
+- On the sidebar (on the menu on your left), click on `SMS`. This will display a dropdown. Click on `Sender ID` from the dropdown options.
+- On the page that appears, you'll find a list of your Sender IDs
+
+
+ {" "}
+
+ If you have not created one yet:
+
+- Click on the `Create Sender ID` button to request a Sender ID.
+- Fill in the form.
+- Click on `Add Sender ID` button.
+
+ {" "}
+
+
+Once it is approved, you can use your Sender ID as the from field on the Novu platform.
+
+## Create a Sendchamp integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sms-sendchamp) page on Novu.
+- Click the "Add a provider" button.
+- Locate **Sendchamp** under the SMS section and click on the **Connect** button.
+- Enter your Sendchamp API Key.
+- Fill in the `From` field.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Connect** button.
+
+Now it is possible to send SMS notifications using **Sendchamp** in Novu.
diff --git a/integrations/providers/sms/simpletexting.mdx b/integrations/providers/sms/simpletexting.mdx
new file mode 100644
index 00000000..5ac0b056
--- /dev/null
+++ b/integrations/providers/sms/simpletexting.mdx
@@ -0,0 +1,24 @@
+---
+title: "SimpleTexting"
+description: "Learn how to use the SimpleTexting provider to send sms notifications using Novu"
+---
+
+You can use the [SimpleTexting](https://simpletexting.com/) SMS provider to send SMS messages to the customers using the Novu Platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+To use SimpleTexting provider in the SMS channel, the first step is to create a SimpleTexting account and grab your `API KEY` in your settings.
+
+# Creating a SimpleTexting integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sms-simpletexting) page on Novu.
+- Click the "Add a provider" button.
+- Locate **SimpleTexting** under the SMS section and click on the **Next** button.
+- Select Your Environment add condition (Optional).
+- Click on the `Create` button.
+- Click on the `Disabled` button and mark it as `Active`.
+- Enter the `API Key`.
+- Enter the `From` value.
+- Click on the **Update** button.
+
+Now it is possible to send SMS notifications using **SimpleTexting** in Novu.
diff --git a/integrations/providers/sms/sms-central.mdx b/integrations/providers/sms/sms-central.mdx
new file mode 100644
index 00000000..8ade8930
--- /dev/null
+++ b/integrations/providers/sms/sms-central.mdx
@@ -0,0 +1,23 @@
+---
+title: "SMS Central"
+description: "Learn how to use the SMS Central provider to send sms notifications using Novu"
+---
+
+You can use the [SMS Central](https://www.smscentral.com.au/) provider to send SMS messages to your customers using the Novu platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+To use the SMS Central provider in the sms channel, you will need to create a SMS Central account and add your SMS Central `Username` & `Password` to the SMS Central integration on the Novu platform.
+
+# Creating the SMS Central integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sms-smscentral) page on Novu.
+- Click the "Add a provider" button.
+- Select SMS Central service
+- Click `Next`
+- Choose your preferred deployment environment: `Development` or `Production`. Then Click the `Create` button.
+- Add `Username` & `Password`
+- Fill up the `From` section with the number you wish to send sms from.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Update** button.
+- You should now be able to send notifications using SMS Central in Novu.
diff --git a/integrations/providers/sms/sms77.mdx b/integrations/providers/sms/sms77.mdx
new file mode 100644
index 00000000..03ec886b
--- /dev/null
+++ b/integrations/providers/sms/sms77.mdx
@@ -0,0 +1,24 @@
+---
+title: "SMS77"
+description: "Learn how to use the SMS77 provider to send sms notifications using Novu"
+---
+
+It is possible to use the [SMS77](https://www.sms77.io/en) provider to send SMS messages to the customers using the Novu Platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+To use the SMS77 provider in the SMS channel, the first step is to create an SMS77 account and add the personal API key to the SMS77 integration on the Novu platform.
+
+# Find the API key
+
+To find the SMS77 API key, log into the personal SMS77 account and navigate to the API Keys page by clicking on the 'Developer' tab present at the end of the sidebar. It is suggested to create a new API key for use with Novu. Copy the newly created API key.
+
+# Creating an SMS77 integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sms-sms77) page on Novu.
+- Click the "Add a provider" button.
+- Locate **SMS77** under the SMS section and click on the **Connect** button.
+- Enter the `API key`.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the **Save** button.
+- Now it is possible to send SMS notifications using **SMS77** in Novu.
diff --git a/integrations/providers/sms/sns.mdx b/integrations/providers/sms/sns.mdx
new file mode 100644
index 00000000..798c3305
--- /dev/null
+++ b/integrations/providers/sms/sns.mdx
@@ -0,0 +1,25 @@
+---
+title: "SNS"
+description: "Learn how to use the SNS provider to send sms notifications using Novu"
+---
+
+You can use the [SNS](https://aws.amazon.com/sns/) provider to send transactional emails to your customers using the Novu Platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+Before you can use SNS as your SMS provider in the Novu platform, you'll need to set up an Amazon Web Services (AWS) account and configure the necessary settings. Here are the steps to get started:
+
+- **Create an AWS Account**: If you don't already have an AWS account, you'll need to create one. You can sign up for an AWS account on the [AWS website](https://aws.amazon.com/).
+- **Set Up Amazon SNS**: After you've created your AWS account, navigate to the AWS Management Console. In the Services menu, locate and click on "Simple Notification Service (SNS)." Follow the prompts to set up SNS for your account.
+- **Configure SMS Preferences**: In your SNS dashboard, configure your SMS preferences. This includes setting up your sender ID and opting in for SMS messaging.
+- **Create Access Keys**: To access SNS programmatically, you'll need to create AWS Access Keys. These keys are used to authenticate your integration with the Novu platform. Go to the [AWS Identity and Access Management console](https://signin.aws.amazon.com/) to create access keys.
+
+# Create an SNS integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-sms-sns) page on Novu.
+- Click the "Add a provider" button.
+- Choose your preferred deployment environment: `Development` or `Production`. Then Click the `Create` button.
+- Enter your `Access Key ID`, `Secret Access key`, and `AWS region`.
+- Click on the `Disabled` button and mark it as `Active`.
+- Click on the `Update` button.
+- You should now be able to send SMS notifications using **SNS** in Novu.
diff --git a/integrations/providers/sms/telnyx.mdx b/integrations/providers/sms/telnyx.mdx
new file mode 100644
index 00000000..cdd34c7d
--- /dev/null
+++ b/integrations/providers/sms/telnyx.mdx
@@ -0,0 +1,47 @@
+---
+title: "Telnyx"
+description: "Learn how to use the Telnyx provider to send sms notifications using Novu"
+---
+
+You can use the [Telnyx](https://telnyx.com/) provider to send transactional emails to your customers using the Novu Platform with a single API to create multi-channel experiences.
+
+# Getting Started
+
+To use the Telnyx provider in the SMS channel, you will need to create a Telnyx account and add your API key to the Telnyx integration on the Novu platform.
+
+# Finding the API Key
+
+To find your Telnyx API key:
+
+- Log into your Telnyx account.
+- Navigate to Account Setting.
+- Then go to [Keys & Credentials](https://portal.telnyx.com/#/app/api-keys).
+- You can copy the API key from there.
+- Alternatively, you can create an API key by tapping on the Create API Key button.
+
+Before copying it, make sure its status is showing active.
+
+# Finding the Message Profile ID
+
+To get your Message profile ID:
+
+- Navigate to [Messaging](https://portal.telnyx.com/#/app/messaging) to find the profiles.
+- Make sure you created a Telnyx Messaging Profile previously. Learn more [here](https://developers.telnyx.com/docs/v2/messaging/quickstarts/portal-setup).
+- Go to the active Messaging Profile.
+- Copy the profile ID.
+
+# Making sure that the From address is valid
+
+A valid from address must be a valid phone number in +E.164 format, a short code, or an alphanumeric sender ID associated with the sending messaging profile. Alphanumeric sender IDs must be between 1 and 11 characters long, and can only contain ASCII letters, numbers, and spaces. They must contain at least one letter.
+
+# Creating a Telnyx integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations) page on the Novu.
+- Click the "Add a provider" button.
+- Locate Telnyx and click on the **Connect** button.
+- Enter your Telnyx API Key.
+- Fill in the Message profile ID field.
+- Enter the Valid From address.
+- Click on the `Disabled` button and `mark it as Active`.
+- Click on the **Save** button.
+- You should now be able to send notifications using Telnyx in Novu.
diff --git a/integrations/providers/sms/termii.mdx b/integrations/providers/sms/termii.mdx
new file mode 100644
index 00000000..bd819aa4
--- /dev/null
+++ b/integrations/providers/sms/termii.mdx
@@ -0,0 +1,64 @@
+---
+title: "Termii"
+description: "Learn how to use the Termii provider to send sms notifications using Novu"
+---
+
+You can use the [Termii](https://termii.com/) provider to send SMS messages to your customers using the Novu Platform with a single API to create multi-channel experiences.
+
+## Getting Started
+
+To use the Termii provider in the SMS channel, the first step is to create a Termii account and add your API key and Sender ID to the Termii integration on the Novu platform.
+
+## Retrieving your API Key
+
+To find your Termii API key:
+
+- [Sign up](https://accounts.termii.com/#/register) or [Login](https://accounts.termii.com/#/login) to your Termii account.
+- Navigate to your [dashboard](https://accounts.termii.com/#/).
+- Then scroll to the bottom of the page to find your API key.
+
+
+ {" "}
+
+ Alternatively, you can find the API key on the Settings page.
+
+- Scroll to the bottom of the sidebar (on the left) and click on settings.
+- This will display a dropdown from which you can then click on `API Token` to view your API key.
+
+ {" "}
+
+
+## Getting your Sender's ID
+
+Sender IDs allow you to brand your messages as you send them to your customers.
+
+To get your Sender's ID:
+
+- On the sidebar (on the menu on your left), click on `Rental`. This will display a dropdown. Click on `SMS Sender IDs` from the dropdown options.
+- On the page that appears, you'll find a list of your Sender IDs
+
+
+ {" "}
+
+
+If you have not created one yet:
+
+- Click on the `Make a new request` button to request a Sender ID.
+- Fill in the form
+- Click on `Save`
+
+
+ {" "}
+
+
+## Creating a Termii integration with Novu
+
+- Visit the [Integrations](https://dashboard-v2.novu.co/integrations) page on Novu.
+- Click the "Add a provider" button.
+- Locate **Termii** under the SMS section and click on the **Connect** button.
+- Enter your Termii API Key.
+- Fill in the `From` field.
+- Click on the Disabled button and mark it as Active.
+- Click on the **Connect** button.
+
+Now it is possible to send SMS notifications using **Termii** in Novu.
diff --git a/integrations/providers/sms/twilio.mdx b/integrations/providers/sms/twilio.mdx
new file mode 100644
index 00000000..92c31b32
--- /dev/null
+++ b/integrations/providers/sms/twilio.mdx
@@ -0,0 +1,54 @@
+---
+title: "Twilio"
+description: "Learn how to use the Twilio provider to send sms notifications using Novu"
+---
+
+You can utilize the [Twilio](https://www.twilio.com/) API to communicate with your customers using SMS messaging. Let’s look at how you can do that:
+
+# Setting up Twilio
+
+1. First, go to [Twilio](https://www.twilio.com/) and create an account, probably starting with their free trial.
+2. You’ll be asked to verify your email and your phone number. Get them verified.
+3. Once you’re done with that, you’ll get an option to ‘get a Twilio phone number’ from your Twilio console.
+4. Click on it to get your Twilio phone number.
+
+When first using Twilio there should be an option to get a Twilio phone number in the main console. Otherwise, you may have to [Buy](https://console.twilio.com/us1/develop/phone-numbers/manage/search?frameUrl=%2Fconsole%2Fphone-numbers%2Fsearch%3Fx-target-region%3Dus1¤tFrameUrl=%2Fconsole%2Fphone-numbers%2Fsearch%3FisoCountry%3DUS%26searchTerm%3D%26searchFilter%3Dleft%26searchType%3Dnumber%26x-target-region%3Dus1%26__override_layout__%3Dembed%26bifrost%3Dtrue) a number from that link to begin using it. Assuming this is the first run though, utilize the free number given to us.
+
+Simply follow one of their many [Tutorials](https://www.twilio.com/docs/usage/requests-to-twilio) within their docs to understand every part of the process.
+
+Irrespective of the language you use, the process is the same:
+
+- Load the Account SID and Auth token into the code, using some sort of safe environment such as .env variables. Nobody should have access to this but you.
+- Create a client object from Twilio that take the SID and Token as variables then create a message with the client.
+- A message is an object that has three things:
+ - that free number from earlier,
+ - the number you would like to send to, and
+ - a body message that you want to send through SMS.
+
+# Creating a Twilio integration with Novu
+
+Creating a Twilio integration with Novu is quite simple. Just follow these steps:
+
+- Visit the [Integrations Store](https://dashboard-v2.novu.co/integrations) on Novu.
+- Click the "Add a provider" button.
+- Locate **Twilio** and click on the `Disabled` button and mark it as `Active`.
+- Click on the **Connect** button.
+- Go to your [Console](https://console.twilio.com/) on Twilio and access the Account Info section at the bottom of the page
+- Enter your `Account SID`, `Auth Token` and `Twilio Phone Number`.
+- Click on the **Save** button.
+- You should now be able to send SMS notifications using **Twilio** in Novu.
+
+## Sending an WhatsApp message with Twillio
+
+To send a WhatsApps message with Twillio integration prefix the phone number of the subscriber with `whatsapp:` as shown below.
+
+```ts
+await novu.trigger("", {
+ to: {
+ subscriberId: "",
+ phone: "whatsapp:555-4242",
+ },
+});
+```
+
+Read more about [sending a Message with the Twilio API for WhatsApp](https://www.twilio.com/docs/whatsapp/tutorial).
diff --git a/integrations/segment.mdx b/integrations/segment.mdx
new file mode 100644
index 00000000..c34b5255
--- /dev/null
+++ b/integrations/segment.mdx
@@ -0,0 +1,288 @@
+---
+title: "How To Integrate Segment With Novu"
+sidebarTitle: "Integrate with Segment"
+---
+
+[Segment](https://segment.com/) is a customer data platform that helps businesses collect, manage, and analyze customer data from multiple sources. It streamlines data integration, ensures data quality, and aids in compliance with regulations. Segment's tools enable better understanding of customer behavior and facilitate personalized customer experiences. It's useful for marketing, analytics, and product teams in diverse industries.
+
+If this is your first time working with the tool, consider consulting the 'Get Started' guide in [Segment's official documentation](https://segment.com/docs/getting-started/).
+
+## Examples Use Case
+
+**Enhancing Customer Engagement and Conversion Rates**
+
+
+
+ - The e-commerce platform uses Segment to collect and unify customer data from various sources like website visits, purchase history, and customer interactions.
+ - Segment helps in creating a unified customer profile by consolidating data from different touchpoints – mobile app usage, website browsing patterns, and previous purchase behavior.
+
+
+
+
+
+ - The data collected is analyzed to understand customer preferences, buying patterns, and engagement levels.
+ - Customers are segmented into different groups based on their behavior, interests, and purchase history. For instance, one segment might include frequent shoppers, while another might focus on those who browse but rarely purchase.
+
+
+
+
+
+
+ - Novu is used to craft and send personalized notifications to these segmented groups.
+ - For frequent shoppers, notifications about new arrivals or exclusive deals might be sent. For those who browse but rarely buy, notifications could include special discount codes or reminders about items left in the cart.
+
+
+
+
+
+
+
+ - Notifications can also be triggered based on specific customer actions. For example, if a customer views a product but doesn't make a purchase, they might receive a notification later highlighting a special offer on that product.
+
+
+
+
+
+
+
+ - Customer responses to these notifications are fed back into Segment. This data is used to refine customer profiles and segmentation strategies continuously.
+ - The effectiveness of different types of notifications is analyzed to optimize future communication.
+
+
+
+**Outcome:**
+
+- Enhanced customer engagement through personalized and timely notifications.
+
+- Improved conversion rates by targeting customers with relevant offers based on their behavior and preferences.
+
+- Greater customer loyalty due to a tailored shopping experience.
+
+## Setting Novu as a Destination
+
+As of today Segment doesn’t have a native Novu Destination integration **yet** .
+
+We will be using [Destination Functions](https://segment.com/docs/connections/functions/destination-functions/). Destination functions allow you to transform and annotate your Segment events and send them to any external tool or API without worrying about setting up or maintaining any infrastructure.
+
+### Create a destination function
+
+- From your workspace, go to `Connections > Catalog` and click the `Functions` tab.
+- Click `New Function`.
+- Select `Destination` as the function type and click `Build`.
+- After you click `Build`, a code editor appears. Use the editor to write the code for your function, configure settings, and test the function’s behavior.
+
+
+ Want to see some example functions? Check out the templates available in the
+ Functions UI, or in the open-source Segment Functions Library.
+
+
+### Code the destination function
+
+Segment invokes a separate part of the function (called a “handler”) for each event type that you send to your destination function.
+
+
+ {" "}
+ Your function isn’t invoked for an event if you’ve configured a destination filter,
+ and the event doesn’t pass the filter.{" "}
+
+
+The default source code template includes handlers for all event types. You don’t need to implement all of them - just use the ones you need, and skip the ones you don’t.
+
+Destination functions can define handlers for each message type in the Segment spec:
+
+- onIdentify
+- onTrack
+- onPage
+- onScreen
+- onGroup
+- onAlias
+- onDelete
+- onBatch
+
+Each of the functions above accepts two arguments:
+
+`event` - Segment event object, where fields and values depend on the event type.
+For example, in “Identify” events, Segment formats the object to match the Identify spec.
+
+`settings` - Set of settings for this function.
+The example below shows a destination function that listens for “Track” events, and sends some details about them to an external service.
+
+```javascript
+async function onTrack(event) {
+ await fetch("https://example-service.com/api", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ event_name: event.event,
+ event_properties: event.properties,
+ timestamp: event.timestamp,
+ }),
+ });
+}
+```
+
+To change which event type the handler listens to, you can rename it to the name of the message type.
+
+For example, if you rename this function `onIdentify`, it listens for “Identify” events instead.
+
+
+ If you you need an example of Novu destination function, you can find it in
+ [here](https://github.com/novuhq/segment-novu-destination-scripts). Feel free
+ to submit your function examples as well!
+
+
+### Sending a payload to Novu
+
+Now let's learn how to send a payload to Novu based on the `onTrack` event function:
+
+To successfully send a payload to Novu, triggering the appropriate [workflow](/concepts/workflows) and delivering it to the correct customer, please ensure that you provide the following information:
+
+1. Novu's Workflow Identifier.
+2. Subscriber ID (Please ensure that the Subscriber ID is both unique and consistent across all of your databases).
+3. Your Novu API Key.
+4. [Novu's Event Trigger API Endpoint](https://api.novu.co/events/trigger)
+
+```javascript
+async function onTrack(event, settings) {
+ const eventProperties = event.properties;
+
+ if (!eventProperties.environment) {
+ throw new InvalidEventPayload("environment property is required");
+ }
+ if (!eventProperties.name) {
+ throw new InvalidEventPayload("Workflow name property is required");
+ }
+
+ console.log("environment:", eventProperties.environment);
+
+ const payload = {
+ name: eventProperties.name,
+ to: {
+ subscriberId: eventProperties.user.subscriberId,
+ email: eventProperties.user.email,
+ firstName: eventProperties.user.firstName,
+ lastName: eventProperties.user.lastName,
+ },
+ payload: { eventProperties },
+ };
+ const endpoint = settings.novuEndpoint;
+ console.log("endpoint:", endpoint);
+
+ const ApiKey = settings.novuApiKey; // Your NOVU API key
+ if (eventProperties.environment === settings.environment) {
+ let response;
+ try {
+ response = await fetch(endpoint, {
+ method: "POST",
+ headers: {
+ Authorization: `ApiKey ${ApiKey}`,
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(payload),
+ });
+ } catch (error) {
+ // Retry on connection error
+ throw new RetryError(error.message);
+ }
+ if (response.status >= 500 || response.status === 429) {
+ // Retry on 5xx (server errors) and 429s (rate limits)
+ throw new RetryError(`Failed with ${response.status}`);
+ }
+ }
+}
+```
+
+#### Create Settings and Secrets
+
+Segment's settings allow you to pass configurable variables to your function, which is the best way to pass sensitive information such as security tokens. For example, you might use settings as placeholders to use information such as an API endpoint and API key.
+You can learn more about it [here](https://segment.com/docs/connections/functions/destination-functions/#create-settings-and-secrets).
+
+
+
+
+
+Click `Add Setting` to add your new setting.
+
+
+
+
+
+Now, while configuring the function, Add these three variables to be then used in the code exposed in the settings function param.
+
+
+
+
+
+#### Test the destination function
+
+You can test your code directly from the editor in two ways:
+
+**1. Use sample events for testing**
+Click `Use Sample Event` and select the source to use events from.
+
+
+
+
+
+Click `Run` to test your function with the event you selected.
+
+**2. Test using manual input**
+You can also manually include your own JSON payload of a Segment event, instead of fetching a sample from one of your workspace sources.
+
+
+
+
+
+**Here is the the event example JSON we will use**
+
+```JSON
+{
+ "type": "track",
+ "event": "User Abandoned Cart",
+ "properties": {
+ "name": "abandoned-cart-recovery-workflow",
+ "user": {
+ "subscriberId": "63da8ee0c037e013fd790ffd",
+ "email": "hi@hi.com",
+ "firstName": "John",
+ "lastName": "Doe"
+ },
+ "environment": "development"
+ }
+}
+```
+
+We will now execute the function based on the manual event input we've provided. This function sends a trigger to Novu, prompting to take action and send the user an email to return and the purchase.
+
+
+
+
+
+Whether you chose the first or second option to test your Destination Function, you can also check if the function triggered your workflow and whether it was delivered through Novu's [Activity Feed](https://dashboard-v2.novu.co/activities?utm_campaign=docs-guides-segment).
+
+
+
+
+
+This guide was inspired by the following resources:
+
+- https://discord.com/channels/895029566685462578/1060308007658987571/1077377322547687475
+- https://segment.com/docs/getting-started/05-data-to-destinations/
+- https://aviyel.com/post/3381/how-to-integrate-novu-with-segment
diff --git a/introduction.mdx b/introduction.mdx
index 27f9d09b..308879d5 100644
--- a/introduction.mdx
+++ b/introduction.mdx
@@ -1,6 +1,6 @@
---
title: Introduction
-description: 'Welcome to the home of novu documentation!'
+description: "Welcome to the home of novu documentation!"
---
+ {" "}
+
+
+ {" "}
+
+
+ {" "}
+
+
+ {" "}
+
+
+ {" "}
+
+
+Let's build and run our iOS application to see if everything works correctly.
+
+
+ {" "}
+
+
+Go to the Firebase documentation website, go to docs here, and select Cloud Messaging.
+We will follow this guide to set up cloud messaging inside the app.
+
+You can read more about how Firebase Cloud Messaging works by reading their [official documentation](https://firebase.google.com/docs/cloud-messaging/ios/client).
+
+
+ {" "}
+
+
+Go to iOS+ and click on the `Set up an Apple platforms client` section.
+
+
+ {" "}
+
+
+The first thing that we need to do is add Firebase to our iOS app.
+Let's create a new Firebase project inside of the console.
+
+
+ {" "}
+
+
+ {" "}
+
+
+ {" "}
+
+
+ {" "}
+
+
+## Create a Firebase project
+
+Select a name for your project.
+
+
+ {" "}
+
+
+Then, add Google Analytics to the Firebase project.
+
+
+ {" "}
+
+
+Here, you can select your Google account.
+
+
+ {" "}
+
+
+ {" "}
+
+
+We will click on Add Firebase to your iOS app.
+
+
+ {" "}
+
+
+We first need to add the bundle name, so go to your iOS Xcode project and copy and paste the bundle name.
+
+
+ {" "}
+
+
+ {" "}
+
+
+Download the `.plist` file and put it in the root of your project.
+
+
+ {" "}
+
+
+ {" "}
+
+
+You can put it right under `info.plist` file.
+
+
+ {" "}
+
+
+We need to add the Firebase SDK using the Swift package manager.
+Copy this https://github.com/firebase/firebase-ios-sdk URL and then go to 'File`->`Add Package Dependencies...`.
+Paste that URL in the top right.
+
+
+ {" "}
+
+
+ {" "}
+
+
+ {" "}
+
+
+ {" "}
+
+
+ {" "}
+
+
+ {" "}
+
+
+We must add the initialization code to the `AppDelegates.swift` file.
+
+Let's import the "FirebaseCore" dependency by adding `import FirebaseCore` to the beginning of the file.
+
+Then, we will copy `firebaseapp.configure()` and place it in `didFinishLaunchingWithOptions` method.
+
+
+ {" "}
+
+
+```Swift
+
+//
+// AppDelegate.swift
+// PushNotificationDemo
+//
+// Created by Emillien Pearce.
+//
+
+import UIKit
+import FirebaseCore //Here
+
+@main
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+
+
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ // Override point for customization after application launch.
+ FirebaseApp.configure() //Here
+ FirebaseConfiguration.shared.setLoggerLevel(.min)
+ return true
+ }
+
+ // MARK: UISceneSession Lifecycle
+
+ func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
+ // Called when a new scene session is being created.
+ // Use this method to select a configuration to create the new scene with.
+ return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
+ }
+
+ func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
+ // Called when the user discards a scene session.
+ // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
+ // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
+ }
+
+
+}
+
+```
+
+Click 'Next' and continue to the console.
+
+
+ {" "}
+
+
+ {" "}
+
+
+Let's build and run our project and see if we get Firebase log messages in the console.
+
+
+ {" "}
+
+
+ {" "}
+
+
+## Create and upload our APNs authentication key
+
+We will create and upload our APNs authentication key to the Firebase project settings.
+
+
+ {" "}
+
+
+Navigate to [Apple Developer Member Center](https://developer.apple.com/account).
+
+Head to the "Keys" section under "Certificates, IDs & Profiles".
+
+
+ {" "}
+
+
+Create a new key.
+
+
+ {" "}
+
+
+Select "Apple Push Notification Service".
+
+
+ {" "}
+
+
+Click "Register".
+
+
+ {" "}
+
+
+We have to download this key and upload it into Firebase.
+
+
+ {" "}
+
+
+Head to "Project Settings".
+
+
+ {" "}
+
+
+Click on Cloud Messaging, then click "Upload APNs Authentication Key".
+
+
+ {" "}
+
+
+Now we can upload the dots p8 file.
+
+
+ {" "}
+
+
+You must enter your `Key ID` and `Team ID`, which you can find in the top right corner.
+
+
+ {" "}
+
+
+ {" "}
+
+
+## Register for Remote Notifications
+
+
+ {" "}
+
+
+1. Copy this code block and place it inside the `AppDelegate.swift` file using the `didFinishLaunchingWithOptions` method.
+ We paste this code block under `firebaseApp.configure()`.
+
+2. We need to conform to this delegate, so we will also create an `AppDelegate` extension at the bottom for `UNUserNotificationCenterDelegate`.
+
+3. Add `import FirebaseMessaging` to the file.
+
+```Swift
+
+//
+// AppDelegate.swift
+// PushNotificationDemo
+//
+// Created by Emillien Pearce.
+//
+
+import UIKit
+import FirebaseCore
+import FirebaseMessaging
+
+@main
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+
+
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ // Override point for customization after application launch.
+ FirebaseApp.configure()
+
+ // Register for Remote Notitifcations
+
+ UNUserNotificationCenter.current().delegate = self
+
+ let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
+ UNUserNotificationCenter.current().requestAuthorization(
+ options: authOptions,
+ completionHandler: { _, _ in }
+ )
+
+ application.registerForRemoteNotifications()
+
+
+ return true
+ }
+
+ // MARK: UISceneSession Lifecycle
+
+ func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
+ // Called when a new scene session is being created.
+ // Use this method to select a configuration to create the new scene with.
+ return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
+ }
+
+ func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
+ // Called when the user discards a scene session.
+ // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
+ // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
+ }
+
+
+}
+
+extension AppDelegate: UNUserNotificationCenterDelegate {
+
+}
+
+```
+
+## Access the Registration Token
+
+To do this, we must first set the `Messaging.messaging().delegate = self` inside the `didFinishLaunchingWithOptions` method.
+
+
+ {" "}
+
+
+ {" "}
+
+
+We will now add a 'Messaging' delegate extension to `AppDelegate.swift` file.
+
+
+ {" "}
+
+
+```Swift
+
+//
+// AppDelegate.swift
+// PushNotificationDemo
+//
+// Created by Emillien Pearce on 13/01/2024.
+//
+
+import UIKit
+import FirebaseCore
+import FirebaseMessaging
+
+@main
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+
+
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ // Override point for customization after application launch.
+ FirebaseApp.configure()
+ FirebaseConfiguration.shared.setLoggerLevel(.min)
+
+
+ // Register for Remote Notitifcations
+
+ UNUserNotificationCenter.current().delegate = self
+
+ let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
+ UNUserNotificationCenter.current().requestAuthorization(
+ options: authOptions,
+ completionHandler: { _, _ in }
+ )
+
+ application.registerForRemoteNotifications()
+
+ // Messaging Delegate
+
+ Messaging.messaging().delegate = self
+
+
+ return true
+ }
+
+ // MARK: UISceneSession Lifecycle
+
+ func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
+ // Called when a new scene session is being created.
+ // Use this method to select a configuration to create the new scene with.
+ return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
+ }
+
+ func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
+ // Called when the user discards a scene session.
+ // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
+ // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
+ }
+
+
+}
+
+extension AppDelegate: UNUserNotificationCenterDelegate {
+
+ // Receive displayed notifications for iOS 10 devices.
+ func userNotificationCenter(_ center: UNUserNotificationCenter,
+ willPresent notification: UNNotification) async
+ -> UNNotificationPresentationOptions {
+ let userInfo = notification.request.content.userInfo
+
+ // With swizzling disabled you must let Messaging know about the message, for Analytics
+ // Messaging.messaging().appDidReceiveMessage(userInfo)
+
+ // ...
+
+ // Print full message.
+ print(userInfo)
+
+ // Change this to your preferred presentation option
+ return [[.alert, .sound]]
+ }
+
+ func userNotificationCenter(_ center: UNUserNotificationCenter,
+ didReceive response: UNNotificationResponse) async {
+ let userInfo = response.notification.request.content.userInfo
+
+ // ...
+
+ // With swizzling disabled you must let Messaging know about the message, for Analytics
+ // Messaging.messaging().appDidReceiveMessage(userInfo)
+
+ // Print full message.
+ print(userInfo)
+ }
+ }
+
+
+
+extension AppDelegate: MessagingDelegate {
+
+ func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
+ print("Firebase registration token: \(String(describing: fcmToken))")
+
+ let dataDict: [String: String] = ["token": fcmToken ?? ""]
+ NotificationCenter.default.post(
+ name: Notification.Name("FCMToken"),
+ object: nil,
+ userInfo: dataDict
+ )
+ // TODO: If necessary send token to application server.
+ // Note: This callback is fired at each app startup and whenever a new token is generated.
+ }
+
+
+}
+
+```
+
+## Receive messages in an Apple App
+
+1. Copy this code block and place it in `UNUserNotificationCenterDelegate` extension.
+2. Add the `didReceiveRemoteNotification`.
+3. We must declare a `gcmMessageIDKey` inside of `AppDelegate`. We can define this as a `string` variable.
+
+
+ {" "}
+
+
+```Swift
+
+//
+// AppDelegate.swift
+// PushNotificationDemo
+//
+// Created by Emillien Pearce.
+//
+
+import UIKit
+import FirebaseCore
+import FirebaseMessaging
+
+@main
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ let gcmMessageIDKey = "gcm.Message_ID"
+
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ // Override point for customization after application launch.
+ FirebaseApp.configure()
+ FirebaseConfiguration.shared.setLoggerLevel(.min)
+
+
+ // Register for Remote Notitifcations
+
+ UNUserNotificationCenter.current().delegate = self
+
+ let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
+ UNUserNotificationCenter.current().requestAuthorization(
+ options: authOptions,
+ completionHandler: { _, _ in }
+ )
+
+ application.registerForRemoteNotifications()
+
+ // Messaging Delegate
+
+ Messaging.messaging().delegate = self
+
+
+ return true
+ }
+
+ // MARK: UISceneSession Lifecycle
+
+ func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
+ // Called when a new scene session is being created.
+ // Use this method to select a configuration to create the new scene with.
+ return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
+ }
+
+ func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
+ // Called when the user discards a scene session.
+ // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
+ // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
+ }
+
+
+}
+
+extension AppDelegate: UNUserNotificationCenterDelegate {
+
+ // Receive displayed notifications for iOS 10 devices.
+ func userNotificationCenter(_ center: UNUserNotificationCenter,
+ willPresent notification: UNNotification) async
+ -> UNNotificationPresentationOptions {
+ let userInfo = notification.request.content.userInfo
+
+ // With swizzling disabled you must let Messaging know about the message, for Analytics
+ // Messaging.messaging().appDidReceiveMessage(userInfo)
+
+ // ...
+
+ // Print full message.
+ print(userInfo)
+
+ // Change this to your preferred presentation option
+ return [[.alert, .sound]]
+ }
+
+ func userNotificationCenter(_ center: UNUserNotificationCenter,
+ didReceive response: UNNotificationResponse) async {
+ let userInfo = response.notification.request.content.userInfo
+
+ // ...
+
+ // With swizzling disabled you must let Messaging know about the message, for Analytics
+ // Messaging.messaging().appDidReceiveMessage(userInfo)
+
+ // Print full message.
+ print(userInfo)
+ }
+
+ func application(_ application: UIApplication,
+ didReceiveRemoteNotification userInfo: [AnyHashable: Any]) async
+ -> UIBackgroundFetchResult {
+ // If you are receiving a notification message while your app is in the background,
+ // this callback will not be fired till the user taps on the notification launching the application.
+ // TODO: Handle data of notification
+
+ // With swizzling disabled you must let Messaging know about the message, for Analytics
+ // Messaging.messaging().appDidReceiveMessage(userInfo)
+
+ // Print message ID.
+ if let messageID = userInfo[gcmMessageIDKey] {
+ print("Message ID: \(messageID)")
+ }
+
+ // Print full message.
+ print(userInfo)
+
+ return UIBackgroundFetchResult.newData
+ }
+
+
+ }
+
+
+
+extension AppDelegate: MessagingDelegate {
+
+ func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
+ print("Firebase registration token: \(String(describing: fcmToken))")
+
+ let dataDict: [String: String] = ["token": fcmToken ?? ""]
+ NotificationCenter.default.post(
+ name: Notification.Name("FCMToken"),
+ object: nil,
+ userInfo: dataDict
+ )
+ // TODO: If necessary send token to application server.
+ // Note: This callback is fired at each app startup and whenever a new token is generated.
+ }
+
+
+}
+
+```
+
+## Adding app capabilities
+
+
+ {" "}
+
+
+Click on the plus sign (+) and select `Background Modes`.
+
+
+ {" "}
+
+
+**Select the following options:**
+
+- Background Fetch
+- Remote Notifications
+- Background Processing
+
+
+ {" "}
+
+
+Add `Push Notifications` capabilities.
+
+
+ {" "}
+
+
+## App Build
+
+
+ {" "}
+
+
+It will ask if you want to receive notifications, and we will allow it.
+
+
+ {" "}
+
+
+## Cloud Message Test
+
+In your Firebase project, navigate to 'engage' section and click on 'messaging'.
+
+
+ {" "}
+
+
+Click on "Send your first message".
+
+
+ {" "}
+
+
+ {" "}
+
+
+We're going to enter the `notification title` and the `notification text`, then we're going to send the test message.
+
+
+ {" "}
+
+
+We must copy and paste this FCM registration token to confirm our device (A physical or a simulator). You can find it in your Xcode console.
+
+
+ {" "}
+
+
+ {" "}
+
+
+ {" "}
+
+
+Click on 'Test'.
+
+
+ {" "}
+
+
+You should see the notification on your device!
+
+
+ {" "}
+
+
+## Novu account creation
+
+
+ {" "}
+
+
+You can immediately configure FCM as a Push channel provider or navigate to the Integration Store.
+
+
+ {" "}
+
+
+## Connecting FCM as a provider
+
+
+ {" "}
+
+
+ {" "}
+
+
+You only need to configure FCM with Novu with the Firebase Service Accounts private key.
+
+To acquire the account key JSON file for your service account, follow this instructions:
+
+1. Select your project. Click the gear icon on the top of the sidebar.
+2. Head to project settings.
+3. Navigate to the service account tab.
+4. Click "Generate New Private Key", then confirm by clicking "Generate Key".
+5. Clicking Generate Key will download the JSON file.
+6. Once the file is on your machine, paste the entire JSON file content in the Service Account field of the FCM provider in the integration store on Novu’s web dashboard.
+
+Make sure your service account key JSON content contains these fields:
+
+```JSON
+
+{
+ "type": "service_account",
+ "project_id": "PROJECT_ID",
+ "private_key_id": "PRIVATE_KEY_ID",
+ "private_key": "PRIVATE_KEY",
+ "client_email": "FIREBASE_ADMIN_SDK_EMAIL",
+ "client_id": "CLIENT_ID",
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+ "token_uri": "https://oauth2.googleapis.com/token",
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+ "client_x509_cert_url": "CLIENT_X509_CERT_URL"
+}
+
+```
+
+
+ {" "}
+
+
+ {" "}
+
+
+ {" "}
+
+
+ {" "}
+
+
+ {" "}
+
+
+## Creating a workflow
+
+In Novu, creating a workflow means establishing a blueprint for sending notifications within your app. This unified structure ties together email, in-app messages, SMS, push notifications, and chat into **one entity**.
+
+Each workflow has a unique name and identifier and includes customized content for various channels, using `{{handlebars}}` variables for personalization.
+
+This ensures consistent platform notifications and allows dynamic adjustments for individual subscribers, scenarios, and use cases.
+
+Workflow creation is for streamlining automated notifications, enabling teams to communicate effectively without extensive technical expertise.
+
+
+
+
+ {" "}
+
+
+ {" "}
+
+
+
+
+ {" "}
+
+
+
+
+ {" "}
+
+
+
+
+## Creating a subscriber
+
+Creating a subscriber in Novu refers to the process of establishing a subscriber entity within the Novu platform. A subscriber is essentially the recipient of the notifications sent through Novu's system. When you create a subscriber, you're setting up the necessary information for Novu to send targeted notifications to that individual.
+
+
+ {" "}
+
+
+
+
+
+
+ ```JSON
+
+ curl --location 'https://api.novu.co/v1/subscribers' \
+
+--header 'Content-Type: application/json' \
+ --header 'Accept: application/json' \
+ --header 'Authorization: ApiKey ' \
+ --data-raw '{
+"subscriberId": "12345678",
+"firstName": "Pawan",
+"lastName": "Jain",
+"email": "pawan.jain@domain.com",
+"phone": "+1234567890",
+"avatar": "avatar-url",
+"locale": "en-US",
+"data": {
+"isDeveloper": true,
+"customKey": "customValue"
+}
+}'
+
+```
+
+
+
+
+
+
+
+
+
+
+ Subscriber can have multiple device tokens
+
+ Here, you can locate the Provider_Identifier.
+
+
+
+```JSON
+
+ curl --request PUT \
+ --url https://api.novu.co/v1/subscribers/{subscriber_id}/credentials \
+--header 'Content-Type: application/json' \
+ --header 'Authorization: ApiKey ' \
+ --data '{
+ "credentials":{
+ "deviceTokens": ["token1", "token2"]
+},
+ "integrationIdentifier":"",
+ "providerId":"fcm"
+}'
+
+```
+
+
+
+
+
+## Sending a notification to iOS device with Novu
+
+To send a notification with Novu, you are actually triggering a notification workflow.
+You have the ability to test the trigger using the user interface or by calling Novu's API.
+
+**Trigger a workflow via the API**
+
+```JSON
+
+curl --location --request POST 'https://api.novu.co/v1/events/trigger' \
+ --header 'Authorization: ApiKey ' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "name": "untitled",
+ "to": {
+ "subscriberId": ""
+ },
+ "payload": {}
+ }'
+
+```
+
+
+ {" "}
+
+
+## Dynamic Content
+
+When we were creating the first workflow, we "Hardcoded" the content of the notification.
+
+Now, we will determine the content when calling the API.
+
+1. In the workflow, we should change the values of the `title` and the `body` to `{{title}}` and `{{body}}`.
+ That way, we could insert values through the payload of the API call.
+
+
+ {" "}
+
+
+2. Add a 'payload' object to the API call.
+
+```JSON
+
+curl --location --request POST 'https://api.novu.co/v1/events/trigger' \
+ --header 'Authorization: ApiKey ' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "name": "untitled",
+ "to": {
+ "subscriberId": "12345678"
+ },
+ "payload": {
+ "title": "This title was set via the Payload",
+ "body": "Payload notification body"
+ },
+ }'
+
+```
+
+
+ {" "}
+
+
+## Sound of a notification
+
+The name of a sound file in your app's main bundle or in the Library/Sounds folder of your app's container directory.
+Specify the string "default" to play the system sound.
+
+Use this key for regular notifications. For critical alerts, use the sound dictionary instead.
+
+```JSON
+
+curl --location --request POST 'https://api.novu.co/v1/events/trigger' \
+ --header 'Authorization: ApiKey ' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "name": "untitled",
+ "to": {
+ "subscriberId": "12345678"
+ },
+ "payload": {
+ "title": "Notification With Sound",
+ "body": "Hello World from Novu"
+ },
+ "overrides": {
+ "fcm": {
+ "apns": {
+ "payload": {
+ "aps": {
+ "sound": "default" // configure sound to the notification
+ }
+ }
+ }
+ }
+ }
+ }'
+
+```
+
+## Priority for notification
+
+If you omit this header, APNs set the notification priority to `10`.
+
+Specify `10` to send the notification immediately.
+
+Specify `5` to send the notification based on power considerations on the user’s device.
+
+Specify `1` to prioritize the device’s power considerations over all other factors for delivery and prevent awakening the device.
+
+```JSON
+
+curl --location --request POST 'https://api.novu.co/v1/events/trigger' \
+ --header 'Authorization: ApiKey ' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "name": "untitled",
+ "to": {
+ "subscriberId": "12345678"
+ },
+ "payload": {
+ "title": "apns-priority: 5",
+ "body": "Novu's API"
+ },
+ "overrides": {
+ "fcm": {
+ "apns": {
+ "headers": {
+ "apns-priority":"5" //priority for notifications
+ },
+ "payload": {
+ "aps": {}
+ },
+ "fcm_options": {}
+ }
+ }
+ }
+ }'
+
+```
+
+## Sending images
+
+Up to this point, your notifications have all contained only text. But if you’ve received many notifications, you know that notifications can have rich content, such as images. It’d be great if your notifications showed users a nice image related to their content. Once again, Firebase makes this super simple.
+
+To show an image in push notifications, you’ll need to create a **_Notification Service Extension_**. This is a separate target in your app that runs in the background when your user receives a push notification. The service extension can receive a notification and change its contents before iOS shows the notification to the user.
+You’ll use Firebase to send an image URL inside a notification.
+You’ll then use a content extension to download the image and add it to the notification’s content.
+
+In Xcode, go to File ▸ New ▸ Target…. Search for Notification Service Extension and select Next. Set a name and configure it to add to your main project.
+
+
+ {" "}
+
+
+ {" "}
+
+
+ {" "}
+
+
+Select **Finish**, and when prompted, select **Activate**.
+
+
+ {" "}
+
+
+When you added the Firebase package to your project, it was only added to the your "main" (In my case it's "PushNotificationDemo") target, so now you need to add the necessary dependency to your new extension.
+Open your app’s project settings and select the name you picked for the extention under Targets.
+
+Under Frameworks and Libraries, select the + button, and search for FirebaseMessaging. Then, select Add. Your project should reflect the image below:
+
+
+ {" "}
+
+
+Select the + button, and search for FirebaseMessaging. Then, select Add.
+
+
+ {" "}
+
+
+ {" "}
+
+
+Now, open `NotificationService.swift`. This file is where you can customize notifications before the user sees them.
+
+First, add the following import to the top of the file:
+`import FirebaseMessaging`
+
+Next, replace the contents of `didReceive(_:withContentHandler:)` with the following:
+
+```Swift
+
+self.contentHandler = contentHandler
+bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
+
+if let bestAttemptContent = bestAttemptContent {
+// Modify the notification content here...
+bestAttemptContent.title = "\(bestAttemptContent.title)"
+
+// Call FIRMessaging extension helper API.
+
+if let messagingContentHandler = self.contentHandler {
+Messaging.serviceExtension().populateNotificationContent(bestAttemptContent, withContentHandler: messagingContentHandler)
+ }
+}
+
+```
+
+Typically, you’d have to search the field containing the image URL, download the image, and finish the presentation with the picture as an attachment.
+Here, you’re using Firebase’s `FIRMessagingExtensionHelper` to perform all that work automatically in a straightforward helper method call.
+
+Remember, iOS only allows you to download your attached image. If the extension’s code takes too long to run, the system will call `serviceExtensionTimeWillExpire()`.
+This gives you a chance to gracefully finish up anything you are doing in the extension or to simply present the notification as is, which is the default implementation.
+
+This is the entire `NotificationService.swift` file.
+
+```Swift
+
+//
+// NotificationService.swift
+// Rich Notifications
+//
+// Created by Emillien Pearce.
+//
+
+import UserNotifications
+import FirebaseMessaging
+
+class NotificationService: UNNotificationServiceExtension {
+
+ var contentHandler: ((UNNotificationContent) -> Void)?
+ var bestAttemptContent: UNMutableNotificationContent?
+
+ override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
+
+ self.contentHandler = contentHandler
+ bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
+
+ if let bestAttemptContent = bestAttemptContent {
+ // Modify the notification content here...
+ bestAttemptContent.title = "\(bestAttemptContent.title)"
+
+ // Call FIRMessaging extension helper API.
+ if let messagingContentHandler = self.contentHandler {
+ Messaging.serviceExtension().populateNotificationContent(bestAttemptContent, withContentHandler: messagingContentHandler)
+ }
+ }
+ }
+
+ override func serviceExtensionTimeWillExpire() {
+ // Called just before the extension will be terminated by the system.
+ // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
+ if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
+ contentHandler(bestAttemptContent)
+ }
+ }
+
+}
+
+```
+
+Let's **rebuild** our app again!
+
+
+ {" "}
+
+
+When making the API call, we should include:
+
+- "mutable-content": 1 inside the "aps" object.
+- "image" in "fcm_options" object,
+- URL address of the image.
+
+```JSON
+
+curl --location --request POST 'https://api.novu.co/v1/events/trigger' \
+ --header 'Authorization: ApiKey ' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "name": "untitled",
+ "to": {
+ "subscriberId": "12345678"
+ },
+ "payload": {
+ "title": "This is a notification with an image",
+ "body": "Check this out"
+ },
+ "overrides": {
+ "fcm": {
+ "apns": {
+ "headers": {},
+ "payload": {
+ "aps": {
+ "mutable-content": 1
+ }
+ },
+ "fcm_options": {
+ "image": "https://www.planetware.com/wpimages/2020/02/france-in-pictures-beautiful-places-to-photograph-eiffel-tower.jpg"
+ }
+ }
+ }
+ }
+ }'
+
+```
+
+
+ {" "}
+
+
+ {" "}
+
+
+## Sending actionable notifications
+
+1. Define Notification Actions:
+
+In your `AppDelegate.swift` file, you should define the notification actions you want to add.
+You can do this by creating a `UNNotificationAction` for each action you want to include.
+For example, let's add two actions: "Accept" and "Reject".
+
+```Swift
+
+// Add these lines inside your AppDelegate class, preferably below the existing extensions.
+
+extension UNNotificationAction {
+ static let accept = UNNotificationAction(
+ identifier: "ACCEPT_ACTION",
+ title: "Accept",
+ options: [.foreground]
+ )
+
+ static let reject = UNNotificationAction(
+ identifier: "REJECT_ACTION",
+ title: "Reject",
+ options: [.destructive, .foreground]
+ )
+}
+
+```
+
+2. Register Notification Category:
+
+ You need to register the notification category with the actions you defined in your `didFinishLaunchingWithOptions` method:
+
+ ```
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ // ... (your existing code)
+
+ // Register notification category
+ let acceptAction = UNNotificationAction.accept
+ let rejectAction = UNNotificationAction.reject
+
+ let messageCategory = UNNotificationCategory(
+ identifier: "MESSAGE_CATEGORY",
+ actions: [acceptAction, rejectAction],
+ intentIdentifiers: [],
+ options: []
+ )
+
+ UNUserNotificationCenter.current().setNotificationCategories([messageCategory])
+
+ return true
+
+ }
+
+```
+
+3. Handle Action Taps:
+
+Now, you need to handle the action taps in the `userNotificationCenter(_:didReceive response:)` method of your `AppDelegate`.
+You can check which action was tapped by inspecting the `response.actionIdentifier` property:
+
+```Swift
+
+func userNotificationCenter(_ center: UNUserNotificationCenter,
+ didReceive response: UNNotificationResponse) async {
+ let userInfo = response.notification.request.content.userInfo
+
+ if response.actionIdentifier == UNNotificationAction.accept.identifier {
+ // Handle the "Accept" action
+ print("User tapped Accept")
+ // Perform the desired action for "Accept"
+ } else if response.actionIdentifier == UNNotificationAction.reject.identifier {
+ // Handle the "Reject" action
+ print("User tapped Reject")
+ // Perform the desired action for "Reject"
+ } else {
+ // Handle other actions or default behavior
+ print("User tapped an action with identifier: \(response.actionIdentifier)")
+ }
+}
+
+```
+
+Your full `AppDelegate.swift` file should look like this:
+
+```Swift
+
+//
+// AppDelegate.swift
+// PushNotificationDemo
+//
+// Created by Emillien Pearce.
+//
+
+import UIKit
+import FirebaseCore
+import FirebaseMessaging
+
+@main
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ let gcmMessageIDKey = "gcm.Message_ID"
+
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ // Override point for customization after application launch.
+ FirebaseApp.configure()
+ FirebaseConfiguration.shared.setLoggerLevel(.min)
+
+
+ // Register for Remote Notitifcations
+
+ UNUserNotificationCenter.current().delegate = self
+
+ let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
+ UNUserNotificationCenter.current().requestAuthorization(
+ options: authOptions,
+ completionHandler: { _, _ in }
+ )
+
+ application.registerForRemoteNotifications()
+
+ // Register notification category
+ let acceptAction = UNNotificationAction.accept
+ let rejectAction = UNNotificationAction.reject
+
+ let messageCategory = UNNotificationCategory(
+ identifier: "MESSAGE_CATEGORY",
+ actions: [acceptAction, rejectAction],
+ intentIdentifiers: [],
+ options: []
+ )
+
+ UNUserNotificationCenter.current().setNotificationCategories([messageCategory])
+
+ // Messaging Delegate
+
+ Messaging.messaging().delegate = self
+
+
+ return true
+ }
+
+ // MARK: UISceneSession Lifecycle
+
+ func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
+ // Called when a new scene session is being created.
+ // Use this method to select a configuration to create the new scene with.
+ return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
+ }
+
+ func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
+ // Called when the user discards a scene session.
+ // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
+ // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
+ }
+
+
+}
+
+extension AppDelegate: UNUserNotificationCenterDelegate {
+
+ // Receive displayed notifications for iOS 10 devices.
+ func userNotificationCenter(_ center: UNUserNotificationCenter,
+ willPresent notification: UNNotification) async
+ -> UNNotificationPresentationOptions {
+ let userInfo = notification.request.content.userInfo
+
+ // With swizzling disabled you must let Messaging know about the message, for Analytics
+ // Messaging.messaging().appDidReceiveMessage(userInfo)
+
+ // ...
+
+ // Print full message.
+ print(userInfo)
+
+ // Change this to your preferred presentation option
+ return [[.alert, .sound]]
+ }
+
+ func userNotificationCenter(_ center: UNUserNotificationCenter,
+ didReceive response: UNNotificationResponse) async {
+ let userInfo = response.notification.request.content.userInfo
+
+ if response.actionIdentifier == UNNotificationAction.accept.identifier {
+ // Handle the "Accept" action
+ print("User tapped Accept")
+ // Perform the desired action for "Accept"
+ } else if response.actionIdentifier == UNNotificationAction.reject.identifier {
+ // Handle the "Reject" action
+ print("User tapped Reject")
+ // Perform the desired action for "Reject"
+ } else {
+ // Handle other actions or default behavior
+ print("User tapped an action with identifier: \(response.actionIdentifier)")
+ }
+
+ // ...
+
+ // With swizzling disabled you must let Messaging know about the message, for Analytics
+ // Messaging.messaging().appDidReceiveMessage(userInfo)
+
+ // Print full message.
+ print(userInfo)
+
+
+ }
+
+ func application(_ application: UIApplication,
+ didReceiveRemoteNotification userInfo: [AnyHashable: Any]) async
+ -> UIBackgroundFetchResult {
+ // If you are receiving a notification message while your app is in the background,
+ // this callback will not be fired till the user taps on the notification launching the application.
+ // TODO: Handle data of notification
+
+ // With swizzling disabled you must let Messaging know about the message, for Analytics
+ // Messaging.messaging().appDidReceiveMessage(userInfo)
+
+ // Print message ID.
+ if let messageID = userInfo[gcmMessageIDKey] {
+ print("Message ID: \(messageID)")
+ }
+
+ // Print full message.
+ print(userInfo)
+
+
+
+ return UIBackgroundFetchResult.newData
+ }
+
+
+ }
+
+
+
+extension AppDelegate: MessagingDelegate {
+
+ func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
+ print("Firebase registration token: \(String(describing: fcmToken))")
+
+ let dataDict: [String: String] = ["token": fcmToken ?? ""]
+ NotificationCenter.default.post(
+ name: Notification.Name("FCMToken"),
+ object: nil,
+ userInfo: dataDict
+ )
+ // TODO: If necessary send token to application server.
+ // Note: This callback is fired at each app startup and whenever a new token is generated.
+ }
+
+
+}
+
+extension UNNotificationAction {
+ static let accept = UNNotificationAction(
+ identifier: "ACCEPT_ACTION",
+ title: "Accept",
+ options: [.foreground]
+ )
+
+ static let reject = UNNotificationAction(
+ identifier: "REJECT_ACTION",
+ title: "Reject",
+ options: [.destructive, .foreground]
+ )
+}
+
+```
+
+Now, let’s trigger a notification with actionable buttons:
+
+```JSON
+
+curl --location --request POST "https://api.novu.co/v1/events/trigger" \
+ --header "Authorization: ApiKey 4de014990cfb201033014548be2db904" \
+ --header "Content-Type: application/json" \
+ --data-raw '{
+ "name": "untitled",
+ "to": {
+ "subscriberId": "12345678"
+ },
+ "payload": {
+ "title": "New Mission",
+ "body": "Your mission, should you choose to accept it,"
+ },
+ "overrides": {
+ "fcm": {
+ "apns": {
+ "payload": {
+ "aps": {
+ "category": "MESSAGE_CATEGORY"
+ }
+ }
+ }
+ }
+ }
+}'
+
+```
+
+
+ {" "}
+
+
+## Additional resources
+
+- [Generating a remote notification by Apple](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification)
+- [Sending notification requests to APNs](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/sending_notification_requests_to_apns)
diff --git a/legacy-guides/add-digest-to-email-notifications.mdx b/legacy-guides/add-digest-to-email-notifications.mdx
new file mode 100644
index 00000000..5b915174
--- /dev/null
+++ b/legacy-guides/add-digest-to-email-notifications.mdx
@@ -0,0 +1,280 @@
+---
+title: "How to Add Digest to Email Notifications"
+description: "Use the digest functionality to send email notifications"
+---
+
+# Introduction
+
+In this guide, you’ll learn how to add digest functionality to email notifications. But before exploring the actual code, let’s understand what a digest notification is and how it works.
+
+You can find the entire code(frontend as well as backend) for this app [here](https://github.com/novuhq/digest-email-app).
+
+
+ If, instead, you want to add digest to in-app notifications, we have a guide for that as well. Take a look [here](/legacy-guides/add-digest-to-inapp-notifications).
+
+
+### What is a digest notification?
+
+Often when you associate notifications with user activity, the end user can be bombarded with messages because of the nature of the activity. Take for example the case of commenting in the context of a social media app. If you were to send a notification to a user for every comment they receive, and they happen to receive 100 comments, it could lead to user fatigue since you would have to send 100 messages.
+
+This is where digest notifications come into the picture!
+
+A digest notification is a notification that consolidates information from several notifications into one and delivers that notification to the end user instead of several separate messages.
+
+### How does digest notification work?
+
+Novu has a built-in digest engine that collects multiple trigger events, aggregates them into a single message, and delivers it to the subscriber. You can use the digest engine by adding a ‘digest node’ to your workflow in the workflow editor in the [Novu dashboard](https://dashboard-v2.novu.co/workflows?utm_campaign=docs-digests). If you want to learn more about it, [this](/workflow/digest) is a great place to start.
+
+Let’s see it in action now!
+
+# Add digest notification to an application
+
+To get started with this, you need the following:
+
+- A Novu account. [Sign up for free](http://dashboard.novu.co/?utm_campaign=docs-gs-digests) if you don’t have one yet.
+- A working React development environment.
+
+# Workflow setup in Novu
+
+Once, you have these, follow the steps below:
+
+1. Head over to the Novu Dashboard.
+2. Click **"Workflows"** on the left sidebar of your Novu dashboard.
+3. Click the **"Create Workflow"** button on the top right:
+
+ {" "}
+
+ Once you click the `create workflow` button, you’ll see a dropdown. Select `blank
+ workflow` from the dropdown:
+
+ {" "}
+
+ You’ll now be taken to the workflow editor:
+
+ {" "}
+
+ Once here, you can add the channels you want to use for sending notifications
+ and configure them. For this guide, we’ll use the `Email` channel. You’ll also
+ see the `Digest` action on the right sidebar.
+
+
+ Each node that is added below the digest node will only be triggered after the
+ specified time interval
+
+
+For example, in our case, say we want the email notification to be sent after every 6 hours. So, add the `digest` action below the `trigger node`, and add the `email` channel node below the `digest` node as shown below:
+
+
+ {" "}
+
+The `digest` node allows you to set a specific time interval for when notifications
+should be sent:
+
+ {" "}
+
+Once, you’ve configured it. Go ahead and configure the `email` channel as per your
+need:
+
+ {" "}
+
+Here’s a brief explanation of all the options:
+
+- **1-Preview:** This shows you a glimpse of what the email notification item will look like when delivered to the client.
+- **2-Sender Name:** Lets you set the sender name for emails that the client will receive.
+- **3-Subject:** This lets you define the subject line for the emails that the client will receive.
+- **4-Filter:** This feature allows you to configure the criteria for delivering notifications. For instance, you can apply a filter based on a subscriber's online status to send them an email if they were online within the last hour.
+- **5-Custom Code** - In custom code, you can write custom digest templates to show some/all parts of a message.
+- **6-Test** - This allows you to send a test email to test if everything is working as per your wish.
+
+You can write your own digest template in the 'custom code' section or just follow this guide as shown in the picture above. Once you’re done configuring this to your liking, click on the `update` button on the top right.
+
+It’ll automatically create a trigger code that you can use in your app. To get it, click on the `get snippet` button on the top right and copy it:
+
+
+ {" "}
+
+Now, let’s see how to add Novu to our app!
+
+# Add Novu to the Backend and Configure it
+
+In your backend, install the novu package using the following code:
+
+```bash
+npm install @novu/node
+
+```
+
+Now, create a route that you want to hit when called from the front end. In our demo app, this is the route:
+
+```jsx
+import express from "express";
+
+import { getEmailDigest } from "../controller/emaildigest.js";
+
+const router = express.Router();
+
+router.post("/sending-email-digest", getEmailDigest);
+
+export default router;
+```
+
+Now, we need to write a controller function that will handle the logic for what is to be sent in the trigger function’s payload. In our case, this is the controller function:
+
+```jsx
+import { sendEmailDigest } from "../novu/novu.js";
+
+export const getEmailDigest = async (req, res) => {
+ const { notif, email } = req.body;
+ try {
+ await sendEmailDigest(notif, email);
+ res.status(201).json({ message: "success", notif: notif });
+ } catch (error) {
+ res.status(409).json({ message: error.message });
+ }
+};
+```
+
+To make it modular, we’ll keep the trigger code in a separate function in a separate file (`novu.js`, in our case) and the trigger function is getting called in the controller function above by the name `getEmailDigest`.
+
+If you’re following the guide, you’ve already copied the trigger function. But before we can add it to our app, we need one key thing - Subscribers.
+
+Subscribers are users to which the notifications are sent. You can see a list of subscribers in the [Novu dashboard](https://dashboard-v2.novu.co/subscribers?utm_campaign=docs-digests) as well.
+
+In our app, we’ll create a subscriber in Node.js as we’re writing our backend in Node.js, but we also have backend [SDKs](/sdks/introduction) ([Node.js](https://github.com/novuhq/novu/tree/next/packages/node), [PHP](https://github.com/novuhq/novu-php), [.NET](https://github.com/novuhq/novu-dotnet), [Elixir](https://github.com/novuhq/novu-elixir), [Go](https://github.com/novuhq/go-novu), [Ruby](https://github.com/novuhq/novu-ruby), [Python](https://github.com/novuhq/novu-python), and [Kotlin](https://github.com/novuhq/novu-kotlin)) to choose from. The recommended way to create a subscriber in NodeJS is as follows:
+
+```jsx
+await novu.subscribers.identify("digestEmailSub", {
+ firstName: "digest email subscriber",
+ email: email,
+});
+```
+
+Here, we’re creating a subscriber with the `subscriberID` of `digestEmailSub.` In most real-world scenarios, the `subscriberId` should be an alphanumeric entity like `adfa67y87ad0`. You can read more about subscribers [here](https://docs.novu.co/platform/subscribers/).
+
+Back in our app, we can now add the trigger function:
+
+```jsx
+import { Novu } from "@novu/node";
+
+export const sendEmailDigest = async (notif, email) => {
+ const novu = new Novu(process.env.YOUR_NOVU_SECRET_KEY);
+ await novu.subscribers.identify("digestEmailSub", {
+ firstName: "digest email subscriber",
+ email: email,
+ });
+
+ novu.trigger("emaildigestworkflow", {
+ to: {
+ subscriberId: "digestEmailSub",
+ email: email,
+ },
+ payload: {
+ notif: notif,
+ },
+ });
+};
+```
+
+Now, we just need to hit the route defined above from the front end.
+
+# Hit the backend route from the front end
+
+From the front end, we just need to hit the route we had defined above. Below, I'm sharing the body component of the demo app I created to illustrate this:
+
+```jsx
+const Body = () => {
+ const [formInput, setFormInput] = useState({ notif: "", email: "" });
+ const [buttonClicked, setButtonClicked] = useState(false);
+
+ const onSubmitHandler = async (e) => {
+ e.preventDefault();
+ const response = await fetch(
+ "https://emaildigestbackend.onrender.com/api/v1/sending-email-digest",
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(formInput),
+ }
+ );
+ console.log(response.data);
+ setFormInput({ notif: "" });
+ };
+
+ const handleClick = () => {
+ setButtonClicked(true);
+
+ // Reset the button feedback after a certain duration
+ setTimeout(() => {
+ setButtonClicked(false);
+ }, 100);
+ };
+
+ const onChangeHandler = (e) => {
+ const value =
+ e.target.name === "email" ? e.target.value.trim() : e.target.value;
+
+ setFormInput((prev) => ({
+ ...prev,
+ [e.target.name]: value,
+ }));
+ };
+ return (
+
+ );
+};
+```
+
+We're hitting the backend route we had created earlier. The backend has been deployed on render as can be seen in this code snippet above.
+
+Congratulations on following the guide up until this point. We can style our app a little using [some TailwindCSS](https://github.com/novuhq/digest-email-app/blob/main/frontend/src/app.css).
+
+If you’ve done everything as recommended, you’ll end up with an app that looks like this:
+
+
+ {" "}
+
+
+In this app, when we enter the email id, some text in the input box, and click send, the notification appears in the inbox after the delay specified above (when we’re creating the workflow).
+
+This is how we add digest to email notifications!
diff --git a/legacy-guides/add-digest-to-inapp-notifications.mdx b/legacy-guides/add-digest-to-inapp-notifications.mdx
new file mode 100644
index 00000000..b47a89ad
--- /dev/null
+++ b/legacy-guides/add-digest-to-inapp-notifications.mdx
@@ -0,0 +1,323 @@
+---
+title: "How to Add Digest to In-App Notifications"
+description: "Leverage the digest functionality to send in-app notifications"
+---
+
+## Introduction
+
+In this guide, you’ll learn how to add digest notifications to a React app. But before exploring the actual code, let’s understand what a digest notification is and how it works.
+
+You can find the entire code(frontend as well as backend) for this app [here](https://github.com/novuhq/digest-learning-app/tree/main).
+
+### What is a Digest Notification?
+
+Often times when you associate notifications with user activity, the end user can be bombarded with messages because of the nature of the activity. Take for example the case of commenting in the context of a social media app. If you were to send a notification to a user for every comment they receive, and they happen to receive 100 comments, it could lead to user fatigue since you would have to send 100 messages.
+
+This is where digest notifications come into the picture!
+
+A digest notification is a notification that consolidates information from several notifications into one and delivers that notification to the end user instead of several separate messages.
+
+### How does Digest Notification Work?
+
+Novu has a built-in digest engine that collects multiple trigger events, aggregates them into a single message and delivers it to the subscriber. You can use the digest engine by adding a `digest node` to your workflow in the workflow editor in the [Novu dashboard](https://dashboard-v2.novu.co/workflows?utm_campaign=docs-digestsinapp).
+
+Let’s see it in action now!
+
+## Add Digest Notification To An App
+
+To get started with this, you need the following:
+
+- A Novu account. [Sign up for free](http://dashboard.novu.co/?utm_campaign=docs-gs-digestsinapp) if you don’t have one yet.
+- A working React development environment.
+
+## Workflow setup in Novu
+
+Once, you have these, follow the steps below:
+
+1. Head over to the Novu Dashboard.
+2. Click **"Workflows"** on the left sidebar of your Novu dashboard.
+3. Click the **"Create Workflow"** button on the top right:
+
+ {" "}
+
+ Once you click the `create workflow` button, you’ll see a dropdown. Select `blank
+ workflow` from the dropdown:
+
+ {" "}
+
+ You’ll now be taken to the workflow editor:
+
+ {" "}
+
+ Once here, you can add the channels you want to use for sending notifications
+ and configure them. For this guide, we’ll use the `In-App` channel. You’ll also
+ see the `Digest` action on the right sidebar.
+
+
+ Each node that is added below the digest node will only be triggered after the
+ specified time interval
+
+For example, in our case, say we want the In-App notification to be sent after every
+6 hours. Next, add the `digest` action below the `trigger node`, and add the `in-app`
+channel node below the `digest` node as shown below:
+
+ {" "}
+
+The `digest` node allows you to set a specific time interval for when notifications
+should be sent:
+
+ {" "}
+
+Once, you’ve configured it. Go ahead and configure the `in-app` channel as per your
+need:
+
+ {" "}
+
+Here’s a brief explanation of all the options:
+
+- **1-Preview**: This shows you a glimpse of what each notification item will look like in the Notification Center UI.
+- **2-Avatar:** If turned on, each notification item will show the avatar of the subscriber.
+- **3-Action:** With this, you can add a primary and secondary call to action button to each notification item.
+- **4-Notification Feeds:** This displays a stream of specific notifications. You can have multiple feeds to show specific notifications in multiple tabs.
+- **5-Redirect URL** - This is the URL to which a subscriber can be directed when they click on a notification item.
+- **6-Filter** - This feature allows you to configure the criteria for delivering notifications. For instance, you can apply a filter based on a subscriber's online status to send them an email if they were online within the last hour.
+
+In our case, we’re going to use the following:
+
+
+ {" "}
+
+Once you’re done configuring this to your liking, click on the `update` button on
+the top right. It’ll automatically create a trigger code that you can use in your
+app. To get it, click on the `get snippet` button on the top right and copy it:
+
+ {" "}
+
+Now, let’s see how to add Novu to our app!
+
+# Add Novu to the Backend and Configure it
+
+In your backend, install the novu package using the following code:
+
+```bash
+npm install @novu/node
+```
+
+Now, create a route which you want to hit when called from the front end. In our demo app, this is the route:
+
+```jsx
+import express from "express";
+import { getDigest } from "../controller/digest.js";
+
+const router = express.Router();
+
+router.post("/sending-digest", getDigest);
+
+export default router;
+```
+
+Now, we need to write a controller function that will handle the logic for what is to be sent in the trigger function’s payload. In our case, this is the controller function:
+
+```jsx
+import { sendDigest } from "../novu/novu.js";
+
+export const getDigest = async (req, res) => {
+ const { name } = req.body;
+ try {
+ await sendDigest(name);
+ res.status(201).json({ message: "success", name: name });
+ } catch (error) {
+ res.status(409).json({ message: error.message });
+ }
+};
+```
+
+To make it modular, we’ll keep the trigger code in a separate function in a separate file (`novu.js`, in our case) and the trigger function is getting called in the controller function above by the name `getDigest`.
+
+If you’re following the guide, you’ve already copied the trigger function. But before we can add it to our app, we need one key thing - **Subscribers.**
+
+Subscribers are entities to which the notifications are sent. You can see a list of subscribers in the [Novu dashboard](https://dashboard-v2.novu.co/subscribers?utm_campaign=docs-digests-inapp) as well.
+
+In our app, we’ll create a subscriber in Node.js as we’re writing our backend in Node.js, but we also have backend [SDKs](/sdks/introduction) (Node.js, PHP, .NET, Go, Ruby, Python and Kotlin) to choose from. The recommended way to create a subscriber in NodeJS is as follows:
+
+```jsx
+await novu.subscribers.identify("aa234u787", {
+ firstName: "digest subscriber",
+});
+```
+
+Here, we’re creating a subscriber with the `subscriberID` of `aa234u787.` You can read more about subscribers [here](/concepts/subscribers).
+
+Back in our app, we can now add the trigger function:
+
+```jsx
+import { Novu } from "@novu/node";
+
+export const sendDigest = async (name) => {
+ const novu = new Novu(process.env.NOVU_SECRET_KEY);
+
+ await novu.subscribers.identify("aa234u787", {
+ firstName: "digest subscriber",
+ });
+
+ await novu.trigger("digest-showcase", {
+ to: {
+ subscriberId: "aa234u787",
+ },
+ payload: {
+ name: name,
+ },
+ });
+};
+```
+
+Now, we just need to hit the route defined above from the front end and add Novu to it.
+
+# Add Novu’s Notification Center to the front end
+
+In the front end, install the Novu notification centre package using the following command:
+
+```bash
+npm install @novu/notification-center
+
+```
+
+Now, import and render the component in your app. The code to do so is as follows:
+
+```jsx
+import {
+ NovuProvider,
+ PopoverNotificationCenter,
+ NotificationBell,
+ IMessage,
+} from "@novu/notification-center";
+
+function Header() {
+ function onNotificationClick(message: IMessage) {
+ // your logic to handle the notification click
+ if (message?.cta?.data?.url) {
+ window.location.href = message.cta.data.url;
+ }
+ }
+
+ return (
+
+
+ {({ unseenCount }) => }
+
+
+ );
+}
+```
+
+You can, of course, modify things as you need. For example, in our app, this is what we’ve done in the header:
+
+```jsx
+import {
+ NovuProvider,
+ PopoverNotificationCenter,
+ NotificationBell,
+} from "@novu/notification-center";
+import "../css/Header.css";
+
+const Header = () => {
+ function onNotificationClick(message) {
+ // your logic to handle the notification click
+ if (message?.cta?.data?.url) {
+ window.location.href = message.cta.data.url;
+ }
+ }
+ return (
+
+ );
+};
+
+export default Header;
+```
+
+You’ll also need to plug in your `applicationIdentifier`. You can get your `application id` from the settings menu in the Novu dashboard.
+
+Beyond this, we just need to call the API we had written above when the form is submitted with the data we want in the payload. The code to do so is as follows:
+
+```jsx
+import { useState } from "react";
+import axios from "axios";
+import "../css/Body.css";
+
+const Body = () => {
+ const [name, setName] = useState("");
+
+ const onSubmitHandler = async (e) => {
+ e.preventDefault();
+ const res = await axios.post(
+ "http://localhost:3000/api/v1/sending-digest",
+ { name }
+ );
+ setName("");
+ };
+ const onChangeHandler = (e) => {
+ setName(e.target.value);
+ };
+ return (
+
+ );
+};
+
+export default Body;
+```
+
+The app is done now!
+
+Congratulations for following the guide up until this point. We can style our app a little using [some CSS](https://github.com/novuhq/digest-learning-app/blob/main/frontend/src/App.css).
+
+If you’ve done everything as recommended, you’ll end up with an app that looks like this:
+
+
+ {" "}
+
+In this app, when we enter some text in the input box and click send, the notification
+appears in the bell icon after the delay specified above (when we’re creating the
+workflow).
+
+This is how we add digest to in-app notifications!
diff --git a/legacy-guides/cookbook/introduction.mdx b/legacy-guides/cookbook/introduction.mdx
new file mode 100644
index 00000000..a04ec299
--- /dev/null
+++ b/legacy-guides/cookbook/introduction.mdx
@@ -0,0 +1,192 @@
+---
+title: "Recipes"
+---
+
+This cookbook contains recipes and code samples demonstrating how to accomplish everyday tasks with Novu in your application. Each code example uses our libraries and SDKs.
+
+
+
+
+
+## Fetch Subscriber Feed
+
+A subscriber feed is a list of all In-App messages for a single subscriber. It's a continuous stream of messages displayed in a list that subscribers can scroll through on the frontend via the Notification Center.
+
+It is a dynamic list with **seen** and **unseen** capabilities. Multiple feeds can exist for a subscriber.
+
+
+ **Subscriber Feed** is very different from **Activity Feed**. The former is
+ for In-App channels, while the latter is a list of every message and relevant
+ metadata across all channels shown in your dashboard.
+
+
+The code sample below fetches the list of all In-App messages sent to a specific subscriber:
+
+
+```javascript Node.js
+const { data: inAppMessages } = await novu.subscribers.getNotificationsFeed('subscriberId', {
+ page: 0,
+ limit: 10,
+
+// it is of type string. By default all feeds messages are fetched
+feedIdentifier: 'Marketing',
+
+// seen and read filter of type boolean
+seen: true,
+read: true
+});
+
+```
+
+
+## Fetch All Feeds
+
+In-App messages are grouped in Feeds. There can be one or multiple feeds.
+
+The code sample below fetches all the feeds that have been created and exist in the In-App steps:
+
+
+```javascript Node.js
+const { data: feedsData } = await novu.feeds.get();
+```
+
+
+
+## Delete a Message From a Feed
+
+A message is a content sent to a single subscriber over a single channel. Some messages are simple, like SMS, while others have more features and capabilities, such as Email, Chat, In-App.
+
+A single message can be deleted from a Feed. The code sample below shows how to do it:
+
+
+ ```javascript Node.js await novu.messages.deleteById('messageId'); ```
+
+
+## Fetch all Messages Sent To All Subscribers
+
+You can retrieve all messages sent to all subscribers. There are a couple of filters you can apply to fetch these messages.
+
+- channel: fetches all messages that were sent via a specific channel, e.g Email, Sms, Push, In-App
+- subscriberId: fetches all messages sent to a specific subscriber
+- transactionIds: fetches all messages via transaction ids.
+
+
+```javascript Node.js
+import { ChannelTypeEnum } from '@novu/node';
+
+// All fields are optional
+const listMessagesOptions = {
+// pagination options
+page: 0,
+limit: 20,
+
+/\*\*
+
+- Filter options
+ \*/
+ // use ChannelTypeEnum.PUSH for push, ChannelTypeEnum.IN_APP for in-app,
+ channel: ChannelTypeEnum.EMAIL, // only email type messages will be fetched
+ subscriberId: '6444105141ffb0919496dfcb',
+ transactionIds: ['644-41051-41ffb0-919496-dfcb'],
+ };
+
+const { data: messagesData } = await novu.messages.list(listMessagesOptions);
+
+```
+
+
+## Mark an In-App Message as Read/Seen
+
+You can mark an In-App message as read/seen. Messages from other channels: **Email**, **Push**, **Chat**, **Sms** can't be marked as read/seen.
+
+
+```javascript Node.js
+const { data: markMessageAsRead } = await novu.subscribers.markMessageRead(
+ 'subscriberId',
+ 'messageId'
+);
+
+const { data: markMessageAsSeen } = await novu.subscribers.markMessageSeen(
+ 'subscriberId',
+ 'messageId'
+);
+```
+
+
+
+## Mark an In-App Message as Read/Unread/Seen/Unseen
+
+You can mark an In-App message as read/unread/seen/unseen. Messages from other channels: **Email**, **Push**, **Chat**, **Sms** can't be marked as read/unread/seen/unseen.
+
+
+```javascript Node.js
+const { data: markMessageAs } = await novu.subscribers.markMessageAs(
+ 'subscriberId',
+ 'messageId',
+ { seen: true, read: false }
+);
+```
+
+
+## Mark all In-App Messages as Read/Unread/Seen/Unseen
+
+You can mark all In-App messages as read/unread/seen/unseen.
+
+Messages from other channels: **Email**, **Push**, **Chat**, **Sms** can't be marked as read/unread/seen/unseen.
+
+
+```javascript Node.js
+import { MarkMessagesAsEnum } from "@novu/node"
+
+const { data: markAllInAppMessages } = await novu.subscribers.markAllMessagesAs(
+'subscriberId',
+
+// can be filtered with feed identifiers
+feedIdentifier: ['Marketing', 'Product']
+
+// MarkMessageAsEnum.READ => It will mark all messages as read
+// MarkMessageAsEnum.SEEN => It will mark all messages as seen
+// MarkMessageAsEnum.UNREAD => It will mark all messages as unread
+// MarkMessageAsEnum.UNSEEN => It will mark all messages as unseen
+markAs: MarkMessageAsEnum.Read
+);
+
+```
+
+
+## Send Slack Notifications
+
+You can send notifications to Slack Channels via Novu like so:
+
+```javascript Node.js
+import {
+ Novu,
+ ChatProviderIdEnum
+} from '@novu/node';
+
+const novu = new Novu("");
+
+// Identify Subscriber
+await novu.subscribers.identify('', {
+ firstName: 'newSubForSlackChat',
+});
+
+// Set credentials for the Subscriber
+await novu.subscribers.setCredentials('', ChatProviderIdEnum.Slack, {
+ webhookUrl: "",
+});
+
+// Trigger slack notification
+await novu.trigger('slack', {
+ to: {
+ subscriberId: ''
+ },
+ payload: {
+ chatMsg: ''
+ }
+});
+```
+
+where `chatMsg` is a payload variable in the workflow editor.
+
+Follow the [full guide](/legacy-guides/slack-guide) on how to send Slack notifications using Novu.
diff --git a/legacy-guides/demos/introduction.mdx b/legacy-guides/demos/introduction.mdx
new file mode 100644
index 00000000..bb324d70
--- /dev/null
+++ b/legacy-guides/demos/introduction.mdx
@@ -0,0 +1,129 @@
+---
+title: "Demo apps"
+description: "Experience the power of Novu for triggering, digesting, and receiving notifications via different channels"
+---
+
+### 1. Email, In-App Notification & Authentication
+
+
+
+
+
+
+
+ Find the source code here!
+
+
+ Try it yourself first-hand!
+
+
+
+### 2. Headless Notification Center
+
+
+
+
+
+
+
+ Find the source code here!
+
+
+ Try it yourself first-hand!
+
+
+
+### 3. Notification Center
+
+
+
+
+
+
+
+ Find the source code here!
+
+
+ Try it yourself first-hand!
+
+
+
+### 4. Email Digest Engine
+
+
+
+
+
+
+
+ Find the source code here!
+
+
+ Try it yourself first-hand!
+
+
+
+### 5. InApp Digest Engine
+
+
+
+
+
+
+
+ Find the source code here!
+
+
+ Try it yourself first-hand!
+
+
diff --git a/legacy-guides/discord-guide.mdx b/legacy-guides/discord-guide.mdx
new file mode 100644
index 00000000..ebfbda98
--- /dev/null
+++ b/legacy-guides/discord-guide.mdx
@@ -0,0 +1,208 @@
+---
+title: "How to use Novu to send notifications to a channel in a Discord server"
+description: "Learn to send Discord notifications swiftly with Novu"
+---
+
+# Introduction
+
+In this guide, you'll learn how to use Novu to send notifications to any channel in a Discord server. But before coding anything up, we just need to go over a couple of setup steps.
+The corresponding docs for this guide are available on our [docs](https://docs.novu.co/channels-and-providers/chat/discord).
+
+
+ The entire code for this app is available on our
+ [GitHub](https://github.com/novuhq/discord-chat-app).
+
+
+So let's begin!
+
+# Set up Discord
+
+Setting up Discord is fairly straightforward. You just need the `webhook Url`. It is pretty simple and can be done in the following easy steps:
+
+1. Go to the channel you want to add the webhook to (you need to be an admin of the discord server).
+
+ {" "}
+
+2. Right-click the channel and select **“Edit Channel”**.
+
+ {" "}
+
+3. Select Integrations -> Webhooks -> Create Webhook
+
+ {" "}
+
+4. Edit the Bot name to your liking, copy the webhook URL and store it somewhere. We'll need it in the future.
+
+ {" "}
+
+
+# Set up Novu
+
+In this part, we'll set up a workflow that will be triggered when we send a notification. Workflows are like blueprints and hold the entire flow of notifications. You can read more about them on our [docs](https://docs.novu.co/workflows/notification-workflows). To set up a notification workflow for our app, follow these steps:
+
+1. Make sure that you've set the Discord Integration as active from the [Novu Integrations Store](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-discordnotifications).
+
+ {" "}
+
+2. Goto the [Novu Web Dashboard](https://dashboard-v2.novu.co/workflows?utm_campaign=docs-discordnotifications).
+3. Click on the **"Add a workflow"** button and select **"Blank workflow"** from the dropdown.
+
+ {" "}
+
+4. Once there, give your workflow a name and drag and drop the **"Chat"** option below the **"Workflow trigger"** step.
+
+ {" "}
+
+5. You can also add variables in the **Workflow Editor**. For example, here I've added 'chatMsg' as a variable as I'll be sending data using it.
+
+ {" "}
+
+ Whatever is placed inside double braces is a variable.
+6. Click the **"Get Snippet"** button, copy the trigger code and keep it somewhere. We'll need this to trigger this workflow.
+
+ {" "}
+
+ Now, we've completed all the setup required and can move to coding!
+
+# Create the backend
+
+The backend for this app is quite simple. Simply install the Novu package:
+
+```bash
+npm install @novu/node
+
+```
+
+Now, create a route that you'll hit when called from the front end. For our demo app, this is the route I've created:
+
+```jsx
+import express from "express";
+import { chatController } from "../controller/chat.js";
+
+const router = express.Router();
+
+router.post("/sendChat", chatController);
+
+export default router;
+```
+
+Now, we need a controller function to handle what is to be sent in the trigger's function payload. Here's the controller I wrote:
+
+```jsx
+import { chat } from "../novu/novu.js";
+
+export const chatController = async (req, res) => {
+ const { chatMsg } = req.body;
+ try {
+ await chat(chatMsg);
+ res.status(201).json({ message: "Message sent successfully" });
+ } catch (error) {
+ console.log("notifController error:", error);
+ res.status(500).json({ message: error.message });
+ }
+};
+```
+
+
+ Notice how we're expecting 'chatMsg' in our payload. This is why we added it
+ as a variable in the workflow created earlier.
+
+To make it modular, we’ll keep the trigger code in a separate function in a separate
+file, `novu.js`, in our case, which is as follows: ```jsx import {
+ (Novu, ChatProviderIdEnum)
+} from '@novu/node';
+
+export const chat = async (chatMsg) => {
+ try {
+ const novu = new Novu(process.env.YOUR_NOVU_SECRET_KEY_HERE);
+
+ await novu.subscribers.identify(process.env.SUB_ID, {
+ firstName: 'newSubForDiscordChat',
+ });
+
+ await novu.subscribers.setCredentials(process.env.SUB_ID, ChatProviderIdEnum.Discord, {
+ webhookUrl: process.env.WEBHOOK_URL
+ });
+
+ } catch (error) {
+ console.log(error);
+ }
+
+}
+
+```
+Add the trigger code you'd copied earlier:
+```jsx
+import { Novu } from '@novu/node';
+
+const novu = new Novu('');
+
+novu.trigger('chat-with-discord', {
+ to: {
+ subscriberId: ''
+ },
+ payload: {
+ chatMsg: ''
+ }
+ });
+```
+
+Our final trigger code should look something like this:
+
+```jsx
+import { Novu, ChatProviderIdEnum } from "@novu/node";
+
+export const chat = async (chatMsg) => {
+ try {
+ const novu = new Novu(process.env.YOUR_NOVU_SECRET_KEY_HERE);
+
+ await novu.subscribers.identify(process.env.SUB_ID, {
+ firstName: "newSubForDiscordChat",
+ });
+
+ await novu.subscribers.setCredentials(
+ process.env.SUB_ID,
+ ChatProviderIdEnum.Discord,
+ {
+ webhookUrl: process.env.WEBHOOK_URL,
+ }
+ );
+
+ await novu.trigger("chat-with-discord", {
+ to: {
+ subscriberId: process.env.SUB_ID,
+ },
+ payload: {
+ chatMsg: chatMsg,
+ },
+ });
+ } catch (error) {
+ console.log(error);
+ }
+};
+```
+
+
+ We're keeping all the sensitive data in a `.env` file to avoid hardcoding as a
+ good practice
+
+
+In this code snippet above, we're first initializing a new instance of Novu, then using it to 'identify' or [create](https://docs.novu.co/subscribers/subscribers#create-a-subscriber) a [subscriber](https://docs.novu.co/subscribers/subscribers).
+
+
+ The 'identify' method tries to find a subscriber with the given info. If it
+ can't find any such subscriber, it creates a new subscriber with the supplied
+ info.
+
+After creating the subscriber, it runs the trigger code for the workflow we had created.
+
+# Front end set up
+
+For our demonstration purposes, the front-end is pretty basic. All we have to do is have an input field to take the notification text in and send the payload. The front-end code for this demo app is available on our [Github repo](https://github.com/novuhq/discord-chat-app/tree/main/frontend).
+And here's our app in all its glory!
+
+
+ {" "}
+
+
+This is how we send Discord notifications using Novu!
diff --git a/legacy-guides/fcm-flutter-novu/how-to-send-push-notifications-to-flutter-apps-with-fcm-using-Novu.mdx b/legacy-guides/fcm-flutter-novu/how-to-send-push-notifications-to-flutter-apps-with-fcm-using-Novu.mdx
new file mode 100644
index 00000000..ac23fe41
--- /dev/null
+++ b/legacy-guides/fcm-flutter-novu/how-to-send-push-notifications-to-flutter-apps-with-fcm-using-Novu.mdx
@@ -0,0 +1,785 @@
+---
+title: "How to send push notifications to Flutter apps (Android & iOS) with FCM using Novu"
+description: "Learn how to integrate Firebase Cloud Messaging with Novu and send notifications to Flutter apps"
+---
+
+## Prerequisites
+
+To complete this guide, you will need the following:
+
+- [Apple Developer](https://developer.apple.com/) membership (to obtain the required permissions to send push notifications).
+- A machine running MacOS to work on building the Flutter app for iOS devices.
+- [Firebase](https://firebase.google.com/) account
+- [Novu](https://dashboard-v2.novu.co/?utm_campaign=docs-gs-legacy-guides-fcm-flutter) account.
+- [Android Studio](https://developer.android.com/studio/install) configured with Dart and Flutter plugins.
+- [Xcode](https://developer.apple.com/xcode/) installed on your machine.
+
+Check out the [Flutter app code for this guide on GitHub](https://github.com/novuhq/flutter-fcm-novu).
+
+## Set up and Create a Flutter App
+
+If you have an existing Flutter App, you can skip this step.
+
+If you are creating a brand new Flutter app, please follow this [guide](https://firebase.google.com/codelabs/firebase-fcm-flutter#0).
+
+## Set up Firebase and FlutterFire
+
+Please follow this [guide to set up a Firebase project and integrate Firebase Cloud Messaging in your Flutter app](https://firebase.google.com/codelabs/firebase-fcm-flutter#2).
+
+## Set Up Apple Integration
+
+Please follow this [guide to ensure that everything is set up on your iOS device](https://firebase.flutter.dev/docs/messaging/apple-integration) to receive messages.
+
+There is an optional section about allowing **Notification Images**. Follow the steps to install and activate the notification service extension. We'll need it later in this guide to display images as part of the push notifications from FCM.
+
+## Set Up FCM Notification Permission & Registration Token
+
+We'll add code to initialize Firebase and ask the user's permission to receive notifications.
+
+Open up the `lib/main.dart` file in your Flutter app and replace everything with the code below:
+
+```dart
+import 'package:flutter/foundation.dart';
+import 'package:firebase_core/firebase_core.dart';
+import 'package:flutter/material.dart';
+import 'firebase_options.dart';
+import 'package:firebase_messaging/firebase_messaging.dart'; // FlutterFire's Firebase Cloud Messaging plugin
+
+// Add Stream controller
+import 'package:rxdart/rxdart.dart';
+
+// for passing messages from event handler to the UI
+final _messageStreamController = BehaviorSubject();
+
+@pragma('vm:entry-point')
+Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
+ // If you're going to use other Firebase services in the background, such as Firestore,
+ // make sure you call `initializeApp` before using other Firebase services.
+ await Firebase.initializeApp();
+
+ print("Handling a background message: ${message.messageId}");
+ print('Message data: ${message.data}');
+ print('Message notification: ${message.notification?.title}');
+ print('Message notification: ${message.notification?.body}');
+
+}
+
+Future main() async {
+ WidgetsFlutterBinding.ensureInitialized();
+ await Firebase.initializeApp(
+ options: DefaultFirebaseOptions.currentPlatform,
+ );
+
+ // Request permission
+ final messaging = FirebaseMessaging.instance;
+
+ // Web/iOS app users need to grant permission to receive messages
+ final settings = await messaging.requestPermission(
+ alert: true,
+ announcement: false,
+ badge: true,
+ carPlay: false,
+ criticalAlert: false,
+ provisional: false,
+ sound: true,
+ );
+
+ if (kDebugMode) {
+ print('Permission granted: ${settings.authorizationStatus}');
+ }
+
+ // Register with FCM
+ // It requests a registration token for sending messages to users from your App server or other trusted server environment.
+ String? token = await messaging.getToken();
+
+ if (kDebugMode) {
+ print('Registration Token=$token');
+ }
+
+ // Set up foreground message handler
+ FirebaseMessaging.onMessage.listen((RemoteMessage message) {
+ if (kDebugMode) {
+ print('Handling a foreground message: ${message.messageId}');
+ print('Message data: ${message.data}');
+ print('Message notification: ${message.notification?.title}');
+ print('Message notification: ${message.notification?.body}');
+ }
+
+ _messageStreamController.sink.add(message);
+ });
+
+ // Set up background message handler
+ FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
+
+ runApp(MyApp());
+}
+
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'Flutter Demo',
+ theme: ThemeData(
+ primarySwatch: Colors.blue,
+ ),
+ home: const MyHomePage(title: 'Flutter Demo Home Page'),
+ );
+ }
+}
+
+class MyHomePage extends StatefulWidget {
+ const MyHomePage({Key? key, required this.title}) : super(key: key);
+
+ final String title;
+
+ @override
+ State createState() => _MyHomePageState();
+}
+
+class _MyHomePageState extends State {
+ String _lastMessage = "";
+
+ _MyHomePageState() {
+ _messageStreamController.listen((message) {
+ setState(() {
+ if (message.notification != null) {
+ _lastMessage = 'Received a notification message:'
+ '\nTitle=${message.notification?.title},'
+ '\nBody=${message.notification?.body},'
+ '\nData=${message.data}';
+ } else {
+ _lastMessage = 'Received a data message: ${message.data}';
+ }
+ });
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(widget.title),
+ ),
+ body: Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text('Last message from Firebase Messaging:',
+ style: Theme.of(context).textTheme.titleLarge),
+ Text(_lastMessage, style: Theme.of(context).textTheme.bodyLarge),
+ ],
+ ),
+ ),
+ );
+ }
+}
+```
+
+_lib/main.dart_
+
+There are 4 sections to pay attention to:
+
+
+
+ This shows the notification permission prompt to the user when the user interacts with the app for the first time. If the user allows it, then we can get a token.
+
+ ```dart
+ // Request permission
+ final messaging = FirebaseMessaging.instance;
+
+ // Web/iOS app users need to grant permission to receive messages
+ final settings = await messaging.requestPermission(
+ alert: true,
+ announcement: false,
+ badge: true,
+ carPlay: false,
+ criticalAlert: false,
+ provisional: false,
+ sound: true,
+ );
+ ```
+
+
+
+ A device's token is needed for the specific device to receive messages. The `getToken()` returns the registration token for the app instance on the device.
+
+ ```dart
+ String? token = await messaging.getToken();
+
+ if (kDebugMode) {
+ print('Registration Token=$token');
+ }
+ ```
+
+
+
+ This handler listens to when a push notification is sent from FCM to the device. If the app is in the foreground, then it prints out the notification title, body, messageId, and data properties to the console.
+
+ ```dart
+ FirebaseMessaging.onMessage.listen((RemoteMessage message) {
+ if (kDebugMode) {
+ print('Handling a foreground message: ${message.messageId}');
+ print('Message data: ${message.data}');
+ print('Message notification: ${message.notification?.title}');
+ print('Message notification: ${message.notification?.body}');
+ }
+
+ _messageStreamController.sink.add(message);
+ });
+ ```
+
+
+
+ This handler listens to when a push notification is sent from FCM to the device. If the app is in the background, then it prints out the notification title, body, messageId, and data properties to the console.
+
+ ```dart
+ ...
+ @pragma('vm:entry-point')
+ Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
+ // If you're going to use other Firebase services in the background, such as Firestore,
+ // make sure you call `initializeApp` before using other Firebase services.
+ await Firebase.initializeApp();
+
+ print("Handling a background message: ${message.messageId}");
+ print('Message data: ${message.data}');
+ print('Message notification: ${message.notification?.title}');
+ print('Message notification: ${message.notification?.body}');
+
+ }
+ ...
+
+
+ FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
+ ```
+
+
+
+
+Now, run the Flutter app. Your device should be connected to your machine to enable the app to run on it.
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Cloud Message Test
+
+In your Firebase project, navigate to `Engage` section and click on `Messaging`.
+
+
+ {" "}
+
+
+Click on `Create your first campaign` and select `Firebase Notification messages`.
+
+
+ {" "}
+
+
+ {" "}
+
+
+Enter the `Notification title` and the `Nabigateotification text`, and click on `Send test message`.
+
+
+ {" "}
+
+
+We must copy and paste this FCM registration token to confirm our device. You can find it logged as shown earlier in our editor.
+
+
+ {" "}
+
+
+ {" "}
+
+
+Click on 'Test'.
+
+
+ {" "}
+
+
+You should see the notification on your device!
+
+
+
+
+
+## Sign Up on Novu
+
+Let's use Novu to fire push notifications via FCM. First, sign up on [Novu Cloud](https://dashboard-v2.novu.co)?utm_campaign=docs-gs-legacy-guides-fc-flutter.
+
+
+ {" "}
+
+
+Next, immediately configure FCM as a Push channel provider.
+
+
+ {" "}
+
+
+You can also do this by heading straight to the `Integrations Store`. Click on `Add a provider`.
+
+## Connect FCM as a Push Channel provider
+
+
+ {" "}
+
+
+ {" "}
+
+
+You only need to configure FCM with Novu with the Firebase Service Accounts private key.
+
+To acquire the account key JSON file for your service account, follow this instructions:
+
+1. Select your project. Click the gear icon on the top of the sidebar.
+2. Head to project settings.
+3. Navigate to the `Service accounts` tab.
+4. Click `Generate new private key`, then confirm by clicking `Generate key`.
+5. Click `Generate key` to download the JSON file.
+6. Once the file is on your machine, paste the entire JSON file content in the `Service Account` field of the FCM provider in the Integration store on Novu’s web dashboard.
+
+Make sure your service account key JSON content contains these fields:
+
+```JSON
+
+{
+ "type": "service_account",
+ "project_id": "PROJECT_ID",
+ "private_key_id": "PRIVATE_KEY_ID",
+ "private_key": "PRIVATE_KEY",
+ "client_email": "FIREBASE_ADMIN_SDK_EMAIL",
+ "client_id": "CLIENT_ID",
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+ "token_uri": "https://oauth2.googleapis.com/token",
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+ "client_x509_cert_url": "CLIENT_X509_CERT_URL"
+}
+
+```
+
+
+ {" "}
+
+
+ {" "}
+
+
+ {" "}
+
+
+ {" "}
+
+
+ {" "}
+
+
+## Create a Notification Workflow
+
+In Novu, creating a workflow means establishing a blueprint for sending notifications within your app. This unified structure ties together email, in-app messages, SMS, push notifications, and chat into **one entity**.
+
+Each workflow has a unique name and identifier and includes customized content for various channels, using `{{handlebars}}` variables for personalization.
+
+This ensures consistent platform notifications and allows dynamic adjustments for individual subscribers, scenarios, and use cases.
+
+
+
+
+ {" "}
+
+
+ {" "}
+
+
+
+
+ {" "}
+
+
+
+
+ {" "}
+
+
+
+
+We need to fire notifications to subscribers. So, let's create a subscriber!
+
+## Create A Subscriber & Update Credentials
+
+A subscriber is essentially the recipient of the notifications sent through Novu's system. When you create a subscriber, you're setting up the necessary information for Novu to send targeted notifications to that individual.
+
+You can see the list of subscribers in the `Subscribers` section of the Novu dashboard.
+
+
+ {" "}
+
+
+
+
+
+
+ ```JSON
+
+ curl --location 'https://api.novu.co/v1/subscribers' \
+
+--header 'Content-Type: application/json' \
+ --header 'Accept: application/json' \
+ --header 'Authorization: ApiKey ' \
+ --data-raw '{
+"subscriberId": "584349343",
+"firstName": "Flutter",
+"lastName": "Boy",
+"email": "flutterboy@gmail.com",
+}'
+
+```
+
+
+
+
+
+
+
+
+
+
+ Subscriber can have multiple device tokens
+
+ Here, you can locate the Provider_Identifier.
+
+
+
+ ```JSON
+
+ curl --request PUT \
+ --url https://api.novu.co/v1/subscribers/{subscriber_id}/credentials \
+ --header 'Content-Type: application/json' \
+ --header 'Authorization: ApiKey ' \
+ --data '{
+ "credentials":{
+ "deviceTokens": [""]
+ },
+ "integrationIdentifier":"",
+ "providerId":"fcm"
+ }'
+
+```
+
+_Update the Subscriber Credentials by adding the device tokens to the subscriber_
+
+
+
+
+
+## Sending a Push Notification to a Device(Android/iOS) with Novu
+
+To send a notification with Novu, you are actually triggering a notification workflow.
+You have the ability to test the trigger using the Novu UI or by calling Novu's API.
+
+**Trigger a workflow via the API**
+
+Insert the `trigger identifier` as the name in the API request below:
+
+```JSON
+
+curl --location --request POST 'https://api.novu.co/v1/events/trigger' \
+ --header 'Authorization: ApiKey ' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "name": "novu-push-workflow-for-flutter",
+ "to": {
+ "subscriberId": ""
+ },
+ "payload": {}
+ }'
+
+```
+
+**Trigger a workflow via the Novu Dashboard**
+
+
+ {" "}
+
+
+ {" "}
+
+
+**Receive Triggered Push Workflow on Device**
+
+
+
+
+
+## Dynamic Content
+
+When we were creating the first workflow, we "Hardcoded" the content of the notification.
+
+Now, we will determine the content when calling the API.
+
+1. In the workflow, we should change the values of the `title` and the `body` to `{{title}}` and `{{body}}`.
+ That way, we could insert values through the payload of the API call.
+
+
+ {" "}
+
+
+2. Add a `payload` object to the API call.
+
+Insert the `trigger identifier` as the name in the API request below:
+
+```JSON
+
+curl --location --request POST 'https://api.novu.co/v1/events/trigger' \
+ --header 'Authorization: ApiKey ' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "name": "novu-push-workflow-for-flutter",
+ "to": {
+ "subscriberId": "584349343"
+ },
+ "payload": {
+ "title": "This title was set via the Payload",
+ "body": "This body was set via the Payload"
+ },
+ }'
+
+```
+
+
+ {" "}
+
+
+## Handling Data Type Messages
+
+With FCM, you can send `Notification` and `Data` messages. We have handled sending notification messages. Let's handle data messages.
+
+Data messages contain user-defined custom key-value pairs. The information is encapsulated in the `data` key. The app can do whatever it pleases with the data once it's received by the device.
+
+**Via the Trigger API call**
+
+```JSON
+
+curl --location --request POST 'https://api.novu.co/v1/events/trigger' \
+ --header 'Authorization: ApiKey ' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "name": "novu-push-workflow-for-flutter",
+ "to": {
+ "subscriberId": "584349343"
+ },
+ "payload": {
+ "title": "This title was set via the Payload",
+ "body": "This body was set via the Payload"
+ },
+ "overrides": {
+ "fcm": {
+ "data": {
+ "subscription_status": "active",
+ "renewed_by": "bisola"
+ }
+ }
+ }
+ }'
+
+```
+
+
+
+
+
+## Sound of a notification
+
+You can use the name of a sound file in your app's main bundle or in the Library/Sounds folder of your app's container directory.
+
+Specify the string "default" to play the system sound. Use it for regular notifications. For critical alerts, use the sound dictionary instead.
+
+The code below works for iOS devices:
+
+```JSON
+
+curl --location --request POST 'https://api.novu.co/v1/events/trigger' \
+ --header 'Authorization: ApiKey ' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "name": "novu-push-workflow-for-flutter",
+ "to": {
+ "subscriberId": "584349343"
+ },
+ "payload": {
+ "title": "Notification With Sound",
+ "body": "Hello World from Novu"
+ },
+ "overrides": {
+ "fcm": {
+ "apns": {
+ "payload": {
+ "aps": {
+ "sound": "default" // configure sound to the notification
+ }
+ }
+ }
+ }
+ }
+ }'
+
+```
+
+For Android devices, use the following:
+
+```JSON
+
+curl --location --request POST 'https://api.novu.co/v1/events/trigger' \
+ --header 'Authorization: ApiKey ' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "name": "novu-push-workflow-for-flutter",
+ "to": {
+ "subscriberId": "584349343"
+ },
+ "payload": {
+ "title": "Notification With Sound",
+ "body": "Hello World from Novu"
+ },
+ "overrides": {
+ "fcm": {
+ "android": {
+ "notification": {
+ "defaultSound": true // This uses the Android framework's default sound for the notification.
+ }
+ }
+ }
+ }
+ }'
+
+```
+
+## Priority for notification
+
+If you omit this header, APNs (iOS) set the notification priority to `10`.
+
+- Specify `10` to send the notification immediately.
+- Specify `5` to send the notification based on power considerations on the user’s device.
+- Specify `1` to prioritize the device’s power considerations over all other factors for delivery and prevent awakening the device.
+
+```JSON
+
+curl --location --request POST 'https://api.novu.co/v1/events/trigger' \
+ --header 'Authorization: ApiKey ' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "name": "novu-push-workflow-for-flutter",
+ "to": {
+ "subscriberId": "584349343"
+ },
+ "payload": {
+ "title": "apns-priority: 5",
+ "body": "Novu's API"
+ },
+ "overrides": {
+ "fcm": {
+ "apns": {
+ "headers": {
+ "apns-priority":"5" //priority for notifications
+ },
+ "payload": {
+ "aps": {}
+ },
+ "fcm_options": {}
+ }
+ }
+ }
+ }'
+
+```
+
+For Android devices, specify `high` or `normal` for the priority of the notification message.
+
+```JSON
+
+curl --location --request POST 'https://api.novu.co/v1/events/trigger' \
+ --header 'Authorization: ApiKey ' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "name": "novu-push-workflow-for-flutter",
+ "to": {
+ "subscriberId": "584349343"
+ },
+ "payload": {
+ "title": "priority",
+ "body": "Novu's API"
+ },
+ "overrides": {
+ "fcm": {
+ "android": {
+ "priority": "high"
+ }
+ }
+ }
+ }'
+
+```
+
+## Sending Push Notification With Image
+
+Up to this point, your notifications have all contained only text. But if you’ve received many notifications, you know that notifications can have rich content, such as images. It’d be great if your notifications showed users a nice image related to their content. Once again, Firebase makes this super simple.
+
+To show an image in push notifications, you’ll need to create a **_Notification Service Extension_**. We handled it earlier in this guide during the Apple setup.
+
+For reiteration, follow [this guide](https://firebase.flutter.dev/docs/messaging/apple-integration#advanced-optional-allowing-notification-images) to ensure it is properly set up for iOS devices.
+
+When making the API call, we should include:
+
+- "mutable-content": 1 inside the “aps” object.
+- "image" in "fcm_options" object,
+- URL address of the image.
+
+```JSON
+
+curl --location --request POST 'https://api.novu.co/v1/events/trigger' \
+ --header 'Authorization: ApiKey ' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "name": "untitled",
+ "to": {
+ "subscriberId": "12345678"
+ },
+ "payload": {
+ "title": "This is a notification with an image",
+ "body": "Check this out"
+ },
+ "overrides": {
+ "fcm": {
+ "apns": {
+ "payload": {
+ "aps": {
+ "mutable-content": 1
+ }
+ },
+ "fcm_options": {
+ "image": "https://www.planetware.com/wpimages/2020/02/france-in-pictures-beautiful-places-to-photograph-eiffel-tower.jpg"
+ }
+ }
+ }
+ }
+ }'
+```
+
+## Additional resources
+
+- [Notifications Usage - Flutter Fire](https://firebase.flutter.dev/docs/messaging/notifications)
diff --git a/legacy-guides/framework-guides/digest.mdx b/legacy-guides/framework-guides/digest.mdx
new file mode 100644
index 00000000..29911701
--- /dev/null
+++ b/legacy-guides/framework-guides/digest.mdx
@@ -0,0 +1,202 @@
+---
+title: "How to batch product notifications via code"
+description: "Leverage Novu's Digest Engine to batch product notifications"
+---
+
+# Introduction
+
+Learn how to use Novu’s Digest engine to batch notifications. In this guide, you’ll learn how to aggregate multiple messages and send them off as one notification. Follow these steps:
+
+## Getting started
+
+Integrating Novu’s code-first workflow with React.Email for your Next.js application can be done in three steps
+
+1. Create a fully-featured Novu NextJS app with the command below:
+
+```bash
+npx novu init
+```
+
+1. Once this installation is complete, simply `cd` into the directory and start your app using the `npm run dev` command.
+
+Now, we’re all set to send digested notifications but before we do, let’s look at what it is and how it works:
+
+## Digest engine and how it works
+
+Novu’s Digest Engine is designed to consolidate multiple trigger events into a single cohesive message before delivering it to the subscriber. This functionality is essential for managing notification overload and ensuring that users receive meaningful, aggregated updates rather than being bombarded with numerous individual notifications. Here’s how it works:
+
+1. **Event Accumulation**: The digest engine collects multiple trigger events based on a unique `subscriberId` to be delivered as one message.
+2. **Batching Events**: The engine batches these events within a specified time interval. This interval can be configured in units of seconds, minutes, hours, days, weeks, or months.
+3. **Delivery**: After the accumulation period, the batched events are sent out as a single notification. This consolidated message reduces the frequency of notifications and provides the user with a summary of all relevant events.
+
+## How to digest in a code-first workflow:
+
+1. Write a workflow and add a digest node to it:
+
+```tsx
+import { Client } from "@novu/framework";
+
+export const client = new Client({
+ apiKey: "",
+ /**
+ * Enable this flag only during local development
+ */
+ strictAuthentication: process.env.NODE_ENV !== "development",
+});
+
+export const digestWorkflow = workflow(
+ "novu-digest",
+
+ async ({ step, payload }) => {
+ // Add Digest Node
+ const digestResult = await step.digest("email-digest", async () => ({
+ unit: "seconds",
+ amount: 30,
+ }));
+
+ // Send a welcome email
+ await step.email("send-email", () => {
+ const resObj = {
+ subject: `Welcome to Novu`,
+ body: digestResult.events.map((event) => event.payload.text).join("\n"),
+ };
+ return resObj;
+ });
+ },
+ {
+ payloadSchema: {
+ properties: {
+ email: { type: "string" },
+ text: { type: "string", default: "Sumit" },
+ },
+ required: ["text"],
+ },
+ }
+);
+```
+
+
+💡 In the digest node above, you can change the digest duration by setting a value to the key `amount` and changing the value of `unit` to seconds, minutes, hours, days, weeks, or months
+
+
+
+2. Here’s a simple client-side app with a handler function attached to the `submit` event:
+
+```tsx
+"use client";
+import React, { useState } from "react";
+
+export default function Home() {
+ const [email, setEmail] = useState("");
+ const [text, setText] = useState("");
+
+ const onClickHandler = async (e: React.FormEvent) => {
+ try {
+ e.preventDefault();
+ await fetch("/api/users", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ email: email,
+ text: text,
+ }),
+ });
+
+ // console.log('working fine');
+ } catch (error) {
+ console.error("Error:", error);
+ }
+ };
+ return (
+
+
+
+ );
+}
+```
+
+3. And the corresponding route that it hits once the event fires:
+
+```tsx
+import { Novu } from "@novu/node";
+
+const novu = new Novu("");
+
+export async function POST(request: Request) {
+ const res = await request.json();
+
+ await novu.trigger("novu-digest", {
+ to: {
+ subscriberId: "novu-sub-digest",
+ },
+ payload: {
+ email: res.email,
+ text: res.text,
+ },
+ });
+
+ console.log("triggered");
+ return Response.json({ success: true });
+}
+```
+
+That’s it!
+
+That’s how simple it is to use Novu’s Digest Engine. Digesting notifications enhances the user experience by providing comprehensive summaries, making it easier for users to stay informed about important updates in a concise manner, without straining users with too many notifications. We have got a thorough guide on digesting notifications [in our docs](https://docs.novu.co/guides/add-digest-to-email-notifications) that you can check out as well.
diff --git a/legacy-guides/framework-guides/framework-mjml.mdx b/legacy-guides/framework-guides/framework-mjml.mdx
new file mode 100644
index 00000000..53501da8
--- /dev/null
+++ b/legacy-guides/framework-guides/framework-mjml.mdx
@@ -0,0 +1,386 @@
+---
+title: "How to send notifications with Next.js and MJML"
+description: "Leverage MJML package to send email notifications"
+---
+
+# Introduction
+
+This guide will walk you through how to send notifications with MJML and Novu. You can check out the code for a [sample demo app](https://github.com/novuhq/framework-mjml).
+
+## Pre-requisites
+
+- A Novu account
+- Node installed on your machine
+- A working NextJS development environment
+
+## Get started with Novu Framework
+
+[Novu Framework](https://docs.novu.co/framework/quickstart) is a "notifications as code" approach that enables developers to define workflows as functions and integrate them with their preferred libraries for email, SMS, and chat generation.
+
+1. To get started with Novu Framework, just run this command in your terminal, it’ll scaffold a new NextJS project with Novu Framework and we’ll be ready to roll!
+
+```jsx
+npx novu init --secret-key=
+
+```
+
+2. Once you execute this command, you’ll be asked to give your project a name. I’ll keep the default `my-novu-app` but you can choose your own.
+
+ {" "}
+
+3. You’ll then be asked if you want to use React-email or not. Since, we’ll be using MJML, I’m choosing the default 'No' option.
+
+ {" "}
+
+4. After this step, all the dependencies will be installed and you will be able to start using Novu Framework.
+
+ {" "}
+
+5. Once this installation is complete, simply cd into the directory and start your app using the `npm run dev` command, and your app will be served on `localhost:4000`
+
+Make sure that the port 4000 isn’t already being used!
+
+You’ll now have a NextJS app running on `http://localhost:4000` and you can make changes to your app as you see fit. Let’s now move to the meaty stuff - using Novu Framework in a NextJS app and the magic of Dev Studio.
+
+## Echo Dev Studio
+
+The Echo Dev Studio is a companion app to the Echo Client SDK. Its goal is to provide a local environment that lives near your code.
+
+To launch the dev studio locally you can run: `npx novu-labs@latest echo`. The Dev Studio will be started by default on port `2022`, and accessible via: `http://localhost:2022`
+
+
+ {" "}
+
+
+Here’s how the Dev Studio looks on the first run:
+
+
+ {" "}
+
+You’ll notice that it asks for an `Echo endpoint` at the bottom. Novu Echo requires
+a single HTTP endpoint (`/api/echo` or similar) to be exposed by your application.
+This endpoint is used to receive events from our Worker Engine. We have more on Novu
+endpoint [in our docs.](https://docs.novu.co/framework/concepts/endpoint)
+
+
+ You can view the Echo Endpoint as a webhook endpoint that Novu will call when
+ it needs to retrieve contextual information for a given subscriber and
+ notification.
+
+
+Just enter the full URL of your Echo Endpoint. In our case, it is `http://localhost:4000/api/echo`
+
+
+ {" "}
+
+Once you do, you’ll see a green checkmark alongside the URL input box and a green
+`connected` highlight at the top right corner.
+
+## Installing and configuring MJML
+
+Integrating MJML with Novu in our NextJS app is quite straightforward, with just one small caveat. Following are the steps to get it installed and configured:
+
+1. Simply run the following command to install it like any other npm package:
+
+```bash
+npm i mjml
+```
+
+
+ Once installed, you need to keep in mind that, MJML doesn’t play very nice
+ with newer web technologies such as Next.js that we’re using today. In order
+ to overcome some hurdles, we need to use the `require` statement when
+ importing MJML in our files, such as when defining the template.
+
+
+2. To write an email template, you can look over some of the examples in the MJML documentation to get inspiration from. In our case, this is the template:
+
+```jsx
+// notice the use of require statement here:
+const mjml2html = require("mjml");
+
+export const mjmlTemplate = (inputs: Inputs) => {
+ const { text, buttonText, imageUrl, buttonUrl } = inputs;
+ return mjml2html(`
+
+
+
+
+ ${text}
+
+
+ OnePage
+
+
+
+
+
+
+
+ HomeFeatures
+ Portfolio
+
+
+
+
+
+ More than an email template
Only on Mailjet template builder
+ ${buttonText}
+
+
+
+
+
+ Best audience
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eleifend sagittis nunc, et fermentum est ullamcorper dignissim.
+
+
+
+ Higher rates
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eleifend sagittis nunc, et fermentum est ullamcorper dignissim.
+
+
+
+ 24/7 Support
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eleifend sagittis nunc, et fermentum est ullamcorper dignissim.
+
+
+
+
+ Why choose us?
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing
+ elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
+
+
+
+
+
+
+
+ Great newsletter for the best company out there
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
+ aliqua. Ut enim ad minim veniam.
+ READ MORE
+
+
+
+
+
+
+ Please contact us if you have any questions.
+
+
+
+
+
+ `);
+};
+```
+
+3. And as final step, we need to define the workflow that uses the template defined above.
+
+```jsx
+import { Client, workflow } from "@novu/framework";
+import { mjmlTemplate } from "./mjml";
+
+export const client = new Client({
+ apiKey: process.env.NOVU_SECRET_KEY,
+ strictAuthentication: process.env.NODE_ENV !== "development",
+});
+
+export const emailWorkflow = workflow('mjml-email-workflow', async ({ payload, step }) => {
+ await step.email('send-email', async (inputs) => {
+ return {
+ subject: `Welcome to MJML with Novu Framework, ${payload.text}!`,
+ body: mjmlTemplate(inputs).html,
+ };
+ }, {
+ controlSchema: {
+ type: 'object',
+ properties: {
+ text: { type: 'string', default: 'Welcome to our service' },
+ imageUrl: { type: 'string', default: 'https://picsum.photos/id/11/2000/300' },
+ buttonText: { type: 'string', default: 'Sign up' },
+ buttonUrl: { type: 'string', default: 'https://novu.co' },
+ },
+ additionalProperties: false,
+ } as const
+ });
+}, {
+ payloadSchema: {
+ properties: {
+ text: { type: 'string', default: 'Sumit' },
+ },
+ additionalProperties: false,
+ }
+});
+```
+
+Once you do this, you’ll see this workflow, the steps in the workflow, step inputs, payload variables and the rendered view of this workflow on the Echo Dev Studio:
+
+
+ {" "}
+
+Here, from the Dev Studio, you or your peers can change things like the text of a
+button, toggle visibility of a button, static text content etc and have it synced
+with the cloud with the `Sync to Cloud` button.
+
+### Payload vs Step Inputs
+
+Notice that in the Echo dev studio above, we’ve used payload as well as step inputs. Here’s how you can decide if you need either or both:
+| Payload | Step Inputs |
+| -------- | ------------------------------------- |
+| is used for dynamic content that changes from one notification to another based on events occurring in your system. | are used for static elements or predefined options that non-technical team members can modify without altering the codebase.|
+| is controlled by developers and passed dynamically through the novu.trigger method. | are defined by developers but aremeant to be utilized and modified by non-technical peers.|
+| Payload examples include User ID, Post ID, Comment, Order ID, 2FA token, etc., which are likely to change with each notification. | Step Inputs examples include the text of a button, whether a section should be shown, static text content, etc., which are generally static but configurable elements.|
+| Payload modifications are made in the code by developers at the time of triggering a notification. | Step Inputs can be modified directly in the UI, offering a no-code solution for non-technical team members to make changes.|
+| Payload data is passed during the novu.trigger method and is part of the dynamic data handling process within notification workflows.| Step Inputs are predefined in the workflow configuration and can be adjusted through the Echo Dev Studio, affecting how notifications are rendered without changing the workflow logic.|
+
+## Syncing with the cloud, with the click of a button
+
+Once done with the workflow, now we need to sync it to the cloud. Fortunately, Novu Framework makes it a breeze to sync changes from the local machine to the cloud and it all happens with a click of a button.
+
+To enable our cloud environment to talk to your local Bridge instance, you need to supply an [Bridge Endpoint](/concepts/endpoint) URL. This sets up a communication channel between our cloud environment and your local instance. To allow Novu to communicate with your local machine a tunnel will need to be generated.
+
+Running the `npm run dev` script in the project launches both the Bridge application and the tunnel solution. The tunnel URL shows up in the console output.
+
+You can also use a tool like `ngrok`:
+
+```bash
+// using ngrok
+ngrok http http://localhost:
+```
+
+In our case, the app is running on port 4000 so we’ll use:
+
+```bash
+ngrok http http://localhost:4000
+```
+
+This will create a tunnel and you’ll see something like this in the terminal:
+
+
+ {" "}
+
+Remember, the exact URL (`/api/novu` or similar) you expose in your application for
+handling Novu Framework requests is what you'd consider the `Bridge URL`.
+
+This URL would be the endpoint within your application's domain where Novu's Worker Engine sends requests to fetch notification content or subscriber details dynamically. In our case, it is this: https://faec-2409-40f2-3c-3b57-400e-a7f7-1fc0-1e5.ngrok-free.app/api/echo
+So, we’ll enter this Bridge URL:
+
+
+ {" "}
+
+And create diff:
+
+ {" "}
+
+
+## Testing our workflow
+
+Once you’ve synced your changes in the previous step, you’ll see a notification that says ‘Sync complete’ and you can now go to Novu Cloud using the ‘Test your workflows’ link and trigger a notification.
+
+
+ {" "}
+
+You’ll see the workflow you’ve created has a blue lightning bolt icon. That icon
+signifies that the corresponding workflow has been created with Novu Framework:
+
+ {" "}
+
+Simply open the workflow and you can send a test email from there.
+
+
+ Make sure that all the expected payload variables and step inputs are being
+ sent in their respective fields!
+
+
+ {" "}
+
+This is the workflow test email in my inbox:
+
+ {" "}
+
+Once tested, you can simply have this workflow triggered whenever you want. For
+instance, a typical use case is to have a workflow triggered when an event
+occurs. To replicate it, I’ve attached a handler function that triggers this
+workflow when the `submit` event fires:
+
+Here’s a simple replication of the stipulated scenario:
+
+```jsx
+"use client";
+
+import { useState } from "react";
+
+export default function Home() {
+ const [loading, setLoading] = useState(false);
+ const [text, setText] = useState("");
+ const [email, setEmail] = useState("");
+
+ const triggerWorkflow = async () => {
+ setLoading(true);
+ const response = await fetch("/api/users", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ text: text,
+ email: email,
+ }),
+ });
+ const result = await response.json();
+ setLoading(false);
+ console.log(result);
+ };
+ return (
+
+ );
+}
+```
+
+And the corresponding route it hits:
+
+```jsx
+import { Novu } from "@novu/node";
+
+const novu = new Novu("");
+
+export async function POST(request: Request) {
+ const res = await request.json();
+
+ await novu.trigger("mjml-email-workflow", {
+ to: {
+ subscriberId: "novu-sub-two",
+ email: res.email,
+ },
+ payload: {
+ text: res.text,
+ },
+ });
+
+ console.log("triggered");
+ return Response.json({ success: true });
+}
+```
+
+So there you go!
+
+This is how you create workflows using Novu Framework and deploy your changes seamlessly to the Novu cloud. You can check out the code for a [sample demo app](https://github.com/novuhq/framework-mjml).
+
+Once you've built the workflow, you might want read one of [our other guides](/legacy-guides/framework-guides/product) on how to empower product teams to manage notification workflows.
+
+Don’t forget to share your workflows with us and as always, hit us up on Discord with any questions you might have!
diff --git a/legacy-guides/framework-guides/framework-nuxt-vuemail.mdx b/legacy-guides/framework-guides/framework-nuxt-vuemail.mdx
new file mode 100644
index 00000000..1b9ff27e
--- /dev/null
+++ b/legacy-guides/framework-guides/framework-nuxt-vuemail.mdx
@@ -0,0 +1,496 @@
+---
+title: "How to send notifications with Nuxt.js and Vuemail"
+description: "Learn how to send email notifications with Nuxt.js, Vuemail and Novu"
+---
+
+This guide assumes you know what [a notification workflow](https://docs.novu.co/workflows/notification-workflows) is and why you need it. It also assumes you are aware of [Nuxt](https://nuxt.com/) and [VueEmail](https://vuemail.net/).
+Fantastic. Now that we are all on the same page let’s proceed.
+
+Novu recently released a new way of baking notification workflows into your apps via code, and it’s huge! Let me walk you through it.
+
+It’s called Novu Framework and you can learn more about it by visiting the related [documentation](https://docs.novu.co/framework/quickstart). You might also benefit from reading this excellent [Code-First Approach to Managing Notification Workflows](https://novu.co/blog/a-code-first-approach-to-managing-notification-workflows/) article.
+
+If you prefer to clone the repository from GitHub and get your hands dirty, we aren’t going to stop you. You can find it [here](https://github.com/novuhq/novu-framework-nuxt-vue-email-example).
+
+Still here? Good, I wrote this guide for you!
+
+## Getting Started
+
+## 1. Initialize **New Nuxt Project**
+
+We will follow the [official installation guide from Nuxt](https://nuxt.com/docs/getting-started/installation), open your terminal / IDE, and run the following command:
+
+```jsx
+npx nuxi@latest init
+```
+
+## 2. **Integrate Novu Framework Into Our App (Notification Workflow)**
+
+1. We will install the `@novu/framework` package in the root of our Nuxt app directory by running the following command:
+
+```jsx
+npm install @novu/framework
+```
+
+2. We must provide an API endpoint for the [Novu Dev Studio](https://docs.novu.co/framework/concepts/studio) to fetch our notification workflow. (Don’t worry; we guide you through all the steps.) Navigate to your app's `app/server` directory and create a new directory named `api`.
+
+```jsx
+cd app/server
+
+mkdir api
+```
+
+Create a file within the `app/server/api` directory and name it `novu.ts`. Copy and paste the code snippet below:
+
+```jsx
+// app/server/api/novu.ts
+import { serve } from "@novu/framework/nuxt";
+import { client, myWorkflow } from "../novu/workflows";
+
+export default defineEventHandler(
+ serve({ client: client, workflows: [myWorkflow] })
+);
+```
+
+3. Now, let's create the instance where we will build and maintain our notification workflow:
+
+Navigate to your `app/server` directory and create another directory named `novu`.
+
+```jsx
+cd app/server
+
+mkdir novu
+```
+
+Create a file within the `app/server/novu` directory and name it `workflows.ts`. Copy and paste the code snippet below:
+
+```jsx
+// app/server/novu/workflows.ts
+// This workflow already renders vue email template.
+
+import { config } from '@vue-email/compiler'
+import { Client, workflow } from '@novu/framework';
+
+const vueEmail = config('templates', {
+ verbose: false,
+ options: {
+ },
+})
+
+export const client = new Client({
+ /**
+ * Disable this flag only during local development
+ * For production this should be true
+ */
+ strictAuthentication: process.env.NODE_ENV !== "development"
+});
+
+export const myWorkflow = workflow('hello-world', async ({ step, payload }) => {
+ await step.email(
+ 'send-email',
+ async () => {
+ const template = await vueEmail.render('sample-email.vue', {
+ props: payload,
+ });
+ return {
+ subject: `You have a new invitation from: ${payload.username}.`,
+ body: template.html,
+ }
+ });
+},
+ {
+ payloadSchema: {
+ // Always `object`
+ type: 'object',
+ // Specify the properties to validate. Supports deep nesting.
+ properties: {
+ username: { type: "string" },
+ invitedByEmail: { type: "string" },
+ inviteLink: { type: "string" },
+ inviteFromIp: { type: "string" },
+ inviteFromLocation: { type: "string" }
+ },
+ // Used to enforce full type strictness, with no rogue properties.
+ additionalProperties: false,
+ // The `as const` is important to let Typescript know that this
+ // type won't change, enabling strong typing on `inputs` via type
+ // inference of the provided JSON Schema.
+ } as const,
+ },
+);
+```
+
+We haven’t finished the guide yet but have everything to build a notification workflow.
+
+Below, We can see a “plain” Client instance and shaping your desired workflow.
+
+```jsx
+// app/server/novu/workflows.ts
+// This is Client instance concept - not a working instance!
+
+import { Client, workflow } from "@novu/framework";
+
+export const client = new Client({
+ /**
+ * Disable this flag only during local development
+ * For production this should be true
+ */
+ strictAuthentication: process.env.NODE_ENV !== "development",
+});
+
+export const myWorkflow = workflow(
+ "", // The Workflow name. Must be unique across your Bridge application.
+ // Workflow resolver, the entry-point for your Workflow steps
+ async ({
+ // Helper function to declare your Workflow Steps.
+ step,
+ // Workflow Trigger payload
+ payload,
+ }) => {
+ // ...your Workflow Steps
+ },
+ {
+ // JSON Schema for validation and type-safety. Zod, and others coming soon.
+ // https://json-schema.org/draft-07/json-schema-release-notes
+
+ // The schema for the Workflow payload passed dynamically via Novu Trigger API
+ // Defaults to an empty schema.
+ payloadSchema: { properties: { name: { type: "string" } } },
+ // The schema for the Workflow inputs passed statically via Novu Web
+ // Defaults to an empty schema.
+ controlSchema: { properties: { brandColor: { type: "string" } } },
+ }
+);
+```
+
+We can:
+
+- Select a name for our workflow.
+- Declare [Workflow Steps](https://docs.novu.co/framework/steps/introduction) as we see fit for our use case
+- Configure what could be passed in the payload of the Workflow Trigger ([payloadSchema](https://docs.novu.co/framework/concepts/inputs))
+- Configure what inputs could be passed statically via Novu Web ([controlSchema](https://docs.novu.co/framework/concepts/inputs))
+
+Note: You can create and manage as many workflows as you wish within `app/server/novu/workflows.ts`; make sure to provide each workflow with a unique name.
+
+## 3. Adding Vuemail in our notification workflow
+
+1. We will install the `vue-email` package in the root of our Nuxt app directory by running the following command:
+
+```jsx
+npm install vue-email
+```
+
+2. We will create one more directory in the root of our directory and name it `templates`. That is where our vue-email templates will be stored.
+
+```c
+mkdir templates
+```
+
+3. In the `app/templates` directory, we will create a file for our first vue-email template and name it `sample-email.vue`.
+
+[You can find examples of Vue email templates here.](https://vuemail.net/playground)
+
+```jsx
+// app/templates/sample-email.vue
+
+
+
+
+
+
+
+ {{ previewText }}
+
+
+
+ Accept
+ Hello {{ username }},
+
+
+
+
+
+
+
+ New Project {{ teamName }} available
+
+ Hello {{ username }},
+
+ {{invitedByUsername}} (
+
+ {{ invitedByEmail }}
+
+ ) has invited you to the {{ teamName }} project on Acme.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Accept
+ Decline
+
+
+
+ or copy and paste this URL into your browser:
+
+ {{ inviteLink }}
+
+
+
+
+
+ This invitation was intended for
+ {{ username }} .This invite was sent from {{ inviteFromIp }} located in
+ {{ inviteFromLocation }}. If you were not expecting this invitation, you can ignore this email. If you are concerned about your account's safety, please reply to this email to get in touch
+ with us.
+
+
+
+
+
+
+
+```
+
+## 4. Launching The Dev Studio
+
+Now that we have everything in our notification workflow configured along with the vue-email template we will use, it’s time to see a visual representation of what we have built.
+
+1. If you haven’t run the development environment for your Nuxt app, now is the time.
+
+ ```jsx
+ npm run dev
+ ```
+
+2. Do you remember that we exposed an Bridge API endpoint in our app for Dev Studio to catch? This is where it happens.
+
+ Run the following command in a separate terminal:
+
+ ```jsx
+ npx novu-labs@latest echo
+ ```
+
+ If you’ve followed this guide, you should see this:
+
+
+
+
+
+ Here is where our exact API endpoint goes. If our application runs on a different port, we should change the URL manually to point Dev Studio in the right direction.
+
+
+
+
+
+ Also, if we have configured everything correctly, we should see that Dev Studio sees our workflow identifier (Workflow unique name).
+
+
+
+
+
+3. Click the “View Workflow” button in the Dev Studio to see the workflow.
+
+ We should see the following:
+
+
+
+
+
+Click on the workflow step node called `send-email`. We should see a preview of our email template and the `Step Input` and `Payload` variables we have configured in the workflow schemas.
+
+
+
+
+
+4. This is the time to adjust the email template, step input schema, or define the properties we anticipate in the payload. We have a live representation of everything. You can add more steps like In-App, SMS, and chat.
+
+## 5. Syncing our workflow to Novu Cloud
+
+Having completed crafting, designing, and modifying our notification workflow to suit our requirements, we're ready to push it to Novu Cloud. There, it will handle the heavy lifting and seamlessly execute all the steps we've configured whenever we trigger the workflow.
+
+1. Click on the `Sync to Cloud` button on the top right.
+
+
+
+
+
+2. We will need to create a local tunnel that the Novu Cloud environment can reach for local experimentation purposes.
+
+
+
+
+
+ On a separate terminal, run the following command:
+
+ ```jsx
+ // Change to the port where the app is currently running
+
+ npx localtunnel --port 3000
+ ```
+
+ We should get something like:
+
+ ```jsx
+ your url is: https://famous-pumas-clap.loca.lt
+ ```
+
+
+
+
+
+3. Click the `Create Diff` button. To push (merge) the workflow code to the cloud, an API Key from our Novu account should be added to our Framework Client instance.
+
+
+
+
+
+ Let’s navigate to `../app/server/novu/workflows.ts` file and add our Novu API Key.
+
+```jsx
+// app/server/novu/workflows.ts
+// This workflow already renders vue email template.
+
+import { config } from '@vue-email/compiler'
+import { Client, workflow } from '@novu/framework';
+
+const vueEmail = config('templates', {
+ verbose: false,
+ options: {
+ },
+})
+
+export const client = new Client({
+ apiKey: process.env.NOVU_SECRET_KEY, // <<-- Your Novu API KEY
+ /**
+ * Disable this flag only during local development
+ * For production this should be true
+ */
+ strictAuthentication: process.env.NODE_ENV !== "development"
+});
+
+export const myWorkflow = workflow('hello-world', async ({ step, payload }) => {
+ await step.email(
+ 'send-email',
+ async () => {
+ const template = await vueEmail.render('sample-email.vue', {
+ props: payload,
+ });
+ return {
+ subject: `You have a new invitation from: ${payload.username}.`,
+ body: template.html,
+ }
+ });
+},
+ {
+ payloadSchema: {
+ // Always `object`
+ type: 'object',
+ // Specify the properties to validate. Supports deep nesting.
+ properties: {
+ username: { type: "string" },
+ invitedByEmail: { type: "string" },
+ inviteLink: { type: "string" },
+ inviteFromIp: { type: "string" },
+ inviteFromLocation: { type: "string" }
+ },
+ // Used to enforce full type strictness, with no rogue properties.
+ additionalProperties: false,
+ // The `as const` is important to let Typescript know that this
+ // type won't change, enabling strong typing on `inputs` via type
+ // inference of the provided JSON Schema.
+ } as const,
+ },
+);
+```
+
+ We will now click the `Create Diff` button again.
+
+
+
+ This is where you can review all the changes made to the workflow. Once you have verified that everything is in order, go ahead and click the `Deploy Changes` button.
+
+
+
+ ## 6. Testing Our Notification Workflow
+
+ There are many ways to test and [trigger our workflow](https://docs.novu.co/api-reference/events/trigger-event), but we'll make a `cURL` API call to Novu Cloud from our terminal in this guide.
+
+ If we don't have any subscribers or users in our database or within our Novu Cloud organization, we'll send the test to ourselves for simplicity.
+
+ - In the `subscriberId` key, input a random number or even our email address (as long as we do not try to assign the same ID key to another subscriber, we should be good).
+ - For the `email` key, we will need to insert a valid email address so we can actually receive the email.
+
+
+ **Note:** Learn more about the structure of [subscriber properties](https://docs.novu.co/subscribers/subscribers).
+
+ We can leave the payload empty or add properties aligned with the `payloadSchema` we established in the workflow definition.
+
+ ```jsx
+
+ curl -X POST https://api.novu.co/v1/events/trigger \
+ -H "Authorization: ApiKey " \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "hello-world",
+ "to": {
+ "subscriberId": "",
+ "email": "john@doemail.com",
+ },
+ "payload": {
+ "username": "Jane Doe",
+ "invitedByEmail": "Jane@doemail.com",
+ "inviteLink": "https://acme.com/projects/accept/foo",
+ "inviteFromIp": "172.0.0.1",
+ "inviteFromLocation": "New York, DC"
+ }
+ }'
+
+ ```
+
+ Now, let’s check our email inbox.
+
+ - No longer limited by UI notification steps and rigidity.
+ - No longer limited by notification content editors and systems. The more, the merrier!
+ - Now an IFTTT (If-This-Then-That) pro engineer!
+
+ Once you've built the workflow, you might want read one of [our other guides](/legacy-guides/framework-guides/product) on how to empower product teams to manage notification workflows. Have fun, and share your use case on Discord with us!
diff --git a/legacy-guides/framework-guides/framework-react-email.mdx b/legacy-guides/framework-guides/framework-react-email.mdx
new file mode 100644
index 00000000..48000b67
--- /dev/null
+++ b/legacy-guides/framework-guides/framework-react-email.mdx
@@ -0,0 +1,555 @@
+---
+title: "How to send notifications with Next.js and React email"
+description: "Leverage the React email package to send email notifications"
+---
+
+# Introduction
+
+In this guide, you’ll learn how to send email notifications using the React email package. But before we jump into it, let's first take a look at the prerequisites!
+
+## Pre-requisites
+
+- A Novu account
+- Node installed on your machine
+- A working NextJS development environment
+
+## Get started with Novu Framework
+
+[Novu Framework](https://docs.novu.co/framework/quickstart) is a "notifications as code" approach that enables developers to define workflows as functions and integrate them with their preferred libraries for email, SMS, and chat generation.
+
+1. To get started with Novu Framework, just run this command in your terminal, it’ll scaffold a new NextJS project with Novu Framework and we’ll be ready to roll!
+
+```jsx
+npx novu init --secret-key=
+```
+
+2. Once you execute this command, you’ll be asked to give your project a name. I’ll keep the default `my-novu-app` but you can choose your own.
+
+ {" "}
+
+3. You’ll then be asked if you want to use React-email or not. You can choose to install it at this step itself or proceed with No and then install it later. I’m choosing the default No option.
+
+ {" "}
+
+4. After this step, all the dependencies will be installed and you will be able to start using Novu Framework.
+
+ {" "}
+
+5. Once this installation is complete, simply cd into the directory and start your app using the `npm run dev` command, and your app will be served on `localhost:4000`
+
+Make sure that the port 4000 isn’t already being used!
+
+You’ll now have a NextJS app running on `http://localhost:4000` and you can make changes to your app as you see fit. Let’s now move to the meaty stuff - using Novu Framework in a NextJS app and the magic of Dev Studio.
+
+## Echo Dev Studio
+
+The Echo Dev Studio is a companion app to the Echo Client SDK. Its goal is to provide a local environment that lives near your code.
+
+To launch the dev studio locally you can run: `npx novu-labs@latest echo`. The Dev Studio will be started by default on port `2022`, and accessible via: `http://localhost:2022`
+
+
+ {" "}
+
+
+Here’s how the Dev Studio looks on the first run:
+
+
+ {" "}
+
+You’ll notice that it asks for an `[Bridge Endpoint](/concepts/endpoint)` at the bottom. Novu Framework
+requires a single HTTP endpoint (`/api/novu` or similar) to be exposed by your application.
+This endpoint is used to receive events from our Worker Engine. We have more on Bridge
+endpoint [in our docs.](https://docs.novu.co/framework/concepts/endpoint)
+
+
+You can view the [Bridge Endpoint](/concepts/endpoint) as a webhook endpoint that Novu will call when it needs to retrieve contextual information for a given subscriber and notification.
+
+
+
+Just enter the full URL of your [Bridge Endpoint](/concepts/endpoint). In our case, it is `http://localhost:4000/api/echo`
+
+
+ {" "}
+
+Once you do, you’ll see a green checkmark alongside the URL input box and a green
+`connected` highlight at the top right corner.
+
+## Installing and configuring React Email
+
+
+ If you opted for installing React Email in our CLI set-up process, you can
+ skip installing it again.
+
+
+Integrating React Email with Novu in our NextJS app is quite straightforward. Following are the steps to get it installed and configured:
+
+1. Simply run the following command to install it like any other npm package:
+
+```jsx
+ npm i @react-email/components react-email
+```
+
+Once installed, proceed to write an email template in the next step
+
+2. To write an email template, you can look over some of the examples in the [React Email documentation](https://react.email/examples) to get inspiration. In our case, this is the template:
+
+```jsx
+import {
+ Body,
+ Button,
+ Container,
+ Column,
+ Head,
+ Hr,
+ Html,
+ Img,
+ Link,
+ render,
+ Row,
+ Section,
+ Text,
+ Tailwind,
+} from "@react-email/components";
+import * as React from "react";
+
+interface VercelInviteUserEmailProps {
+ username?: string;
+ userImage?: string;
+ invitedByUsername?: string;
+ invitedByEmail?: string;
+ teamName?: string;
+ teamImage?: string;
+ inviteLink?: string;
+ inviteFromIp?: string;
+ showJoinButton?: boolean;
+ inviteFromLocation?: string;
+ buttonText?: string
+}
+
+const baseUrl = process.env.VERCEL_URL
+ ? `https://${process.env.VERCEL_URL}`
+ : "";
+
+export const VercelInviteUserEmail = ({
+ username,
+ userImage,
+ invitedByUsername,
+ invitedByEmail,
+ teamName,
+ teamImage,
+ inviteLink,
+ inviteFromIp,
+ inviteFromLocation,
+ showJoinButton,
+ buttonText
+}: VercelInviteUserEmailProps) => {
+
+ return (
+
+
+
+
+
+
+
+
+
+ Hello {username},
+
+
+ {invitedByUsername} (
+
+ {invitedByEmail}
+
+ ) has invited you to the {teamName} team on{" "}
+ Vercel.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {showJoinButton && (
+
+
+
+ )}
+
+ or copy and paste this URL into your browser:{" "}
+
+ {inviteLink}
+
+
+
+
+ This invitation was intended for{" "}
+ {username}. This invite was
+ sent from {inviteFromIp}{" "}
+ located in{" "}
+ {inviteFromLocation}. If you
+ were not expecting this invitation, you can ignore this email. If
+ you are concerned about your account's safety, please reply to
+ this email to get in touch with us.
+
+
+
+
+
+ );
+};
+
+VercelInviteUserEmail.PreviewProps = {
+ username: "alanturing",
+ userImage: `${baseUrl}/static/vercel-user.png`,
+ invitedByUsername: "Alan",
+ invitedByEmail: "alan.turing@example.com",
+ teamName: "Enigma",
+ teamImage: `${baseUrl}/static/vercel-team.png`,
+ inviteLink: "https://vercel.com/teams/invite/foo",
+ inviteFromIp: "204.13.186.218",
+ inviteFromLocation: "São Paulo, Brazil",
+} as VercelInviteUserEmailProps;
+
+export default VercelInviteUserEmail;
+
+export function renderReactEmail(input: any, payload: any) {
+ return render();
+
+}
+```
+
+3. And as final step, we need to define the workflow that uses the template defined above.
+
+```jsx
+export const newSignup = workflow(
+ "new-signup",
+ async ({ step, payload }) => {
+ // Send a welcome email
+ await step.email(
+ "send-email",
+ async (inputs) => {
+ return {
+ subject: `Welcome to Novu, ${payload.username}`,
+ body: renderReactEmail(inputs, payload),
+ };
+ },
+ {
+ controlSchema: {
+ type: "object",
+ properties: {
+ showJoinButton: { type: "boolean", default: true },
+ buttonText: { type: "string", default: "Join the team" },
+ userImage: {
+ type: "string",
+ default:
+ "https://react-email-demo-bdj5iju9r-resend.vercel.app/static/vercel-user.png",
+ format: "uri",
+ },
+ invitedByUsername: { type: "string", default: "Alan" },
+ invitedByEmail: {
+ type: "string",
+ default: "alan.turing@example.com",
+ format: "email",
+ },
+ teamName: { type: "string", default: "Team Awesome" },
+ teamImage: {
+ type: "string",
+ default:
+ "https://react-email-demo-bdj5iju9r-resend.vercel.app/static/vercel-team.png",
+ format: "uri",
+ },
+ inviteLink: {
+ type: "string",
+ default: "https://vercel.com/teams/invite/foo",
+ format: "uri",
+ },
+ inviteFromIp: { type: "string", default: "204.13.186.218" },
+ inviteFromLocation: {
+ type: "string",
+ default: "São Paulo, Brazil",
+ },
+ },
+ },
+ }
+ );
+ // JSON Schema for validation and type-safety. Zod, and others coming soon.
+ },
+ { payloadSchema: { properties: { text: { type: "string" } } } }
+);
+```
+
+Once you do this, you’ll see this workflow, the steps in the workflow, step inputs, payload variables and, the rendered view of this workflow on the Echo Dev Studio:
+
+
+ {" "}
+
+Here, from the Dev Studio, you or your peers can change things like the text of a
+button, toggle visibility of a button, static text content, etc, and have it synced
+with the cloud with the `Sync to Cloud` button.
+
+### Payload vs Step Inputs
+
+Notice that in the Echo dev studio above, we’ve used payload as well as step inputs. Here’s how you can decide if you need either or both:
+
+- Payload is used for dynamic content that changes from one notification to another based on events occurring in your system.
+- Step Inputs are for static elements or predefined options that non-technical team members can modify without altering the codebase.
+- Payload is controlled by developers and passed dynamically through the novu.trigger method.
+- Step Inputs are defined by developers but are meant to be utilized and modified by non-technical peers.
+- Payload examples include User ID, Post ID, Comment, Order ID, 2FA token, etc., which are likely to change with each notification.
+- Step Inputs examples include the text of a button, whether a section should be shown, static text content, etc., which are generally static but configurable elements.
+- Payload modifications are made in the code by developers at the time of triggering a notification.
+- Step Inputs can be modified directly in the UI, offering a no-code solution for non-technical team members to make changes.
+- Payload data is passed during the novu.trigger method and is part of the dynamic data handling process within notification workflows.
+- Step Inputs are predefined in the workflow configuration and can be adjusted through the Echo Dev Studio, affecting how notifications are rendered without changing the workflow logic.
+
+## Syncing with the cloud, with the click of a button
+
+Once done with the workflow, now we need to sync it to the cloud. Fortunately, Novu Framework makes it a breeze to sync changes from the local machine to the cloud and it all happens with a click of a button.
+
+To enable our cloud environment to talk to your local Echo instance, you need to supply an Echo endpoint URL. This sets up a communication channel between our cloud environment and your local instance. To allow Novu to communicate with your local machine a tunnel will need to be generated.
+
+Running the `npm run dev` script in the project launches both the Bridge application and the tunnel solution. The tunnel URL shows up in the console output.
+
+You can also use a tool like `ngrok`:
+
+```bash
+// using ngrok
+ngrok http http://localhost:
+```
+
+In our case, the app is running on port 4000 so we’ll use:
+
+```bash
+ngrok http http://localhost:4000
+```
+
+This will create a tunnel and you’ll see something like this in the terminal:
+
+
+ {" "}
+
+Remember, the exact URL (`/api/novu` or similar) you expose in your application for
+handling Novu Framework requests is what you'd consider the `Bridge URL`.
+
+This URL would be the endpoint within your application's domain where Novu's Worker Engine sends requests to fetch notification content or subscriber details dynamically. In our case, it is this: `https://0536-2409-40f2-1039-43e1-3053-d98e-c276-ee5.ngrok-free.app/api/echo`
+
+So, we’ll enter this Bridge URL:
+
+
+ {" "}
+
+And create diff:
+
+ {" "}
+
+
+## Testing our workflow
+
+Once you’ve synced your changes in the previous step, you’ll see a notification that says ‘Sync complete’ and you can now go to Novu Cloud using the ‘Test your workflows’ link and trigger a notification.
+
+
+ {" "}
+
+You’ll see the workflow you’ve created has a blue lightning bolt icon. That icon
+signifies that the corresponding workflow has been created with Novu Framework:
+
+ {" "}
+
+Simply open the workflow and you can send a test email from there.
+
+
+ Make sure that all the expected payload variables and step inputs are being
+ sent in their respective fields!
+
+
+ {" "}
+
+This is the workflow test email in my inbox:
+
+ {" "}
+
+Once tested, you can simply have this workflow triggered whenever you want. For
+instance, a typical use case is to have a workflow triggered when an event
+occurs. To replicate it, I’ve attached a handler function that triggers this
+workflow when the `submit` event fires:
+
+Here’s a simple replication of the stipulated scenario:
+
+```jsx
+"use client";
+import Image from "next/image";
+import styles from "./page.module.css";
+import React, { useState } from "react";
+import axios from "axios";
+
+export default function Home() {
+ const [email, setEmail] = useState("");
+ const [username, setUsername] = useState("");
+
+ const onClickHandler = async (e: React.FormEvent) => {
+ try {
+ e.preventDefault();
+ await fetch("/api/users", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ email: email,
+ username: username,
+ }),
+ });
+
+ // console.log('working fine');
+ } catch (error) {
+ console.error("Error:", error);
+ }
+ };
+ return (
+
+
+
+ );
+}
+```
+
+And the corresponding route it hits:
+
+```jsx
+import { Novu } from "@novu/node";
+
+const novu = new Novu("");
+
+export async function POST(request: Request) {
+ const res = await request.json();
+
+ await novu.trigger("", {
+ to: {
+ subscriberId: "",
+ },
+ payload: {
+ email: res.email,
+ username: res.username,
+ },
+ });
+
+ console.log("triggered");
+ return Response.json({ success: true });
+}
+```
+
+## More workflow examples
+
+Creating workflows with Novu Framework is a breeze. Here are a couple of other examples from React email docs created with Novu Framework:
+
+1. AWS Email verification example
+
+ {" "}
+
+2. Apple invoice email example
+
+ {" "}
+
+
+## Conclusion
+
+So there you go!
+
+This is how you create workflows using Novu Framework and deploy your changes seamlessly to the Novu cloud. You can check out the code for a [sample demo app](https://github.com/novuhq/novu-framework-nextjs-react-email-example).
+Once you've built the workflow, you might want read one of [our other guides](/legacy-guides/framework-guides/product) on how to empower product teams to manage notification workflows.
+
+
+Don’t forget to share your workflows with us and as always, hit us up on Discord
+with any questions you might have!
diff --git a/legacy-guides/framework-guides/framework-remix.mdx b/legacy-guides/framework-guides/framework-remix.mdx
new file mode 100644
index 00000000..ba26a774
--- /dev/null
+++ b/legacy-guides/framework-guides/framework-remix.mdx
@@ -0,0 +1,335 @@
+---
+title: "How to send notifications with Remix and React email"
+description: "Learn how to send email notifications with Remix, React email and Novu"
+---
+
+# Introduction
+
+Learn how to send notifications with Remix, React email and Novu. You can check out the complete code for a [working app](https://github.com/novuhq/novu-framework-remix-example).
+
+## Prerequisites
+
+- A Novu account
+- Node installed on your machine
+- A working Remix app
+
+## Follow these Steps
+
+### 1. Install all dependencies including react email components
+
+```jsx
+ npm install @novu/framework @react-email/components react-email
+```
+
+### 2. Integrate Novu with Remix
+
+Within the `app/routes` directory, create an `api.novu.tsx` file.
+
+```jsx
+// app/routes/api.novu.tsx
+
+import { serve } from "@novu/framework/remix";
+import { client, signUpWorkflow } from "~/novu/workflows";
+
+const handler = serve({
+ client: client,
+ workflows: [signUpWorkflow],
+});
+
+export { handler as action, handler as loader };
+```
+
+### 3. Create an email template in your Remix app
+
+Within the `app` directory, create an `emails` folder and add an email template file to it.
+
+In this scenario, create a `vercel-invite-user.tsx` file and the code below to it:
+
+```ts
+// app/emails/vercel-invite-user.tsx
+
+import {
+ Body,
+ Button,
+ Container,
+ Column,
+ Head,
+ Heading,
+ Hr,
+ Html,
+ Img,
+ Link,
+ render,
+ Row,
+ Section,
+ Text,
+ Tailwind,
+} from "@react-email/components";
+
+interface VercelInviteUserEmailProps {
+ username?: string;
+ userImage?: string;
+ invitedByUsername?: string;
+ invitedByEmail?: string;
+ teamName?: string;
+ teamImage?: string;
+ inviteLink?: string;
+ inviteFromIp?: string;
+ showJoinButton?: boolean;
+ inviteFromLocation?: string;
+ buttonText?: string;
+}
+
+const baseUrl = process.env.VERCEL_URL
+ ? `https://${process.env.VERCEL_URL}`
+ : "";
+
+export const VercelInviteUserEmail = ({
+ username,
+ userImage,
+ invitedByUsername,
+ invitedByEmail,
+ teamName,
+ teamImage,
+ inviteLink,
+ inviteFromIp,
+ inviteFromLocation,
+ showJoinButton,
+ buttonText,
+}: VercelInviteUserEmailProps) => {
+ return (
+
+
+
+
+
+
+
+
+
+ Join {teamName} on Vercel
+
+
+ Hello {username},
+
+
+ {invitedByUsername} (
+
+ {invitedByEmail}
+
+ ) has invited you to the {teamName} team on
+ Vercel
+ .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {showJoinButton && (
+
+
+
+ )}
+
+ or copy and paste this URL into your browser:{" "}
+
+ {inviteLink}
+
+
+
+
+ This invitation was intended for{" "}
+ {username}. This invite was
+ sent from {inviteFromIp}{" "}
+ located in{" "}
+ {inviteFromLocation}. If you
+ were not expecting this invitation, you can ignore this email. If
+ you are concerned about your account's safety, please reply to
+ this email to get in touch with us.
+
+
+
+
+
+ );
+};
+
+VercelInviteUserEmail.PreviewProps = {
+ username: "alanturing",
+ userImage: `${baseUrl}/static/vercel-user.png`,
+ invitedByUsername: "Alan",
+ invitedByEmail: "alan.turing@example.com",
+ teamName: "Enigma",
+ teamImage: `${baseUrl}/static/vercel-team.png`,
+ inviteLink: "https://vercel.com/teams/invite/foo",
+ inviteFromIp: "204.13.186.218",
+ inviteFromLocation: "São Paulo, Brazil",
+} as VercelInviteUserEmailProps;
+
+export default VercelInviteUserEmail;
+
+export function renderEmail(input: any, payload: any) {
+ return render();
+}
+```
+
+### 4. Create a Novu Workflow
+
+Next, create a Novu workflow with an email step. This code-first notification workflow approach makes it easy for product teams to modify notification content.
+
+Within the `app` directory, create an `novu` folder and add a `workflows.ts` file to it. Copy/paste the code below to the recently created file.
+
+```jsx
+// app/novu/workflows.ts
+
+import { Client, workflow } from "@novu/framework";
+import { renderEmail } from "~/emails/vercel-invite-user";
+
+export const client = new Client({
+ apiKey: process.env.NOVU_SECRET_KEY,
+ /**
+ * Disable this flag only during local development
+ * For production this should be true
+ */
+ strictAuthentication: process.env.NODE_ENV !== "development",
+});
+
+export const signUpWorkflow = workflow(
+ "new-signup",
+ async ({ step, payload }) => {
+ // Send a welcome email
+ await step.email(
+ "send-email",
+ async (inputs) => {
+ return {
+ subject: `Welcome to sending emails with Novu & Remix`,
+ body: renderEmail(inputs, payload),
+ };
+ },
+ {
+ controlSchema: {
+ type: "object",
+ properties: {
+ showJoinButton: { type: "boolean", default: true },
+ buttonText: { type: "string", default: "Join the team" },
+ userImage: {
+ type: "string",
+ default:
+ "https://react-email-demo-bdj5iju9r-resend.vercel.app/static/vercel-user.png",
+ format: "uri",
+ },
+ invitedByUsername: { type: "string", default: "Alan" },
+ invitedByEmail: {
+ type: "string",
+ default: "alan.turing@example.com",
+ format: "email",
+ },
+ teamName: { type: "string", default: "Team Awesome" },
+ teamImage: {
+ type: "string",
+ default:
+ "https://react-email-demo-bdj5iju9r-resend.vercel.app/static/vercel-team.png",
+ format: "uri",
+ },
+ inviteLink: {
+ type: "string",
+ default: "https://vercel.com/teams/invite/foo",
+ format: "uri",
+ },
+ inviteFromIp: { type: "string", default: "204.13.186.218" },
+ inviteFromLocation: {
+ type: "string",
+ default: "São Paulo, Brazil",
+ },
+ },
+ },
+ }
+ );
+ // JSON Schema for validation and type-safety. Zod, and others coming soon.
+ },
+ { payloadSchema: { properties: { text: { type: "string" } } } }
+);
+```
+
+### 5. Preview Email Workflow & Sync to Novu Cloud
+
+Open Novu Dev Studio to preview and make changes to the email workflow as needed via the command below:
+
+```jsx
+npx novu-labs@latest echo
+```
+
+1. Run the Studio
+
+ {" "}
+
+
+**Note:** Use the port on which your Remix app is running for the [Bridge Endpoint](/concepts/endpoint) so that the Novu Dev Studio can connect to your API route as highlighted in the image above.
+
+2. Check out the signup email workflow and test
+
+
+ {" "}
+
+
+3. Deploy to Novu Cloud when you're done.
+
+On the top right(as seen in the image above) of the Novu Dev Studio, you can sync to Novu Cloud when you're done working locally.
+
+**Note:** You'll need to create a local tunnel that the Novu Cloud environment can reach for local experimentation purposes. Ngrok is a good tunnel.
+
+### 6. Send a Notification
+
+Trigger a notification using the recently deployed workflow either via your [Novu Cloud dashboard](https://dashboard-v2.novu.co).
+
+```js
+import { Novu } from "@novu/node";
+
+const novu = new Novu("");
+
+novu.trigger("new-signup", {
+ to: {
+ subscriberId: "789",
+ },
+});
+```
+
+Once you've built the workflow, you might want read one of [our other guides](/legacy-guides/framework-guides/product) on how to empower product teams to manage notification workflows.
diff --git a/legacy-guides/framework-guides/framework-svelte.mdx b/legacy-guides/framework-guides/framework-svelte.mdx
new file mode 100644
index 00000000..38f8a826
--- /dev/null
+++ b/legacy-guides/framework-guides/framework-svelte.mdx
@@ -0,0 +1,319 @@
+---
+title: "How to send notifications with Svelte and Svelte email"
+description: "Learn how to send email notifications with Svelte, Svelte email and Novu"
+---
+
+# Introduction
+
+Learn how to send notifications with Svelte, Svelte email and Novu. You can check out the complete code for a [working app](https://github.com/novuhq/novu-svelte-email).
+
+## Prerequisites
+
+- A Novu account
+- Node installed on your machine
+- A working SvelteKit app
+
+## Follow these Steps
+
+### 1. Install all dependencies
+
+```jsx
+ npm install @novu/framework svelte-email
+```
+
+### 2. Integrate Novu with SvelteKit
+
+Within the `src/routes` directory, create an `api/novu` folder and add a `+server.ts` file to it.
+
+```jsx
+// src/routes/api/novu/+server.ts
+
+import { svelteWorkflow, client } from "$lib/novu/workflows";
+import { serve } from "@novu/framework/sveltekit";
+
+export const { GET, POST, PUT } = serve({
+ client: client,
+ workflows: [svelteWorkflow],
+});
+```
+
+### 3. Create an email template in Svelte
+
+Within the `src/lib` directory, create an `emails` folder and add an email template in a `.svelte` file to it.
+
+For example, `airbnb-review.svelte`:
+
+```ts
+// src/lib/emails/airbnb-review.svelte
+
+
+
+
+
+
+
+
+ {#if showLogoImage}
+
+ {/if}
+
+
+
+ Here's what {authorName} wrote
+ {reviewText}
+
+ Now that the review period is over, we’ve posted {authorName}’s review to your Airbnb
+ profile.
+
+
+ While it’s too late to write a review of your own, you can send your feedback to {authorName}
+ using your Airbnb message thread.
+
+
+ {#if showFeedbackButton}
+
+ {/if}
+
+
+ Common questions
+
+ How do reviews work?
+
+
+
+ How do star ratings work?
+
+
+
+
+ Can I leave a review after 14 days?
+
+
+
+ {bottomAddress}
+ Report unsafe behavior
+
+
+
+```
+
+### 4. Create a Novu Workflow
+
+Next, create a Novu workflow with an email step. This code-first notification workflow approach makes it easy for product teams to modify notification content.
+
+```jsx
+// src/lib/novu/workflows.ts
+
+import { Client, workflow } from "@novu/framework";
+import { render } from "svelte-email";
+import AirbnbReview from "$lib/emails/airbnb-review.svelte";
+
+export const client = new Client({
+ /**
+ * The Novu API key is needed for when you
+ * need to sync to Novu Cloud
+ */
+ apiKey: process.env.NOVU_SECRET_KEY,
+ /**
+ * Disable this flag only during local development
+ * For production this should be true
+ */
+ strictAuthentication: process.env.NODE_ENV !== "development",
+});
+
+export const svelteWorkflow = workflow(
+ "airbnb-review",
+ async ({ step, payload }) => {
+ await step.email(
+ "send-email",
+ async (inputs) => {
+ const html = render({
+ template: AirbnbReview,
+ props: {
+ authorName: inputs.authorName,
+ showFeedbackButton: inputs.showFeedbackButton,
+ showLogoImage: inputs.showLogoImage,
+ feedbackButtonText: inputs.feedbackButtonText,
+ bottomAddress: inputs.bottomAddress,
+ authorImage: inputs.authorImage,
+ logoImage: inputs.logoImage,
+ },
+ });
+ return {
+ subject: `Here's Your Host Feedback`,
+ body: html,
+ };
+ },
+ {
+ controlSchema: {
+ type: "object",
+ properties: {
+ showFeedbackButton: { type: "boolean", default: true },
+ showLogoImage: { type: "boolean", default: true },
+ authorName: { type: "string", default: "Alex" },
+ feedbackButtonText: { type: "string", default: "Send My Feedback" },
+ authorImage: {
+ type: "string",
+ default:
+ "https://react-email-demo-bdj5iju9r-resend.vercel.app/static/vercel-user.png",
+ format: "uri",
+ },
+ logoImage: {
+ type: "string",
+ default:
+ "https://svelte-email-rjaapma15-konzeptfabrik.vercel.app/airbnb-logo.png",
+ format: "uri",
+ },
+ bottomAddress: {
+ type: "string",
+ default: "Airbnb, Inc., 888 Brannan St, San Francisco, CA 94103",
+ },
+ },
+ },
+ }
+ );
+ }
+);
+```
+
+### 5. Preview Email Workflow & Sync to Novu Cloud
+
+Open Dev Studio to preview and make changes to the email workflow as needed via the command below:
+
+```jsx
+npx novu-labs@latest echo
+```
+
+1. Run the Studio
+
+ {" "}
+
+
+**Note:** Use the port on which your Svelte app is running for the Bridge endpoint so that the Novu Dev Studio can connect to your API route.
+
+2. Check out the email workflow and test
+
+
+ {" "}
+
+
+3. Deploy to Novu Cloud when you're done.
+
+On the top right of the Novu Dev Studio, you can sync to Novu Cloud when you're done working locally.
+
+**Note:** You'll need to create a local tunnel that the Novu Cloud environment can reach for local experimentation purposes. Ngrok is a good tunnel.
+
+### 6. Send a Notification
+
+Trigger a notification using the recently deployed workflow either via your [Novu Cloud dashboard](https://dashboard-v2.novu.co) or [code](/inbox/react/get-started).
+
+Once you've built the workflow, you might want read one of [our other guides](/legacy-guides/framework-guides/product) on how to empower product teams to manage notification workflows.
diff --git a/legacy-guides/framework-guides/otp.mdx b/legacy-guides/framework-guides/otp.mdx
new file mode 100644
index 00000000..7fd01ee9
--- /dev/null
+++ b/legacy-guides/framework-guides/otp.mdx
@@ -0,0 +1,330 @@
+---
+title: "How to send OTP verification email notifications with React-email"
+description: "Learn how to send OTP verification email notifications with React-email in your NextJS app"
+---
+
+## Introduction
+
+In this guide, you’ll learn how to send OTP verification email notifications using the React-email package. Follow these steps:
+
+## Getting started
+
+Integrating Novu’s code-first workflow with React.Email for your Next.js application can be done in a few steps:
+
+1. Create a NextJS app and wait for the installation:
+
+```bash
+npx novu init@latest --secret-key=
+
+// The command will ask you if you want to include react-email into your new project.
+```
+
+2. Once this installation is complete, simply `cd` into the directory and start your app using the `npm run dev` command.
+
+## Using a code-first workflow
+
+1. Write an email template - To write an email template, you can look over some of the examples in the [React Email](https://react.email/examples) documentation to get inspiration. In our case, this is the template:
+
+```ts
+import {
+ Body,
+ Button,
+ Container,
+ Head,
+ Heading,
+ Hr,
+ Html,
+ Img,
+ Link,
+ Preview,
+ Section,
+ Text,
+ render,
+} from "@react-email/components";
+import * as React from "react";
+
+interface LinearLoginCodeEmailProps {
+ validationCode?: string;
+ showJoinButton?: boolean;
+ buttonText?: string;
+ inviteLink?: string;
+ logoURL?: string;
+ inviteFromLocation?: string;
+ inviteFromIp?: string;
+ supportEmail?: string;
+}
+
+export const LinearLoginCodeEmail = ({
+ validationCode,
+ showJoinButton,
+ buttonText,
+ inviteLink,
+ logoURL,
+ inviteFromIp,
+ inviteFromLocation,
+ supportEmail,
+}: LinearLoginCodeEmailProps) => (
+
+
+ Your login code for Linear
+
+
+
+ Your login code for Linear
+
+ {showJoinButton && (
+
+
+
+ )}
+
+
+ This link and code will only be valid for the next 5 minutes. If the
+ link does not work, you can use the login verification code directly:
+
+
+ {validationCode}110658
+
+
+ Not expecting this email?
+
+ This invite was sent from{" "}
+ {inviteFromIp} located in{" "}
+ {inviteFromLocation}.
+
+
+ Please contact{" "}
+
+ {supportEmail}
+ {" "}
+ if you did not request this code.
+
+
+
+
+);
+
+LinearLoginCodeEmail.PreviewProps = {
+ validationCode: "tt226-5398x",
+} as LinearLoginCodeEmailProps;
+
+export default LinearLoginCodeEmail;
+
+const logo = {
+ borderRadius: 21,
+ width: 42,
+ height: 42,
+};
+
+const main = {
+ backgroundColor: "#ffffff",
+ fontFamily:
+ '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif',
+};
+
+const container = {
+ margin: "0 auto",
+ padding: "20px 0 48px",
+ maxWidth: "560px",
+};
+
+const secondary = {
+ color: "#000",
+ display: "inline-block",
+ fontFamily: "HelveticaNeue-Medium,Helvetica,Arial,sans-serif",
+ fontSize: "20px",
+ fontWeight: 500,
+ lineHeight: "24px",
+ marginBottom: "0",
+ marginTop: "2rem",
+ textAlign: "center" as const,
+};
+
+const paragraphSupportText = {
+ fontSize: "15px",
+ fontWeight: "700",
+};
+
+const paragraph = {
+ margin: "0 0 15px",
+ fontSize: "15px",
+ lineHeight: "1.4",
+ color: "#3c4149",
+};
+
+const buttonContainer = {
+ padding: "27px 0 27px",
+};
+
+const button = {
+ backgroundColor: "#5e6ad2",
+ borderRadius: "3px",
+ fontWeight: "600",
+ color: "#fff",
+ fontSize: "15px",
+ textDecoration: "none",
+ textAlign: "center" as const,
+ display: "block",
+ padding: "11px 23px",
+};
+
+const hr = {
+ borderColor: "#dfe1e4",
+ margin: "42px 0 26px",
+};
+
+const code = {
+ color: "#000",
+ display: "inline-block",
+ fontFamily: "HelveticaNeue-Bold",
+ fontSize: "16px",
+ fontWeight: 700,
+ letterSpacing: "6px",
+ lineHeight: "40px",
+ paddingBottom: "8px",
+ paddingTop: "8px",
+ margin: "0 auto",
+ width: "100%",
+ textAlign: "center" as const,
+};
+
+const paragraphSupport = {
+ color: "#444",
+ fontSize: "15px",
+ fontFamily: "HelveticaNeue,Helvetica,Arial,sans-serif",
+ letterSpacing: "0",
+ lineHeight: "23px",
+ padding: "0 40px",
+ margin: "0",
+ textAlign: "center" as const,
+};
+
+const link = {
+ color: "#444",
+ textDecoration: "underline",
+};
+
+const codeContainer = {
+ background: "rgba(0,0,0,.05)",
+ borderRadius: "4px",
+ margin: "16px auto 14px",
+ verticalAlign: "middle",
+ width: "280px",
+};
+
+export function renderOTPEmail(payload: any) {
+ return render();
+}
+```
+
+2. Launch Dev Studio - Novu’s code-first approach lets you see how the template would look when rendered, right when defining it, using the Dev Studio. To launch the dev studio locally you can run `npx novu-labs@latest echo`. The Dev Studio will be started by default on port 2022, and accessible via: http://localhost:2022
+
+
+ {" "}
+
+
+3. Define a workflow that uses that template to send notifications - In this step, we need to define a workflow that uses the template we wrote above to render the email notification:
+
+```ts
+import { Client, workflow } from "@novu/framework";
+import { renderOTPEmail } from "./otp";
+
+export const client = new Client({
+ apiKey: process.env.NOVU_SECRET_KEY,
+ /**
+ * Disable this flag only during local development
+ */
+ strictAuthentication: process.env.NODE_ENV !== "development",
+});
+
+export const otpFlow = workflow(
+ "otp-flow",
+ async ({ step, payload }) => {
+ // Send a welcome email
+ await step.email(
+ "send-email",
+ async (inputs) => {
+ return {
+ subject: `Here's your verification code, Sumit!`,
+ body: renderOTPEmail(inputs, payload),
+ };
+ },
+ {
+ controlSchema: {
+ type: "object",
+ properties: {
+ showJoinButton: { type: "boolean", default: true },
+ buttonText: { type: "string", default: "Login to Linear" },
+ logoURL: {
+ type: "string",
+ default:
+ "https://react-email-demo-7qy8spwep-resend.vercel.app/static/linear-logo.png",
+ format: "uri",
+ },
+ supportEmail: { type: "string", default: "support@linear.co" },
+ validationCode: { type: "string", default: "tt226-5398x" },
+ inviteLink: {
+ type: "string",
+ default: "https://linear.app",
+ format: "uri",
+ },
+ inviteFromIp: { type: "string", default: "204.13.186.218" },
+ inviteFromLocation: {
+ type: "string",
+ default: "São Paulo, Brazil",
+ },
+ },
+ },
+ }
+ );
+ // JSON Schema for validation and type-safety. Zod, and others coming soon.
+ },
+ { payloadSchema: { properties: { text: { type: "string" } } } }
+);
+```
+
+4. Triggering the workflow - Lastly, we need to trigger the workflow we created above. Here’s how to trigger it:
+
+```ts
+import { Novu } from "@novu/node";
+
+const novu = new Novu("");
+
+export async function POST(request: Request) {
+ const res = await request.json();
+
+ await novu.trigger("otp-flow", {
+ to: {
+ subscriberId: "new-user",
+ },
+ payload: {
+ email: res.email,
+ username: res.username,
+ },
+ });
+
+ console.log("triggered");
+ return Response.json({ success: true });
+}
+```
+
+When we trigger this workflow, here’s the email received on the client-side:
+
+
+ {" "}
+
+That’s it!
+
+That’s how you create and use an OTP workflow. You can check out [our docs](https://docs.novu.co/guides/framework-guides/framework-react-email) for a hands on guide with more in-depth instructions.
+
+Once you've built the workflow, you might want read one of [our other guides](/legacy-guides/framework-guides/product) on how to empower product teams to manage notification workflows.
+
+Don’t forget to share your workflows with us and as always, hit us up on Discord with any questions you might have!
diff --git a/legacy-guides/framework-guides/product.mdx b/legacy-guides/framework-guides/product.mdx
new file mode 100644
index 00000000..295738fd
--- /dev/null
+++ b/legacy-guides/framework-guides/product.mdx
@@ -0,0 +1,55 @@
+---
+title: "Empowering Product Teams to Manage Notification Workflows"
+description: "Enabling product teams to independently manage and optimize notification workflows for improved efficiency and user experience"
+---
+
+# Introduction
+
+Novu’s code-first workflow empowers product teams to manage notification for end-users independently once the engineering teams have built and delivered the notification platform for the product.
+
+Let’s see how product teams can leverage it:
+
+1. **Engineering Team sets things up:** Engineering teams integrate Novu’s code-first workflows into their application. This involves setting up the structure of notification workflows in the codebase, defining events, steps, inputs, payload, etc. These workflows are functions that execute business logic and need to be set only once.
+2. **Empowering Non-Technical Teams:** Once the foundational workflows are established, product teams can take over! Code-first workflows allow non-technical users to modify workflows safely without breaking critical integrations. Through an exposed Inputs interface, product teams can adjust notification content and behaviour, tailoring the user experience without needing to dive into the code at all. This helps both teams win - product teams are empowered to manage and optimize notifications independently, creating a dynamic and user-centric notification system without constant reliance on engineering teams.
+3. **Insights and Optimization**: Further, with Novu’s Web Dashboard, Product teams gain valuable insights into last-mile delivery from the activity monitor. This enables them to refine notification strategies and optimize for better user satisfaction.
+
+This collaborative approach between the Engineering and Product teams allows both teams to focus on their strengths, resulting in a more efficient and effective notification strategy, without constant interruptions in each other’s flow.
+
+## Customising the workflow
+
+To demonstrate the customizability, we’ll assume that the Engineering team has successfully delivered the basic setup as outlined in [one of our guides.](https://docs.novu.co/guides/framework-guides/framework-react-email)
+
+1. To start things off, you only need to visit the workflow section in our [Web Dashboard](https://dashboard-v2.novu.co/).
+
+ {" "}
+
+
+- You’ll see the blue lightning bolt icon right next to the workflow name symbolising that it is a code-first workflow.
+- Now, simply click on the workflow to open it and make your desired changes.
+- The green `Sync` indicator on the top right corner means that your app is successfully connected with Novu.
+
+2. Once you open a workflow, you’ll see all the steps of the workflow:
+
+ {" "}
+
+3. Simply, select a node to see the rendered view. This is what will get sent to the end user.
+
+ {" "}
+
+4. To start making changes, simply click on either of the two buttons highlighted below:
+
+ {" "}
+
+5. Once you click on it, you’ll be taken to the edit view where you’ll see real-time preview of the workflow. You can make changes to the step inputs on the right-hand side and the rendered view will update in real-time to reflect those changes
+
+ {" "}
+
+6. Let's take a look at what payload and step inputs are:
+7. In almost all the scenarios, you’ll be tweaking and modifying step inputs and the workflow editor makes it blazing fast and effortlessly simple. Simply change the text of the respective field or button or toggle any button on or off till the rendered view becomes what you want your end users to receive. Here’s how you can tweak it:
+
+ {" "}
+
+
+## Conclusion
+
+The collaboration resulting from Novu’s code-first workflow increases agility, enabling rapid iterations and quick adjustments based on user feedback. It fosters effective collaboration between product and engineering teams, resulting in a more efficient workflow and improved user notifications.
diff --git a/legacy-guides/headless-notification-center-guide.mdx b/legacy-guides/headless-notification-center-guide.mdx
new file mode 100644
index 00000000..b90ede2e
--- /dev/null
+++ b/legacy-guides/headless-notification-center-guide.mdx
@@ -0,0 +1,463 @@
+---
+title: "How to use Headless Notification Center"
+description: "Use the headless version of the notification center"
+---
+
+## Introduction
+
+In this guide, you'll learn about how to use the headless version of our notification center. The headless version is useful for scenarios in which a lightweight solution for integrating notification functionality into a web app that is completely unstyled (so that you can style it as per your wish) is required.
+
+
+You can find the entire code ( front-end as well as back-end ) of this app [here.](https://github.com/novuhq/novu-headless-demo-app)
+
+## What is the headless notification center
+
+The headless version of Novu's notification library package provides users with a lightweight solution for integrating notification functionality into their web applications.
+
+
+With just the essential API methods, users can easily incorporate our notification
+system into any framework or vanilla JavaScript project, without being constrained
+by our default UI or dependencies.
+
+This gives the users of the headless notification center greater flexibility and control over what the end-users of a product see and experience.
+
+## How to use the headless notification center
+
+To get started with this you need:
+
+1. A Novu account. [Sign up for free](http://dashboard.novu.co/?utm_campaign=docs-gs-guides-headless) if you don’t have one yet.
+2. A working dev environment in the framework of your choice (or vanilla JS). Here, we'll use a React environment
+
+## Workflow set up in Novu
+
+Once, you have signed up for Novu and have a dev environment, follow the steps below:
+
+1. Head over to the Novu Dashboard.
+2. Click **"Workflows"** on the left sidebar of your Novu dashboard.
+3. Click the **"Create Workflow"** button on the top right:
+
+ {" "}
+
+ Once you click the **"Create Workflow"** button, you’ll see a dropdown. Select `blank
+ workflow` from the dropdown:
+
+ {" "}
+
+ You’ll now be taken to the workflow editor:
+
+ {" "}
+
+ Once here, you can add the channels you want to use for sending notifications
+ and configure them. For this guide, we’ll use the `In-App` channel.
+
+ {" "}
+
+ The in-app node allows you to further customize the notifications that will
+ get sent, as per your need. In our case, it is a simple `description`, which will
+ contain the text that will be entered from the front end. Here's the `in-app`
+ node for your reference:
+
+
+ {" "}
+
+
+Here's a brief overview of all the options:
+
+- **1-Preview**: This shows you a glimpse of what each notification item will look like in the Notification Center UI.
+- **2-Avatar:**: If turned on, each notification item will show the avatar of the subscriber.
+- **3-Action:**: With this, you can add a primary and secondary call to action button to each notification item.
+- **4-Notification Feeds:**: This displays a stream of specific notifications. You can have multiple feeds to show specific notifications in multiple tabs.
+- **5-Redirect URL**: This is the URL to which a subscriber can be directed when they click on a notification item.
+- **6-Filter**: This feature allows you to configure the criteria for delivering notifications. For instance, you can apply a filter based on a subscriber's online status to send them an email if they were online within the last hour.
+
+Once you’re done configuring this to your liking, click on the `update` button on the top right. It'll automatically create a trigger code that you can use in your app. To get it, click on the `get snippet` button on the top right and copy it:
+
+
+ {" "}
+
+
+Let's now see how to add it to our app!
+
+## Pre-requisites:
+
+To be able to use the headless notification center, you'll first need the following:
+
+1. `applicationIdentifier`: You can find this from the `settings` menu in the [Novu web dashboard.](https://dashboard-v2.novu.co/settings?utm_campaign=docs-guides-headless)
+2. `subscriberID`: In most real-world apps, you'll get this from a database. It is used to uniquely identify the entity to whom a notification will be delivered. In our case, we're assuming the `subscriberID` to be `12345`.
+
+## Setting up the back end:
+
+For our demo app, we're setting up the back end as follows:
+
+1. Install the Novu package. Open your terminal and run the following command:
+
+```bash
+npm install @novu/node
+```
+
+Once installed, you can import Novu into your app and initialize it using your Novu account credentials. This step establishes a connection between your app and the Novu notification service.
+
+```bash
+import { Novu } from '@novu/node';
+const novu = new Novu('');
+```
+
+
+Replace the `` value with the authentic key from the API Key section of your [Novu Dashboard.](http://dashboard.novu.co/?utm_campaign=docs-guides-headless)
+
+
+🔑 Please do not hardcode your credentials in a file in production. Use environment variables instead.
+
+
+Using the above info, we'll write a function that will help us interact with Novu's notification system:
+
+```javascript
+import { Novu } from "@novu/node";
+
+export const notification = async (description) => {
+ const novu = new Novu("");
+ await novu.subscribers.identify(process.env.SUSBSCRIBER_ID, {
+ firstName: "newSubForHeadless",
+ });
+
+ // code snippet copied when creating the workflow
+ await novu.trigger("headless-demo", {
+ to: {
+ subscriberId: process.env.SUSBSCRIBER_ID,
+ },
+ payload: {
+ description: description,
+ },
+ });
+};
+```
+
+After this, we'll set up a simple controller function for handling notifications:
+
+```javascript
+import { notification } from "../novu/novu.js";
+
+export const notifController = async (req, res) => {
+ const { description } = req.body;
+ try {
+ await notification(description);
+ res.status(201).json({ message: "Subscriber created successfully" });
+ } catch (error) {
+ console.log("notifController error:", error);
+ res.status(500).json({ message: error.message });
+ }
+};
+```
+
+And finally, we'll need to establish routes that we'll hit from the front end:
+
+```javascript
+import express from "express";
+import { notifController } from "../controller/notif.js";
+
+const router = express.Router();
+
+router.post("/create-sub", notifController);
+
+export default router;
+```
+
+Now, we'll move to the front end because that's where all the magic happens!
+
+## Setting up the front end:
+
+In the front end, install the headless package:
+
+```bash
+npm install @novu/headless
+```
+
+Then, import it and initialize the headless service:
+
+```javascript
+import { HeadlessService, FetchResult, ISession } from "@novu/headless";
+
+useEffect(() => {
+ const headlessService = new HeadlessService({
+ applicationIdentifier: "SWMw97ec1ZNA",
+ subscriberId: "12345",
+ });
+
+ headlessService.initializeSession({
+ listener: (res) => {},
+ onSuccess: (session) => {
+ headlessServiceRef.current = headlessService;
+ fetchNotifications();
+ },
+ onError: (error) => {
+ console.log("headlessSice error:", error);
+ },
+ });
+}, [fetchNotifications]);
+```
+
+We've added the `fetchNotifications` function inside a useEffect because we want the effect to run whenever the `fetchNotifications` executes. Here's the `fetchNotifications` function for reference:
+
+```javascript
+const fetchNotifications = useCallback(() => {
+ const headlessService = headlessServiceRef.current;
+ if (headlessService) {
+ headlessService.fetchNotifications({
+ listener: ({ data, error, isError, isFetching, isLoading, status }) => {
+ // Handle the state of the fetching process and errors here.
+ },
+ onSuccess: (response) => {
+ // Handle the fetched notifications here.
+ setNotifications(response.data); // Store notifications in the state
+ },
+ page: pageNum, // page number to be fetched
+ });
+ }
+}, [pageNum]);
+```
+
+Here, we've memoized the `fetchNotifications` to optimize performance and passed `pageNum` in a dependency array so that it reruns when the page number changes.
+
+
+You can refer to the entire context [here](https://github.com/novuhq/novu-headless-demo-app/blob/main/frontend/src/context/NotificationContext.js)
+for a better understanding.
+
+We've also used several methods related to the Headless Notification Center package. You can find the complete list [here.](/inbox/headless/get-started)
+
+The methods that we've used in our app are `markNotificationsAsRead`, `deleteNotification`, and `markAllMessagesAsRead`. They are as follows:
+
+```javascript
+const markNotificationsAsRead = (messageIds) => {
+ if (!Array.isArray(messageIds)) {
+ messageIds = [messageIds];
+ }
+
+ const headlessService = headlessServiceRef.current;
+
+ if (headlessService) {
+ headlessService.markNotificationsAsRead({
+ messageId: messageIds,
+ listener: (result) => {},
+ onError: (error) => {
+ console.error("Error marking notifications as read:", error);
+ },
+ });
+ }
+};
+
+const deleteNotification = (messageId) => {
+ const headlessService = headlessServiceRef.current;
+ if (headlessService) {
+ headlessService.removeNotification({
+ messageId: messageId,
+ listener: function (result) {},
+ onSuccess: function (message) {},
+ onError: function (error) {
+ console.error(error);
+ },
+ messageIds: "messageOne",
+ });
+ }
+};
+
+const markAllMessagesAsRead = (feedId) => {
+ const headlessService = headlessServiceRef.current;
+
+ headlessService.markAllMessagesAsRead({
+ listener: (result) => {
+ console.log(result);
+ // Handle the result of marking all messages as read
+ // You can update the state or perform other actions here
+ },
+ onError: (error) => {
+ console.error("Error marking all messages as read:", error);
+ // Implement error handling if needed
+ },
+ feedId: feedId, // Pass the feed ID here, it can be an array or a single ID
+ });
+};
+```
+
+
+ You can view the entire API reference
+ [here](/inbox/headless/get-started) and pick up the
+ methods that you'd like to use for your specific requirements.
+
+
+### Coding the UI up:
+
+On the UI front, we have a simple form with a submit handler function and a couple of components:
+
+```javascript
+const Body = () => {
+ const [description, setDescription] = useState("");
+ const { active, setActive } = useNotification();
+ const onSubmitHandler = async (e) => {
+ e.preventDefault();
+ await axios.post(
+ "https://headless-backend-qx89.onrender.com/api/v1/create-sub",
+ { description }
+ );
+
+ setDescription("");
+ setActive(true);
+ };
+ const onChangeHandler = (e) => {
+ setDescription(e.target.value);
+ };
+ return (
+
+
Enter notification text
+
+
+ );
+};
+```
+
+The first component is the `appBar` component which includes a bell icon, which can show notifications in a modal when clicked. The modal is displayed when showModal is true, and the modal can be closed by clicking outside of it. The component interacts with the notification-related functionalities provided by the useNotification hook:
+
+```javascript
+const AppBar = () => {
+ const [showModal, setShowModal] = useState(false);
+ const modalRef = useRef(null);
+
+ const { fetchNotifications, setActive, active } = useNotification();
+
+ const handleShowNotification = async (e) => {
+ e.stopPropagation();
+ setShowModal((prev) => !prev);
+ await fetchNotifications();
+ setActive(false);
+ };
+ const handleCloseModal = () => {
+ setShowModal(false);
+ };
+ useEffect(() => {
+ const handleDocumentClick = (e) => {
+ if (modalRef.current && !modalRef.current.contains(e.target)) {
+ handleCloseModal();
+ }
+ };
+ if (showModal) {
+ document.addEventListener("click", handleDocumentClick);
+ }
+ return () => {
+ document.removeEventListener("click", handleDocumentClick);
+ };
+ }, [showModal]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {showModal && }
+
+
+ );
+};
+
+export default AppBar;
+```
+
+And the second component is the `NotificationModal` which is what gets displayed when the user clicks on the bell icon.
+
+
+The beauty of using the headless version of the notification center is that you can customise exactly how it looks and which functionalities you need in it.
+
+Our `NotificationModal` component is as follows:
+```javascript
+const NotificationModal = () => {
+
+ const { notifications, markNotificationsAsRead, markAllMessagesAsRead, deleteNotification, setPageNum, pageNum, fetchNotifications } = useNotification();
+
+ const handleNotificationRead = (notificationId) => {
+ markNotificationsAsRead(notificationId);
+ }
+
+ const handleNotificationDelete = (notificationId) => {
+ let newClass = document.getElementById(`${notificationId}`);
+ newClass.style.display = 'none';
+ deleteNotification(notificationId);
+ }
+
+ const handleMarkAllAsRead = () => {
+ markAllMessagesAsRead();
+ }
+ const onPrvHandler = () => {
+ setPageNum((prv) => prv - 1)
+
+ }
+ const onNxtHandler = () => {
+ setPageNum((prv) => prv + 1)
+
+ }
+
+
+ useEffect(() => {
+ fetchNotifications();
+ }, [pageNum, fetchNotifications])
+ return (
+
+
+
+
+ Notifications
+
+
+
+
+
+
+
+
+
+ {notifications.map(not => (
+
+ <>
+
+ {not?.payload?.description}
+
+
+ {!not?.read && (
+
+ )}
+
+
+ >
+
+ ))
+ }
+
+
+ )
+
+}
+
+```
+
+Finally, we've used some css to style the app to our liking. You can see that [here.](https://github.com/novuhq/novu-headless-demo-app/tree/main/frontend/src/styles).
+## Conclusion
+If you've followed everything till here, you'll end up with an app like the following:
+
+Again, don't forget that you can view the entire code [here](https://github.com/novuhq/novu-headless-demo-app/tree/main) and if you've any questions, either related to this headless app or something else, feel free to ping use [here.](https://discord.gg/novu)
+Also, we've several demo apps for you to check out [here.](https://inbox.novu.co/)
+Thanks for reading!
+```
diff --git a/legacy-guides/novu-fcm-react-native-android.mdx b/legacy-guides/novu-fcm-react-native-android.mdx
new file mode 100644
index 00000000..f3c7412b
--- /dev/null
+++ b/legacy-guides/novu-fcm-react-native-android.mdx
@@ -0,0 +1,411 @@
+---
+title: "How to send push notifications in an Android app (react native) with FCM and Novu"
+description: "Learn how to send FCM push notifications in a React native Android app using Novu and FCM"
+---
+
+## Prerequisites
+
+To complete this tutorial, you will need the following:
+
+- A [Firebase](https://firebase.google.com/) account
+- A [Novu](https://dashboard-v2.novu.co/?utm_campaign=docs-gs-guides-fcm-android) account
+- An [Expo](https://expo.dev) account
+
+Push notifications are notifications that are sent to a user’s devices whether the user is using the app or not. We’ll be using the Firebase Cloud Messaging (FCM) integration of Novu to send these notifications. You can find the complete code for this project in our [Github repo.](https://github.com/novuhq/fcm-react-native-android)
+
+## Start a React-Native project
+
+We'll use the `create-expo` tool to initialize a new expo app. It creates a new React Native app with the expo package installed. We're naming our app `fcm-novu-rn2` and hence using the following command:
+
+```javascript
+ npx create-expo-app fcm-novu-rn2
+```
+
+If it asks you to choose a template, choose the 'blank' template.
+
+Now, go to the project directory and install the `react-native-firebase` package using the following command:
+
+```bash
+npm install --save @react-native-firebase/app
+```
+
+Once this is installed, install the messaging module of Firebase using the following command:
+
+```bash
+npm i --save @react-native-firebase/messaging
+```
+
+## Getting the FCM token
+
+Now, we need to grab the `token key` from FCM. We'll need this to send notifications using FCM. We're using `useEffect` to grab this key as follows:
+
+```jsx
+// import messaging module
+import messaging from "@react-native-firebase/messaging";
+
+// Request permission to send notifications
+const authStatus = await messaging().requestPermission();
+
+// Check if permission is granted or provisional
+const enabled =
+ authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
+ authStatus === messaging.AuthorizationStatus.PROVISIONAL;
+
+// If permission is granted or provisional, proceed
+if (enabled) {
+ // Log the authorization status
+ console.log("Authorization status:", authStatus);
+
+ // Retrieve the FCM token for the device
+ const token = await messaging().getToken();
+ // Log the FCM token
+ console.log("FCM Token is:", token);
+
+ // Return a function to unsubscribe from notifications
+ return unsubscribe;
+} else {
+ // If permission is not granted, log the error status
+ console.log("Could not grab token...", authStatus);
+}
+```
+
+## FCM handlers
+
+FCM uses something called a `background handler` to, as the name suggests handle notifications when the app is in the background. So, we'll add it to our app:
+
+```jsx
+// Background message handler for incoming messages while the app is in bg
+messaging().setBackgroundMessageHandler(async (remoteMessage) => {
+ // Log a message indicating that a message was handled in the background
+ console.log("Message handled in the background!", remoteMessage);
+});
+```
+
+Similarly, we also need a `foreground handler` to handle notifications when the app is in the foreground. In our case, we're updating a state's value in our handler. Here's our foreground handler:
+
+```jsx
+// handle incoming messages while the app is in the foreground
+const unsubscribe = messaging().onMessage(async (remoteMessage) => {
+ // Log a message indicating that the onMessage event is working
+ console.log("working!!!!");
+
+ // Update state with the received message
+ setNotification(remoteMessage);
+});
+return unsubscribe;
+```
+
+## Setting up Expo
+
+In order to load projects from Expo CLI, we need a dev client to be installed in our app. To install it, execute the following command from the root of the project directory:
+
+```bash
+ npx expo install expo-dev-client
+```
+
+Once installed, log into your expo account using the following command:
+
+```bash
+eas login
+```
+
+It'll ask for your username and password and log you in.
+
+Now, we are ready to create our build of the app. Use the following command to create a development build of the app for the Android platform:
+
+```bash
+eas build --profile development --platform android
+```
+
+
+ If expo asks if you want to automatically create an EAS project for your app,
+ choose yes. Do the same when it asks you if you want to generate a new
+ keystore.
+
+
+ Do not change the Android package name, even if EAS asks you to!
+
+
+## Setting up Firebase
+
+1. Create a Firebase account if you don't already have one. Then, create a new project and all the relevant details.
+
+ {" "}
+
+2. Once your project has been created, you'll be greeted with this welcome screen and asked to add Firebase to your app, so let's do that.
+
+ {" "}
+
+3. We'll choose the 'Android' option because we're creating an Android app.
+
+ {" "}
+
+4. Find the package name from the `app.json` file of the project:
+
+ {" "}
+
+5. Add this package name to Firebase:
+
+ {" "}
+
+6. Now, we need to add `SHA1` keys from our project to the Firebase app. Run the following command to obtain `SHA1` key. We'll get two `SHA1` keys, one of which we'll add to Firebase right now and the other one, later on.
+
+```bash
+eas credentials
+```
+
+If it asks you to, choose the 'Android' platform then 'Development' build then 'Keystore' then 'Set up a new keystore' then 'Select Build Credentials' and then 'Create a new build credential configuration'.
+
+7. Now copy the `SHA1` key and add it to Firebase and register the app:
+
+8. Click 'next' on all the other options and finally click 'continue to console' as follows:
+
+9. Go to 'project settings' and scroll all the way down
+
+10. Click on the 'Add fingerprint' button and add the second `SHA1` key here.
+
+11. Now, download the Google services file and add it to the project's root directory. We also need to specify its location in the `app.json` file:
+
+12. Now we need to upload the Firebase server key to expo. Go to project settings and find it under the 'Cloud Messaging' tab.
+
+13. Now, upload that key to expo using the following command:
+``` bash
+expo push:android:upload --api-key
+```
+14. Rebuild the app using the following command and we're good to go!
+``` bash
+eas build --profile development --platform android
+```
+15. If you have an Android device with you, simply scan and install the app. Otherwise, if you want to run it on an emulator, simply copy the link given alongside the QR code and open it on the emulator and then install the app.
+
+16. Start the server to run the app, using the following command:
+
+```bash
+npx expo start --dev-client
+```
+
+Again, you can either scan the QR code or copy and paste the link.
+
+
+ {" "}
+
+
+ Sometimes it doesn't fire up the emulator even when you have it installed. In
+ that case, start the emulator manually and run the server again!
+
+
+## Setting up Novu
+
+We'll need a workflow to trigger notifications to our user's devices. Follow the steps below to create one to ensure we are on the path to making FCM work as expected:
+
+1. Head over to the [Integrations Store](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-guides-fcm-android) on the Novu Web Dashboard and make sure that you've turned on the 'Firebase Cloud Messaging' integration.
+
+ {" "}
+
+
+ If you're doing this for the first time, you'll need to get your service
+ account key from the [Firebase
+ Console](https://console.firebase.google.com/). Read more about it in our
+ [FCM provider docs.](https://docs.novu.co/integrations/providers/push/fcm)
+
+2. Now, we need to create a workflow that we'll trigger from our app to send notifications. Head over to [workflows in the Novu Web Dashboard](https://dashboard-v2.novu.co/workflows?utm_campaign=docs-guides-fcm-android) and click on the 'Add a workflow' button.
+
+ {" "}
+
+3. Choose the 'blank workflow' option from the dropdown menu.
+
+ {" "}
+
+4. Drag and drop the 'Push' node below the Workflow Trigger node.
+
+ {" "}
+
+5. If you hover over the newly added 'Push' node, you'll see an error saying 'Message content and title are missing'.
+
+ {" "}
+
+6. We're going to add a title and body for notifications. So, add them and give your workflow a suitable name.
+
+ {" "}
+
+7. Now, click on the 'Update' and then the 'get snippet' button to get the trigger code.
+
+ {" "}
+
+
+## Create a subscriber and update credentials
+
+A subscriber is essentially the recipient of the notifications sent through Novu’s system. When you create a subscriber, you’re setting up the necessary information for Novu to send targeted notifications to. You can find more about subscribers in [our docs.](https://docs.novu.co/subscribers/subscribers)
+To create a subscriber, we'll use the `identify` method:
+
+```jsx
+// creating new instance of Novu
+const novu = new Novu("");
+
+// creating subscriber
+await novu.subscribers.identify("newSubForRNA", {
+ firstName: "RNA",
+});
+```
+
+Now, we'll add the FCM token that we'd received [earlier](#getting-the-fcm-token) to this subscriber using the `setCredentials` method:
+
+```jsx
+await novu.subscribers.setCredentials("newSubForRNA", PushProviderIdEnum.FCM, {
+ deviceTokens: [""],
+});
+// notice the use of 'newSubForRNA' here. This identifies the subscriber to which we're adding the FCM token
+```
+
+## Sending a push notification using Novu
+
+To send a notification with Novu, we are actually triggering a notification workflow. Here's how to do it using Node:
+
+```jsx
+novu.trigger("rna-2", {
+ to: {
+ subscriberId: "newSubForRNA",
+ },
+ payload: {},
+});
+```
+
+When triggered using the trigger code from above, this is the notification received on the device:
+
+
+ {" "}
+
+
+## Dynamic content
+
+So far, we've sent notifications that have had "hard-coded" content. Now we'll send dynamic data when triggering a notification.
+
+The way to do so is by changing the values of the title and the body to `{{title}}` and `{{body}}` in our workflow.
+
+1. Go to the workflow we'd created earlier and change the values of the title and the body to `{{title}}` and `{{body}}` respectively.
+
+ {" "}
+
+2. Simply add these identifiers to the payload by changing the trigger code:
+
+```jsx
+novu.trigger("rna-2", {
+ to: {
+ subscriberId: "newSubForRNA",
+ },
+ payload: {
+ title: payloadTitle,
+ body: payloadBody,
+ },
+});
+```
+
+
+ We're extracting `payloadTitle` and `payloadBody` from the request body
+
+Now, the notification delivered to the subscriber will contain dynamic data as shown
+below:
+
+ {" "}
+
+
+## Sending images
+
+Up to this point, your notifications have all contained only text. But if you’ve received many notifications, you know that notifications can have rich content, such as images. It’d be great if your notifications showed users a nice image related to their content. Once again, Firebase makes this super simple.
+
+All you need to do is to specify the `URL` of the image in the override as shown below:
+
+```jsx
+novu.trigger("rna-2", {
+ to: {
+ subscriberId: "newSubForRNA",
+ },
+ payload: {
+ title: payloadTitle,
+ body: payloadBody,
+ },
+ overrides: {
+ fcm: {
+ android: {
+ notification: {
+ imageUrl: "URL of the image",
+ },
+ },
+ },
+ },
+});
+```
+
+The following notification is what gets delivered to the subscriber:
+
+
+ {" "}
+
+
+## Handling data type messages
+
+With FCM, you can send `Notification` and `Data` messages. We have handled sending notification messages. Let’s handle data messages. Data type messages are useful when, in addition to the usual data, you want to send some custom data. Maybe whether a subscriber has purchased the pro plan of our product or not, maybe which tier of usage they have purchased etc. You can do this easily by modifying the override. Here's an example of sending the subscription status using the identifier `subStatus`:
+
+```jsx
+novu.trigger("rna-2", {
+ to: {
+ subscriberId: "newSubForRNA",
+ },
+ payload: {
+ title: payloadTitle,
+ body: payloadBody,
+ },
+ overrides: {
+ fcm: {
+ android: {
+ data: {
+ subStatus: "true",
+ },
+ },
+ },
+ },
+});
+```
+
+On the user's device, you can extract this out and have it displayed like this:
+
+
+ {" "}
+
+
+## Priority of a notification
+
+FCM tries to deliver high-priority messages immediately. The priority of a notification can also be altered by changing the overrides, as shown below:
+
+```jsx
+novu.trigger("rna-2", {
+ to: {
+ subscriberId: "newSubForRNA",
+ },
+ payload: {
+ title: payloadTitle,
+ body: payloadBody,
+ },
+ overrides: {
+ fcm: {
+ android: {
+ data: {
+ subStatus: "true",
+ },
+ notification: {
+ priority: "low",
+ },
+ },
+ },
+ },
+});
+```
+
+
+ {" "}
+
+
+## Additional resources
+
+- [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging)
+- [FCM Architectural Overview](https://firebase.google.com/docs/cloud-messaging/fcm-architecture)
diff --git a/legacy-guides/novu-fcm-web.mdx b/legacy-guides/novu-fcm-web.mdx
new file mode 100644
index 00000000..b57df6fd
--- /dev/null
+++ b/legacy-guides/novu-fcm-web.mdx
@@ -0,0 +1,275 @@
+---
+title: "How to send web push notifications with FCM and Novu"
+description: "Send FCM push notifications in a React app using Novu"
+---
+
+## Introduction
+
+In this guide, we'll learn how to send FCM push notifications in a web app using Novu. But before exploring the actual code, let’s understand what a push notification is and how it works.
+You can find the [frontend code](https://github.com/novuhq/fcmWebPushFE) as well as [backend code](https://github.com/novuhq/FCMWebPushBE) for the app on Github.
+
+
+Push notifications are notifications that are sent to a user's devices whether the
+user is using the app or not. We'll be using Firebase Cloud Messaging (FCM) integration
+of Novu to send these notifications. This guide will be broken down into three parts:
+1. Setting up Novu. 2. Setting up Firebase. 3. Adding Firebase to the Frontend. 4.
+Adding Novu to the Backend.
+
+## Setting up Firebase
+
+1. Create a Firebase account if you don't already have one. Then, create a new project and all the relevant details.
+
+ {" "}
+
+2. Once your project has been created, you'll be greeted with this welcome screen and asked to add Firebase to your app, so let's do that.
+
+ {" "}
+
+3. We'll choose the web option because we're creating a web app.
+
+ {" "}
+
+4. Give your app a name and click on the 'Register' button.
+
+ {" "}
+
+5. We're done with the Firebase setup! Now, we'll set up Novu and finally proceed to add both (Novu and FCM) to our app.
+
+## Setting up Novu
+
+We'll need a workflow to trigger notifications to our user's devices. Follow the steps below to create one to ensure we are on the path to make FCM work as expected:
+
+1. Head over to the [Integrations Store](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-guides-fcm-web) on the Novu Web Dashboard and make sure that you've turned on the 'Firebase Cloud Messaging' integration.
+
+ {" "}
+
+
+ If you're doing this for the first time, you'll need to get your service
+ account key from the [Firebase
+ Console](https://console.firebase.google.com/). Read more about it in our
+ [FCM provider docs.](https://docs.novu.co/integrations/providers/push/fcm)
+
+2. Now, we need to create a workflow that we'll trigger from our app to send notifications. Head over to [workflows in the Novu Web Dashboard](https://dashboard-v2.novu.co/workflows?utm_campaign=docs-guides-fcm-web) and click on the 'Add a workflow' button.
+
+ {" "}
+
+3. Choose the 'blank workflow' option from the dropdown menu.
+
+ {" "}
+
+4. Drag and drop the 'Push' node below the Workflow Trigger node.
+
+ {" "}
+
+5. If you hover over the newly added 'Push' node, you'll see an error saying 'Message content and title are missing'.
+
+ {" "}
+
+6. We're going to use the identifiers `title` and `body` for the title and the content of notifications, so let's add them in the 'Push message title' and the 'Push message content' fields respectively. So, add them and give your workflow a suitable name.
+
+ {" "}
+
+7. Now, click on the 'get snippet' button to get the trigger code.
+
+ {" "}
+
+
+## Adding Firebase to the frontend
+
+1. In your project, add Firebase using the following command:
+
+```bash
+npm install firebase
+```
+
+2. Create a new file called `firebase.js` and add the following to it:
+
+```jsx
+import { initializeApp } from "firebase/app";
+import { getMessaging, onMessage } from "firebase/messaging";
+
+//from firebase console
+// Your web app's Firebase configuration
+const firebaseConfig = {
+ apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
+ authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
+ projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
+ storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
+ messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
+ appId: import.meta.env.VITE_FIREBASE_APP_ID,
+};
+
+// Initialize Firebase
+export const app = initializeApp(firebaseConfig);
+
+// Initialize Firebase Cloud Messaging and get a reference to the service
+export const messaging = getMessaging(app);
+
+export const onMessageListener = () =>
+ new Promise((resolve) => {
+ onMessage(messaging, (payload) => {
+ console.log("payload", payload);
+ resolve(payload);
+ });
+ });
+```
+
+3. Now, we need to generate tokens using the `getToken` method provided by Firebase. But we want to use this only when the user has allowed notifications on their end. So let's request notification permission from the user and if granted, we'll generate the token. Add this to your root module:
+
+```jsx
+async function requestPermission() {
+ const permission = await Notification.requestPermission();
+ if (permission === "granted") {
+ // get token
+ const token = await getToken(messaging, {
+ vapidkey: import.meta.env.VITE_VAPID_KEY,
+ });
+ } else if (permission === "denied") {
+ alert("Persmission denied!");
+ }
+}
+useEffect(() => {
+ requestPermission();
+}, []);
+```
+
+4. To use the `getToken` method, you need a Voluntary Application Server Identification or `VAPID key`. Go to your Project Settings -> Cloud Messaging -> Generate key pair (under Web Push Certificates):
+
+ {" "}
+
+5. In order for Firebase's background listener service to work, you need a service worker. Make sure you've created a service worker file `firebase-messaging-sw.js` and add the following to it:
+
+```jsx
+importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js");
+importScripts(
+ "https://www.gstatic.com/firebasejs/8.10.0/firebase-messaging.js"
+);
+
+const firebaseConfig = {
+ apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
+ authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
+ projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
+ storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
+ messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
+ appId: import.meta.env.VITE_FIREBASE_APP_ID,
+};
+
+firebase.initializeApp(firebaseConfig);
+const messaging = firebase.messaging();
+messaging.onBackgroundMessage((payload) => {
+ console.log("notif incoming", payload);
+ const notificationTitle = JSON.parse(JSON.parse(payload.notification)).title;
+ // payload.notification.title;
+ const notificationOptions = {
+ body: payload.notification.body,
+ icon: payload.notification.image,
+ };
+
+ self.registration.showNotification(notificationTitle, notificationOptions);
+});
+```
+
+## Adding Novu to the backend
+
+In your app's backend, add Novu using the following command:
+
+```bash
+npm install @novu/node
+```
+
+Now, create a route that you want to hit when called from the front end. In our app, this is the route:
+
+```jsx
+import express from "express";
+import { createNotif } from "../controller/notif.js";
+
+const router = express.Router();
+
+router.post("/create", createNotif);
+
+export default router;
+```
+
+Now, we need a controller function to handle the logic for what is to be sent in the trigger function’s payload. In our case, this is the controller function:
+
+```jsx
+import { inAppNotification } from "../novu/novu.js";
+
+export const createNotif = async (req, res) => {
+ const { title, body } = req.body;
+ try {
+ await inAppNotification(title, body);
+ res.status(201).json({ message: "success", title: title, body: body });
+ } catch (error) {
+ res.status(409).json({ message: error.message });
+ }
+};
+```
+
+To make it modular, we’ll keep the trigger code in a separate function in a separate file (`novu.js`, in our case) and the trigger function is getting called in the controller function above by the name `createNotif`.
+
+If you’re following the guide, you should already have the trigger function. But before we can add it to our app, we need one key thing - **Subscribers.**
+
+[Subscribers](https://docs.novu.co/subscribers/subscribers) are entities to which the notifications are sent. You can see a list of subscribers in the [Novu dashboard](https://dashboard-v2.novu.co/subscribers?utm_campaign=docs-guides-fcm-web) as well.
+
+We'll create a subscriber in our app. Our backend will be written in Node.js, but we also have backend [SDKs in PHP, .NET, Go, Ruby, Python, and Kotlin](/sdks/introduction). The recommended way to create a subscriber in NodeJS is as follows:
+
+```jsx
+await novu.subscribers.identify(process.env.NOVU_SUB_ID, {
+ firstName: "pushSubscriber",
+});
+```
+
+Here, we're creating a subscriber with the `subscriberID` of whatever value the `env` file contains for the identifier `NOVU_SUB_ID`. You can read more about subscribers [in our docs.](concepts/subscribers).
+
+Back in our app, before we can now add the trigger function, we need to set device identifiers using the [setCredential method](https://docs.novu.co/integrations/providers/push/fcm#set-device-token):
+
+```jsx
+await novu.subscribers.setCredentials(
+ process.env.NOVU_SUB_ID,
+ PushProviderIdEnum.FCM,
+ {
+ deviceTokens: [process.env.DEVICE_TOKEN],
+ }
+);
+```
+
+
+ You'll need to pass the `subscriberID` and the `deviceTokens` in the
+ `setCredentials` method.
+
+
+Finally, we can add the trigger code:
+
+```jsx
+import { Novu, PushProviderIdEnum } from "@novu/node";
+
+export const inAppNotification = async (title, body) => {
+ const novu = new Novu(process.env.NOVU_SECRET_KEY);
+ await novu.subscribers.identify(process.env.NOVU_SUB_ID, {
+ firstName: "pushSubscriber",
+ });
+
+ await novu.subscribers.setCredentials(
+ process.env.NOVU_SUB_ID,
+ PushProviderIdEnum.FCM,
+ {
+ deviceTokens: [process.env.DEVICE_TOKEN],
+ }
+ );
+
+ novu.trigger(process.env.NOVU_WORKFLOW_ID, {
+ to: {
+ subscriberId: process.env.NOVU_SUB_ID,
+ },
+ payload: {
+ title: title,
+ body: body,
+ },
+ });
+};
+```
+
+We're done with the app. You can find the [frontend code](https://github.com/novuhq/fcmWebPushFE) as well as [backend code](https://github.com/novuhq/FCMWebPushBE) for the app on Github.
+
+Congratulations on following the guide up until this point. If you’ve done everything as recommended, you’ll end up with an app that uses Firebase Cloud Messaging to send notifications to your users using Novu.
diff --git a/legacy-guides/slack-guide.mdx b/legacy-guides/slack-guide.mdx
new file mode 100644
index 00000000..bf1203d0
--- /dev/null
+++ b/legacy-guides/slack-guide.mdx
@@ -0,0 +1,266 @@
+---
+title: "How to use Novu to send notifications to a Slack channel"
+description: "Learn to send Slack notifications swiftly with Novu (manually managed)"
+---
+
+# Introduction
+
+In this guide, you'll learn how to use Novu to send notifications directly to a Slack channel. But before coding anything up, we first need to go through a setup process.
+The corresponding docs for this guide are available on our [docs](https://docs.novu.co/channels-and-providers/chat/slack).
+
+
+ The entire code of this app (frontend as well as backend) can be found
+ [here](https://github.com/novuhq/slack-app).
+
+
+So let's begin!
+
+# Create a Slack App
+
+Creating a Slack app is fairly simple. Follow these steps to create your app:
+
+1. Go to [Slack's app dashboard](https://api.slack.com/apps) and click on the 'Create new App' button, as shown in the image:
+
+ {" "}
+
+2. Choose 'From Scratch' from the following dialog:
+
+ {" "}
+
+3. Choose a name for your app and select the Slack workspace in which you want to send notifications:
+
+ {" "}
+
+4. Once you're done, simply click the 'Create App' button:
+
+ {" "}
+
+5. Once done, you'll be greeted with the screen shown below. We'll make a couple of changes here and it'll be ready to go.
+
+ {" "}
+
+6. We'll need `Client Id` from the [Slack Developer's Dashboard](https://api.slack.com/apps) for configuring Slack Integration in the [Novu Web Dashboard](https://dashboard-v2.novu.coovu.coovu.co/integrations?utm_campaign=docs-guides-slack) later, so keep it handy.
+
+ {" "}
+
+
+# Create a workflow in the Novu Web Dashboard
+
+1. To create a workflow, head to the workflow section in the [Novu Web Dashboard](https://dashboard-v2.novu.co/workflows?utm_campaign=docs-guides-slack).
+2. Click on the 'Add a workflow' button and select 'Blank workflow' from the dropdown.
+
+ {" "}
+
+3. Once there, give your workflow a name and drag and drop the 'chat' option below the 'workflow trigger' step.
+
+ {" "}
+
+4. You can also add variables in the Workflow Editor. For example, here I've added 'chatMsg' as a variable as I'll be sending data using it.
+
+ {" "}
+
+ Whatever is placed inside double braces is a variable.
+5. Make sure that you've turned on the Slack integration in the [Integrations Store](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-guides-slack).
+
+ {" "}
+
+6. To turn the Slack integration on, you'll need `Client Id`. You should have it already but if you don't, you can obtain it from the [Slack Developer's Dashboard](https://api.slack.com/apps).
+
+ {" "}
+
+7. Once you have it, you need to plug it into the respective field in the [Slack Integration Settings](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-guides-slack) on the Novu Web Dashboard.
+
+ {" "}
+
+
+# Create the backend
+
+The backend for this app is quite simple. Simply install the Novu package:
+
+```bash
+npm install @novu/node
+
+```
+
+Now, create a route that you'll hit when called from the front end. We'll also need to add it to our Slack app (discussed below). For our demo app, this is the route I've created:
+
+```jsx
+import express from "express";
+import { chatController } from "../controller/chat.js";
+
+const router = express.Router();
+
+router.post("/sendChat", chatController);
+
+export default router;
+```
+
+Now, we need a controller function to handle what is to be sent in the trigger's function payload. Here's the controller I wrote:
+
+```jsx
+import { chat } from "../novu/novu.js";
+
+export const chatController = async (req, res) => {
+ const { chatMsg } = req.body;
+ try {
+ await chat(chatMsg);
+ res.status(201).json({ message: "Message sent successfully" });
+ } catch (error) {
+ res.status(500).json({ message: error.message });
+ }
+};
+```
+
+
+ Notice how we're expecting 'chatMsg' in our payload. This is why we added it
+ as a variable in the workflow created earlier.
+
+
+To make it modular, we’ll keep the trigger code in a separate function in a separate file, `novu.js`, in our case, which is as follows:
+
+```jsx
+import { Novu, ChatProviderIdEnum } from "@novu/node";
+
+export const chat = async (chatMsg) => {
+ const novu = new Novu(process.env.YOUR_NOVU_SECRET_KEY_HERE);
+ await novu.subscribers.identify(process.env.SUB_ID, {
+ firstName: "newSubForSlackChat",
+ });
+
+ await novu.trigger("slack", {
+ to: {
+ subscriberId: process.env.SUB_ID,
+ },
+ payload: {
+ chatMsg: chatMsg,
+ },
+ });
+};
+```
+
+In this code snippet above, we're first initializing a new instance of Novu, then using it to 'identify' or [create](https://docs.novu.co/subscribers/subscribers#create-a-subscriber) a [subscriber](https://docs.novu.co/subscribers/subscribers).
+
+
+ The 'identify' method tries to find a subscriber with the given info. If it
+ can't find any such subscriber, it creates a new subscriber with the supplied
+ info.
+
+After creating the subscriber, it runs the trigger code for the workflow we had created.
+You can find the trigger workflow by clicking on the 'Get Snippet' button on the
+workflow:
+
+ {" "}
+
+
+ {" "}
+
+
+# Configure the Slack app
+
+There are two ways to configure the Slack app. One is the Novu managed option and another the manually managed option.
+Since the Novu managed is fairly straightforward, I'll demonstrate the manual method here. Follow along to set it up!
+
+1. Goto 'Incoming Webhooks' in your Slack app settings and turn it on.
+
+ {" "}
+
+2. Click on the 'Add New Webhook to Workspace':
+
+ {" "}
+
+3. Now, go ahead and select the channel in which you want to send notifications and click 'allow'.
+
+ {" "}
+
+4. Then, copy the 'webhookUrl' from Slack.
+
+ {" "}
+
+5. Now, add the code below to the backend trigger functionality. Using this code, we are specifying what `webhookUrl` Novu will use to authenticate when using the Slack provider.
+
+
+
+```javaScript
+import {
+ Novu,
+ ChatProviderIdEnum
+} from '@novu/node';
+
+const novu = new Novu("");
+
+await novu.subscribers.setCredentials('subscriberId', ChatProviderIdEnum.Slack, {
+webhookUrl: "",
+});
+
+```
+
+
+```bash
+curl -L -X PUT 'https://api.novu.co/v1/subscribers//credentials' \
+-H 'Content-Type: application/json' \
+-H 'Accept: application/json' \
+-H 'Authorization: ApiKey ' \
+-d '{
+ "providerId": "slack",
+ "credentials": {
+ "webhookUrl": ""
+ },
+ "integrationIdentifier": "slack-MnGLxp8uy"
+}'
+```
+
+
+
+
+In our case, we'll add it to the `Novu.js` file we created earlier so that the final file becomes :
+
+```jsx
+import { Novu, ChatProviderIdEnum } from "@novu/node";
+
+export const chat = async (chatMsg) => {
+ const novu = new Novu(process.env.YOUR_NOVU_SECRET_KEY_HERE);
+ await novu.subscribers.identify(process.env.SUB_ID, {
+ firstName: "newSubForSlackChat",
+ });
+
+ await novu.subscribers.setCredentials(
+ process.env.SUB_ID,
+ ChatProviderIdEnum.Slack,
+ {
+ webhookUrl: process.env.SLACK_WEBHOOK_URL,
+ }
+ );
+
+ await novu.trigger("slack", {
+ to: {
+ subscriberId: process.env.SUB_ID,
+ },
+ payload: {
+ chatMsg: chatMsg,
+ },
+ });
+};
+```
+
+6. Finally, we'll add the route we'd created earlier to the redirect URL section, as shown:
+
+ {" "}
+
+
+ For demonstration purposes, I've added both the local as well as the
+ deployed URL. You only need one depending on whether you're running this
+ locally or have a deployed version.
+
+ Now, our backend is all done, our Slack is all setup and all we need to do is
+ hit the URL
+
+# Front end set up
+
+For our demonstration purposes, the front end is pretty basic. All we have to do is have an input field to take the notification text in and send the payload. The front end code for this demo app is available on our [Github repo](https://github.com/novuhq/slack-app/tree/main/frontend).
+And here's our app in all its glory!
+
+
+ {" "}
+
+
+This is how we send Slack notifications using Novu!
diff --git a/legacy-guides/usecases/centralized-multiple-legacy-implementations.mdx b/legacy-guides/usecases/centralized-multiple-legacy-implementations.mdx
new file mode 100644
index 00000000..7cb8b0ff
--- /dev/null
+++ b/legacy-guides/usecases/centralized-multiple-legacy-implementations.mdx
@@ -0,0 +1,13 @@
+---
+title: "Centralized multiple legacy implementations of different teams"
+description: "Learn to use Novu to centralize legacy notification implementations."
+---
+
+If you're coming from a medium or large team with multiple implementations of sending notifications across channels, then you can be rest assured that Novu will be the central gateway.
+
+- Install Novu's SDK (according to your preferred tech).
+- Connect your existing delivery providers to Novu.
+- [Migrate your users to subscribers](/concepts/subscribers#bulk-subscriber-creation) on Novu either via Cloud or on-prem.
+- Now, every notification fires via Novu.
+
+Teams in charge of content can always update the workflow templates via the dashboard. Engineers can always debug and add as many providers or activate as many more channels needed!
diff --git a/legacy-guides/usecases/debug-notifications.mdx b/legacy-guides/usecases/debug-notifications.mdx
new file mode 100644
index 00000000..c0b47697
--- /dev/null
+++ b/legacy-guides/usecases/debug-notifications.mdx
@@ -0,0 +1,20 @@
+---
+title: "Debug notifications across channels in a single place"
+description: "Understand the lifecycle of your notifications"
+---
+
+Debugging notifications can be a hassle. Novu allows you understand and debug notifications sent across different channels in one place.
+
+In [Novu's Activity Feed](https://dashboard-v2.novu.co/activities?utm_campaign=docs-usecases-debug), you can access a log of all the notifications that have been sent & filter through the successful & failed ones. Furthermore, you'll know why it failed!
+
+You can filter through channels, workflows, emails, subscribers and transactions.
+
+
+
+
+
+
+
+
+
+
diff --git a/legacy-guides/usecases/digest-multiple-events.mdx b/legacy-guides/usecases/digest-multiple-events.mdx
new file mode 100644
index 00000000..de20b33c
--- /dev/null
+++ b/legacy-guides/usecases/digest-multiple-events.mdx
@@ -0,0 +1,14 @@
+---
+title: "Digest multiple events in a single notification"
+description: "Learn to leverage Novu in digesting multiple events."
+---
+
+One of the recurring features developers yearn for is the ability to batch multiple notifications and send as one to subscribers.
+
+Novu ships with a built-in digest engine that handles batching and digesting multiple events in a single notification. You can programmatically adjust the digest recurrence or set it from the dashboard.
+
+
+
+
+
+[Explore more about Novu's Digest Feature](/workflow/digest)
diff --git a/legacy-guides/usecases/embedded-notification-center.mdx b/legacy-guides/usecases/embedded-notification-center.mdx
new file mode 100644
index 00000000..7220da89
--- /dev/null
+++ b/legacy-guides/usecases/embedded-notification-center.mdx
@@ -0,0 +1,16 @@
+---
+title: "Fully featured embedded notification center"
+description: "A real-time fully functional embedded notification center"
+---
+
+Novu provides a real-time embedded notification center widget that can simply be dropped into any web application.
+
+
+
+
+
+It's available as a drop-in component for React, Vue and Angular apps. For non-specific frameworks, it's also ready as a bundled web component.
+
+We care about the developer experience so much that we also built a headless, zero-UI version to empower you to hand-craft your notification center.
+
+[Explore a lot more about the Notification Center here](/inbox/overview)
diff --git a/legacy-guides/usecases/introduction.mdx b/legacy-guides/usecases/introduction.mdx
new file mode 100644
index 00000000..afd8f013
--- /dev/null
+++ b/legacy-guides/usecases/introduction.mdx
@@ -0,0 +1,55 @@
+---
+title: "Learn by usecase"
+description: "Learn Novu's Use Cases"
+---
+
+Novu is built to empower engineering teams in integrating rich product notification experiences seamlessly without re-inventing the wheel.
+
+It has been thoroughly and beautifully crafted to help individuals, teams, businesses and organizations focus on what truly matters: creating exceptional user experiences and driving engagement through smooth and swift notifications delivery.
+
+The common use cases for using Novu are highlighted below:
+
+
+
+
+
+
+
+
+
+
diff --git a/legacy-guides/usecases/manage-notifications-in-multiple-languages.mdx b/legacy-guides/usecases/manage-notifications-in-multiple-languages.mdx
new file mode 100644
index 00000000..df921294
--- /dev/null
+++ b/legacy-guides/usecases/manage-notifications-in-multiple-languages.mdx
@@ -0,0 +1,6 @@
+---
+title: "Manage notifications in multiple languages"
+description: "Learn to send notifications in different languages effortlessly"
+---
+
+This feature is currently under development. However if you urgently need this, [reach out to us for more information](https://notify.novu.co/meetings/novuhq/notifications-45min?utm_campaign=novuDocs&utm_content=notifications-in-multiple-languages-docs).
diff --git a/legacy-guides/usecases/multiple-delivery-providers.mdx b/legacy-guides/usecases/multiple-delivery-providers.mdx
new file mode 100644
index 00000000..a5f0ad1a
--- /dev/null
+++ b/legacy-guides/usecases/multiple-delivery-providers.mdx
@@ -0,0 +1,20 @@
+---
+title: "Managing multiple delivery providers in one place"
+description: "Learn to manage many delivery providers across different channels in one dashboard"
+---
+
+[Novu's dashboard](https://dashboard-v2.novu.co/integrations?utm_campaign=docs-usecases-multipledelivery) empowers you to manage all your notification delivery providers across Email, Sms, Push and Chat in one place.
+
+You can activate, deactivate, modify settings, set primary and secondary active providers as you wish from the dashboard.
+
+### Integrations Store
+
+
+
+
+
+### Add a new Delivery Provider
+
+
+
+
diff --git a/legacy-guides/usecases/send-notifications-at-any-scale.mdx b/legacy-guides/usecases/send-notifications-at-any-scale.mdx
new file mode 100644
index 00000000..2e8a6482
--- /dev/null
+++ b/legacy-guides/usecases/send-notifications-at-any-scale.mdx
@@ -0,0 +1,8 @@
+---
+title: "Processing and sending notifications at any scale"
+description: "Novu can handle notifications at extremely large scale"
+---
+
+[Novu's Cloud](https://dashboard-v2.novu.co?utm_campaign=docs-usecases-notificationsscale) is built to process and send notifications at any scale. For folks using the self-hosted Novu version, you can scale it up as your needs grow.
+
+If you're looking to use Novu on a very large scale & you need to confirm infrastructure & scalability prowess, [please reach out to us for more information](https://notify.novu.co/meetings/novuhq/notifications-45min?utm_campaign=docs-bam-usecases-notificationsscale).
diff --git a/linkedin-insights.js b/linkedin-insights.js
new file mode 100644
index 00000000..5974d4a7
--- /dev/null
+++ b/linkedin-insights.js
@@ -0,0 +1,14 @@
+(function (l) {
+ if (!l) {
+ window.lintrk = function (a, b) {
+ window.lintrk.q.push([a, b]);
+ };
+ window.lintrk.q = [];
+ }
+ var s = document.getElementsByTagName("script")[0];
+ var b = document.createElement("script");
+ b.type = "text/javascript";
+ b.async = true;
+ b.src = "https://snap.licdn.com/li.lms-analytics/insight.min.js";
+ s.parentNode.insertBefore(b, s);
+})(window.lintrk);
diff --git a/linkedin.js b/linkedin.js
new file mode 100644
index 00000000..e0a3898e
--- /dev/null
+++ b/linkedin.js
@@ -0,0 +1,3 @@
+_linkedin_partner_id = "5915650";
+window._linkedin_data_partner_ids = window._linkedin_data_partner_ids || [];
+window._linkedin_data_partner_ids.push(_linkedin_partner_id);
diff --git a/logo/dark.svg b/logo/dark.svg
index f78dd4c7..1dfd9c79 100644
--- a/logo/dark.svg
+++ b/logo/dark.svg
@@ -11,9 +11,9 @@
-
-
-
+
+
+
diff --git a/logo/light.svg b/logo/light.svg
index 2e2f9d57..b5c605b2 100644
--- a/logo/light.svg
+++ b/logo/light.svg
@@ -11,9 +11,9 @@
-
-
-
+
+
+
diff --git a/logo/logo-dark-bg.svg b/logo/logo-dark-bg.svg
index 507ead0a..eae8efee 100644
--- a/logo/logo-dark-bg.svg
+++ b/logo/logo-dark-bg.svg
@@ -1,37 +1,42 @@
diff --git a/logo/logo-light-bg.svg b/logo/logo-light-bg.svg
index 434ea1ee..4a7b5152 100644
--- a/logo/logo-light-bg.svg
+++ b/logo/logo-light-bg.svg
@@ -16,17 +16,17 @@
-
-
+
+
-
-
-
+
+
+
-
-
+
+
diff --git a/logo/novu-logo-placeholder.svg b/logo/novu-logo-placeholder.svg
new file mode 100644
index 00000000..bfd536cd
--- /dev/null
+++ b/logo/novu-logo-placeholder.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/mint.json b/mint.json
deleted file mode 100644
index f6894b1d..00000000
--- a/mint.json
+++ /dev/null
@@ -1,82 +0,0 @@
-{
- "$schema": "https://mintlify.com/schema.json",
- "name": "Novu",
- "logo": {
- "dark": "/logo/logo-dark-bg.svg",
- "light": "/logo/logo-light-bg.svg"
- },
- "favicon": "/favicon.png",
- "colors": {
- "primary": "#9563FF",
- "light": "#AE87FF",
- "dark": "#0D001D",
- "background": {
- "dark": "#090014"
- },
- "anchors": {
- "from": "#FF7F57",
- "to": "#9563FF"
- }
- },
- "topbarLinks": [
- {
- "name": "Support",
- "url": "mailto:hi@mintlify.com"
- }
- ],
- "topbarCtaButton": {
- "name": "Dashboard",
- "url": "https://dashboard.mintlify.com"
- },
- "tabs": [
- {
- "name": "API Reference",
- "url": "api-reference"
- }
- ],
- "anchors": [
- {
- "name": "Documentation",
- "icon": "book-open-cover",
- "url": "https://mintlify.com/docs"
- },
- {
- "name": "Community",
- "icon": "slack",
- "url": "https://mintlify.com/community"
- },
- {
- "name": "Blog",
- "icon": "newspaper",
- "url": "https://mintlify.com/blog"
- }
- ],
- "navigation": [
- {
- "group": "Get Started",
- "pages": ["introduction", "quickstart", "development"]
- },
- {
- "group": "Essentials",
- "pages": ["essentials/markdown", "essentials/code", "essentials/images", "essentials/settings", "essentials/navigation"]
- },
- {
- "group": "API Documentation",
- "pages": ["api-reference/authentication"]
- },
- {
- "group": "Endpoint Examples",
- "pages": [
- "api-reference/endpoint/get",
- "api-reference/endpoint/create",
- "api-reference/endpoint/update",
- "api-reference/endpoint/delete"
- ]
- }
- ],
- "footerSocials": {
- "twitter": "https://twitter.com/mintlify",
- "github": "https://github.com/mintlify",
- "linkedin": "https://www.linkedin.com/company/mintsearch"
- }
-}
diff --git a/quickstart.mdx b/quickstart.mdx
deleted file mode 100644
index d7f34867..00000000
--- a/quickstart.mdx
+++ /dev/null
@@ -1,86 +0,0 @@
----
-title: 'Quickstart'
-description: 'Start building awesome documentation in under 5 minutes'
----
-
-## Setup your development
-
-Learn how to update your docs locally and and deploy them to the public.
-
-### Edit and preview
-
-
-
- During the onboarding process, we created a repository on your Github with
- your docs content. You can find this repository on our
- [dashboard](https://dashboard.mintlify.com). To clone the repository
- locally, follow these
- [instructions](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository)
- in your terminal.
-
-
- Previewing helps you make sure your changes look as intended. We built a
- command line interface to render these changes locally. 1. Install the
- [Mintlify CLI](https://www.npmjs.com/package/mintlify) to preview the
- documentation changes locally with this command: ``` npm i -g mintlify ```
- 2. Run the following command at the root of your documentation (where
- `mint.json` is): ``` mintlify dev ```
-
-
-
-### Deploy your changes
-
-
-
-
- Our Github app automatically deploys your changes to your docs site, so you
- don't need to manage deployments yourself. You can find the link to install on
- your [dashboard](https://dashboard.mintlify.com). Once the bot has been
- successfully installed, there should be a check mark next to the commit hash
- of the repo.
-
-
- [Commit and push your changes to
- Git](https://docs.github.com/en/get-started/using-git/pushing-commits-to-a-remote-repository#about-git-push)
- for your changes to update in your docs site. If you push and don't see that
- the Github app successfully deployed your changes, you can also manually
- update your docs through our [dashboard](https://dashboard.mintlify.com).
-
-
-
-
-## Update your docs
-
-Add content directly in your files with MDX syntax and React components. You can use any of our components, or even build your own.
-
-
-
-
- Add flair to your docs with personalized branding.
-
-
-
- Implement your OpenAPI spec and enable API user interaction.
-
-
-
- Draw insights from user interactions with your documentation.
-
-
-
- Keep your docs on your own website's subdomain.
-
-
-
diff --git a/recipes/workflows/feedback-reviews.mdx b/recipes/workflows/feedback-reviews.mdx
new file mode 100644
index 00000000..a6ed0203
--- /dev/null
+++ b/recipes/workflows/feedback-reviews.mdx
@@ -0,0 +1,66 @@
+---
+title: "Feedback & Reviews"
+description: "Grow your business faster by asking users to leave a positive review"
+---
+
+## Intro
+
+Feedback and reviews are the lifeblood of any business. Setting up an automated workflow to do so however, is unnecessarily complicated by the need to implement notification logic like delayed send.
+
+[Explore the source code on GitHub](https://github.com/novuhq/examples/tree/main/novu-workflows/react/feedback-reviews)
+
+## Preview
+
+
+
+## Code Example
+
+```javascript
+import { workflow } from '@novu/framework';
+import { renderFeedbackEmail } from '../emails/feedback';
+import { zodControlSchema, zodDelayControlSchema, zodPayloadSchema } from './schemas';
+
+export const AirbnbReview = workflow(
+ "Airbnb Review",
+ async ({ step, payload }) => {
+ /**
+ * Delay Step
+ */
+ await step.delay(
+ 'delay-1-week',
+ async (controls) => {
+ return {
+ type: "regular",
+ unit: controls.unit, // 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'months'
+ amount: controls.amount, // the number of units to delay workflow execution for
+ };
+ },
+ {
+ controlSchema: zodDelayControlSchema
+ }
+ );
+
+ /**
+ * Email Step
+ */
+ await step.email(
+ "send-email",
+ async (controls) => {
+ return {
+ subject: controls.emailSubject,
+ body: renderFeedbackEmail(controls, payload),
+ };
+ },
+ {
+ controlSchema: zodControlSchema
+ }
+ );
+ },
+ {
+ payloadSchema: zodPayloadSchema
+ },
+);
+```
\ No newline at end of file
diff --git a/recipes/workflows/introduction.mdx b/recipes/workflows/introduction.mdx
new file mode 100644
index 00000000..a7f3581f
--- /dev/null
+++ b/recipes/workflows/introduction.mdx
@@ -0,0 +1,35 @@
+---
+title: "Common Notification Workflow Examples"
+sidebarTitle: "Overview"
+description: "You shouldn't have to reinvent the wheel. Here we share how to implement several real-world use cases of notifications workflows."
+---
+
+## Workflow examples
+
+This directory consists of drop-in working projects of different types of notification workflows you can use.
+
+Each directory is a batteries-included Novu Nextjs app that you can simply clone, install and run!
+
+
+
+
+ Send One-Time Passwords (OTP) without worrying about email reputation scores.
+
+
+ Let users "Reset Password" or "Forgot Username".
+
+
+ Suspicious activity? Inform your users of recent login attempts.
+
+
+ Send invoices or receipts after a transaction is processed.
+
+
+ Let buyers know when their order has shipped, complete with tracking number.
+
+
+ Reviews are the lifeblood of any business. Remind and incentivize your buyers to leave a review.
+
+
+
+
diff --git a/recipes/workflows/invoice-receipt.mdx b/recipes/workflows/invoice-receipt.mdx
new file mode 100644
index 00000000..bef0a612
--- /dev/null
+++ b/recipes/workflows/invoice-receipt.mdx
@@ -0,0 +1,50 @@
+---
+title: "Invoice Receipt"
+description: "Send invoices or receipts immediately after a user purchases."
+---
+
+## Introduction
+
+Invoices and receipts require custom fields such as the order number, billing details, and objects purchased which must be dynamically fetched from the database. The use of step controls and the payload schema simplifies the fetching of that unique data.
+
+[Explore the source code on GitHub](https://github.com/novuhq/examples/tree/main/novu-workflows/react/invoice-receipts)
+
+## Preview
+
+
+
+
+## Code example
+
+```javascript
+import { workflow } from '@novu/framework';
+import { renderAppleReceiptEmail } from '../emails/apple-receipt';
+import { zodControlSchema, jsonSchema, zodPayloadSchema } from './schemas';
+
+/**
+ * Apple Receipt workflow
+ */
+export const appleReceipt = workflow(
+ "Apple Receipt",
+ async ({ step, payload }) => {
+ await step.email(
+ "send-email",
+ async (controls) => {
+ return {
+ subject: controls.receiptSubject,
+ body: renderAppleReceiptEmail(controls, payload),
+ };
+ },
+ {
+ controlSchema: zodControlSchema
+ },
+ );
+ },
+ {
+ payloadSchema: zodPayloadSchema
+ }
+);
+```
\ No newline at end of file
diff --git a/recipes/workflows/multi-workflow-digest.mdx b/recipes/workflows/multi-workflow-digest.mdx
new file mode 100644
index 00000000..a3fd6a96
--- /dev/null
+++ b/recipes/workflows/multi-workflow-digest.mdx
@@ -0,0 +1,124 @@
+---
+title: "Multi Workflow Digest Summary"
+sidebarTitle: "Multi-workflow digest"
+description: "Send a digest notification email by batching different types of notifications from multiple workflows"
+---
+
+## Introduction
+
+This example demonstrates how to send a digest notification email by batching different types of notifications from multiple workflows using the Novu Framework. The digest notification email is sent to the user with a summary of all the notifications that have been triggered by different workflows.
+
+**Use case**: Linear is a project management tool that allows users to create and manage projects and tickets. When someone comments on the ticket, someone assigns the ticket to the user or there can be more events for which the user has turned on the Notification, the user receives a notification. The user can receive multiple notifications in a short period. To avoid spamming the user with multiple emails, Linear sends a digest notification email that contains a summary of all the notifications.
+
+Source Code on GitHub: [Multi-workflow digest](https://github.com/novuhq/examples/tree/main/novu-workflows/react/multi-workflow-digest)
+
+Sync the changes from local studio before triggering the workflows using backend SDKs or API. Read more about [sync](framework/deployment/syncing).
+
+
+
+
+## Code Example
+
+```typescript Summary worklflow to digest all notifications from different workflows
+import { CronExpression, workflow } from "@novu/framework";
+import { renderLinearDigestEmail } from "../../emails/linear-digest";
+import {
+ ticketAssignedPayloadSchema,
+ commentOnTicketPayloadSchema,
+ summaryWorkflowPayloadSchema,
+} from "./schemas";
+
+export const summaryWorkflow = workflow(
+ "multi-workflow-digest",
+ async ({ step }) => {
+ // Digest all notifications from different workflows
+ const digestedNotifications = await step.digest(
+ "digest-all-notifications",
+ async () => {
+ return {
+ cron: CronExpression.EVERY_DAY_AT_8AM,
+ };
+ }
+ );
+
+ // Send digested notifications via email
+ await step.email("send-email", async () => {
+ return {
+ subject: `${digestedNotifications.events.length} unread notifications on Novu - Linear`,
+ body: renderLinearDigestEmail(digestedNotifications.events),
+ };
+ });
+ },
+ {
+ payloadSchema: summaryWorkflowPayloadSchema,
+ }
+);
+```
+
+```typescript Workflow to send notification when someone comments on a ticket
+export const someoneCommentedOnTicket = workflow(
+ "someone-commented-on-ticket",
+ async ({ step, payload, subscriber }) => {
+ await step.inApp("send-inbox-notification", async () => {
+ return {
+ body: `${payload.comment.author.userName} commented ${payload.comment.text} on ticket ${payload.ticket.id}`,
+ };
+ });
+
+ await step.custom("digest-the-message", async () => {
+ try {
+ await summaryWorkflow.trigger({
+ to: subscriber?.subscriberId as string,
+ payload: payload,
+ });
+ return {
+ success: true,
+ error: null,
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error: error,
+ };
+ }
+ });
+ },
+ {
+ payloadSchema: commentOnTicketPayloadSchema,
+ }
+);
+```
+
+```typescript Workflow to send notification when a ticket is assigned to the user
+export const ticketAssigned = workflow(
+ "ticket-assigned-to-user",
+ async ({ step, payload, subscriber }) => {
+ await step.inApp("send-inbox-notification", async () => {
+ return {
+ body: `${payload.assign.author.userName} assigned ticket ${payload.ticket.id} ${payload.ticket.title} to you.`,
+ };
+ });
+
+ await step.custom("digest-the-message", async () => {
+ try {
+ await summaryWorkflow.trigger({
+ to: subscriber?.subscriberId as string,
+ payload: payload,
+ });
+ return {
+ success: true,
+ error: null,
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error: error,
+ };
+ }
+ });
+ },
+ {
+ payloadSchema: ticketAssignedPayloadSchema,
+ }
+);
+```
\ No newline at end of file
diff --git a/recipes/workflows/otp.mdx b/recipes/workflows/otp.mdx
new file mode 100644
index 00000000..1058251c
--- /dev/null
+++ b/recipes/workflows/otp.mdx
@@ -0,0 +1,81 @@
+---
+title: "OTP (One-Time Password)"
+description: "Send OTPs without setting up your own infrastructure"
+---
+
+## Intro
+
+Sending One-Time Passwords (OTP) is often unnecessarily complicated, requiring engineering teams to waste valuable development cycles setting up SMS infrastructure and getting email reputation satisfactory for sending six digit passcodes. This three channel workflow example shows how to send an email, push notification, or SMS OTP.
+
+[Explore the source code on GitHub](https://github.com/novuhq/examples/tree/main/novu-workflows/react/otp)
+
+## Preview
+
+Send an email
+
+
+Send a mobile push notification
+
+
+Send an SMS notification
+
+
+
+## Code Sample
+
+```javascript
+import { workflow } from '@novu/framework';
+import { renderOtpEmail } from '../emails/slack-otp';
+import { zodControlSchema, zodPayloadSchema, zodPushControlSchema, zodSmsControlSchema } from './schemas';
+
+export const SlackVerificationOTP = workflow(
+ "Slack Verify OTP",
+ async ({ step, payload }) => {
+ await step.email(
+ "send-email",
+ async (controls) => {
+ return {
+ subject: controls.emailSubject,
+ body: renderOtpEmail(controls, payload),
+ };
+ },
+ {
+ controlSchema: zodControlSchema
+ });
+
+ // -----------------------------------push flow-------------------------------------------------------------------------
+ await step.push('send-push', async (controls) => {
+ return {
+ subject: controls.pushNotificationSubject,
+ body: `Your verification code from Slack is ${payload.validationCode}`,
+ };
+ },
+ {
+ controlSchema: zodPushControlSchema
+ });
+
+ // -----------------------------------sms flow-------------------------------------------------------------------------
+ await step.sms('send-sms', async (controls) => {
+ return {
+ subject: controls.smsSubject,
+ body: `Your verification code from Slack is ${payload.validationCode}`,
+ };
+ },
+ {
+ controlSchema: zodSmsControlSchema
+ });
+ },
+ {
+ payloadSchema: zodPayloadSchema
+ },
+);
+```
diff --git a/recipes/workflows/password-reset.mdx b/recipes/workflows/password-reset.mdx
new file mode 100644
index 00000000..53edd773
--- /dev/null
+++ b/recipes/workflows/password-reset.mdx
@@ -0,0 +1,48 @@
+---
+title: "Password Reset"
+description: "Help users reclaim access to their account via the 'Password Reset' or 'Forgot Username' functions"
+---
+
+## Intro
+
+To help users re-establish access to their account securely, a password reset email should be succint and have a unique password reset URL that expires after a given time. This example uses the payload schema to fetch the unique expiring password reset link.
+
+[Explore the source code on GitHub](https://github.com/novuhq/examples/tree/main/novu-workflows/react/password-reset)
+
+## Preview
+
+
+
+
+## Code Example
+
+```javascript
+import { workflow } from '@novu/framework';
+import { renderPasswordResetEmail } from '../emails/password-reset';
+import { zodControlSchema, zodPayloadSchema } from './schemas';
+
+export const DropboxPasswordResetEmail = workflow(
+ "Dropbox Password Reset",
+ async ({ step, payload }) => {
+ await step.email(
+ "send-email",
+ async (controls) => {
+ return {
+ subject: controls.emailSubject,
+ body: renderPasswordResetEmail(controls, payload),
+ };
+ },
+ {
+ controlSchema: zodControlSchema
+ }
+ );
+ },
+ {
+ payloadSchema: zodPayloadSchema
+ }
+);
+```
+
diff --git a/recipes/workflows/recent-login.mdx b/recipes/workflows/recent-login.mdx
new file mode 100644
index 00000000..fd245c08
--- /dev/null
+++ b/recipes/workflows/recent-login.mdx
@@ -0,0 +1,66 @@
+---
+title: "Recent Login"
+description: "Notify users of recent login activity"
+---
+
+## Intro
+
+Sending recent logins is a great way to inform users of possible unusal activity and verify their usage. This workflow example leverages the payload schema to display the login IP, and notifies users through both email and a push notification.
+
+[Explore the source code on GitHub](https://github.com/novuhq/examples/tree/main/novu-workflows/react/recent-login)
+
+## Preview
+
+
+
+
+## Code Example
+
+```javascript
+import { workflow } from '@novu/framework';
+import { renderLoginEmail } from '../emails/yelp-recent-login';
+import { zodControlSchema, zodPayloadSchema, zodPushControlSchema } from './schemas';
+
+export const YelpRecentLogin = workflow(
+ "Yelp Recent Login",
+ async ({ step, payload }) => {
+ /**
+ * Email Flow
+ */
+ await step.email(
+ "send-email",
+ async (controls) => {
+ return {
+ subject: "Recent login to your Account",
+ body: renderLoginEmail(controls, payload),
+ };
+ },
+ {
+ controlSchema: zodControlSchema
+ },
+ );
+
+ /**
+ * Push Flow
+ */
+ await step.push(
+ "send-push",
+ async (controls) => {
+ return {
+ subject: controls.pushNotificationSubject,
+ body: `Hi ${payload.userFirstName}, we noticed a recent login to your Yelp account. If this was you, there's nothing else you need to do. If this wasn't you or please see our support page.`,
+ };
+ },
+ {
+ controlSchema: zodPushControlSchema
+ }
+ );
+ },
+ {
+ payloadSchema: zodPayloadSchema
+ },
+);
+```
diff --git a/recipes/workflows/scaffold.mdx b/recipes/workflows/scaffold.mdx
new file mode 100644
index 00000000..ee0b67e2
--- /dev/null
+++ b/recipes/workflows/scaffold.mdx
@@ -0,0 +1,124 @@
+---
+title: ""
+description: ""
+---
+
+## Intro
+
+
+[Explore the source code on GitHub](https://github.com/novuhq/examples/tree/main/novu-workflows/react/otp)
+
+## Preview
+
+
+
+
+
+## Code Example
+
+```javascript
+import { Client, workflow } from "@novu/framework";
+import { renderOtpEmail } from "./emails/slack-otp";
+
+
+export const client = new Client({
+ /**
+ * Enable this flag only during local development
+ */
+ strictAuthentication: false,
+});
+
+export const SlackVerificationOTP = workflow(
+ "Slack Verify OTP",
+ async ({ step, payload }) => {
+ await step.email(
+ "send-email",
+ async (inputs) => {
+ return {
+ subject: inputs.emailSubject,
+ body: renderOtpEmail(inputs, payload),
+ };
+ },
+ {
+ inputSchema: {
+ type: "object",
+ properties: {
+ emailSubject: {
+ title: "Email Subject",
+ type: "string",
+ default: "Verify your Slack OTP!"
+ },
+ confirmAddressHeader: {
+ type: "string",
+ default: "Confirm your email address",
+ title: "Confirm Address Header"
+ },
+ majorBodyText: {
+ type: "string",
+ default: "Your confirmation code is below - enter it in your open browser window and we'll help you get signed in.",
+ title: "Major Body Text"
+ },
+ showMagicLink: {
+ type: "boolean",
+ default: false,
+ title: "Show Magic Link"
+ },
+ linkText: {
+ type: 'string',
+ default: 'Click this link if the OTP does not work for you!',
+ title: 'Magic link Text'
+ },
+ showOTP: {
+ type: "boolean",
+ default: true,
+ title: "Show OTP"
+ },
+ },
+ },
+ });
+
+ // -----------------------------------push flow-------------------------------------------------------------------------
+ await step.push('send-push', async () => {
+ return {
+ subject: 'You received a Slack OTP',
+ body: `Your verification code from Slack is ${payload.validationCode}`,
+ };
+ });
+
+ // -----------------------------------sms flow-------------------------------------------------------------------------
+ await step.sms('send-sms', async () => {
+ return {
+ subject: 'You received a Slack OTP',
+ body: `Your verification code from Slack is ${payload.validationCode}`,
+ };
+ });
+ },
+ {
+ payloadSchema: {
+ type: "object",
+ properties: {
+ validationCode: {
+ type: "string",
+ default: "123456",
+ title: "OTP",
+ },
+ magicLinkURL: {
+ type: "string",
+ default: "https://slack.com/magic/link",
+ title: "Magic Link URL"
+ }
+ },
+ },
+ },
+);
+
+```
+
+## Installation
+
+```
+npm run dev
+```
diff --git a/recipes/workflows/shipping-confirmation.mdx b/recipes/workflows/shipping-confirmation.mdx
new file mode 100644
index 00000000..be8ab583
--- /dev/null
+++ b/recipes/workflows/shipping-confirmation.mdx
@@ -0,0 +1,51 @@
+---
+title: "Shipping Confirmation"
+description: "Let buyers know their order is on the way!"
+---
+
+## Intro
+
+What delights the dopamine receptors more than an email confirming their order's delivery date? Reward your buyers by letting them know their order is confirmed and on the way, complete with tracking info fetched by leveraging the payload schema.
+
+[Explore the source code on GitHub](https://github.com/novuhq/examples/tree/main/novu-workflows/react/shipping-order-confirmation)
+
+## Preview
+
+
+
+
+## Code Example
+
+```javascript
+import { workflow } from '@novu/framework';
+import { renderShippingOrderEmail } from '../emails/shipping-order-confirmation';
+import { zodControlSchema, zodPayloadSchema, jsonControlSchema, jsonPayloadSchema } from './schemas';
+
+export const AmazonShippingOrderConfirmation = workflow(
+ "Amazon Shipping Order",
+ async ({ step, payload }) => {
+ await step.email(
+ "send-email",
+ async (controls) => {
+ return {
+ subject: controls.emailSubject,
+ body: renderShippingOrderEmail(controls, payload),
+ };
+ },
+ {
+ controlSchema: zodControlSchema
+ }
+ );
+ },
+ {
+ payloadSchema: zodPayloadSchema
+ },
+);
+```
+
+
+ PROTIP: Delight users by sending their tracking info - leverage the payload schema to do this.
+
diff --git a/recipes/workflows/translations.mdx b/recipes/workflows/translations.mdx
new file mode 100644
index 00000000..078a00f8
--- /dev/null
+++ b/recipes/workflows/translations.mdx
@@ -0,0 +1,63 @@
+---
+title: 'Translations'
+sidebarTitle: 'Translations'
+description: 'Translate notification content to different languages using i18n.'
+---
+
+Translations allow you to seamlessly adapt your notification workflows to different languages and automatically apply them based on your users locale. Translations enhance engagement, personalize the user experience, and enables you to expand your market reach.
+
+**Use case:** Facebook is a social media platform with users from all over the world. Facebook allows the user to use Facebook in their language and expect to get emails from Facebook in their selected language.
+
+The following example uses [i18n](https://www.npmjs.com/package/i18n) in a simple text email, but if you use React to create your emails you can use [react-i18next](https://react.i18next.com/) and pass the variables needed for it. Before using i18n to translate, the subscriber's locale is used to set to the language that this email should be translated to.
+
+**Note:** Translations in `@novu/framework` are entirely separate from translations in [v0.x](https://v0.x-docs.novu.co/content-creation-design/translations).
+
+```typescript
+import { workflow } from '@novu/framework';
+import i18next from 'i18next';
+import en from './locales/en.json';
+import de from './locales/de.json';
+
+i18next.init({
+ fallbackLng: 'en',
+ resources: {
+ en: {
+ translation: en,
+ },
+ de: {
+ translation: de,
+ },
+ },
+});
+
+export const welcomeWorkflow = workflow(
+ 'welcome-workflow',
+ async ({ step, subscriber }) => {
+ // Send welcome notifications via email
+ await step.email('welcome-email', async () => {
+ const t = i18next.getFixedT(subscriber?.locale as string);
+
+ return {
+ subject: t('welcomeEmailSubject', { firstName: subscriber?.firstName }),
+ body: t('welcomeEmailBody', { username: subscriber?.data?.username }),
+ };
+ });
+ }
+);
+```
+
+```typescript locales/en.json
+{
+ "welcomeEmailSubject": "Welcome {{firstName}} to Facebook",
+ "welcomeEmailBody": "Welcome to Facebook, you can now edit your profile {{username}} and connect with your friends and family",
+}
+```
+
+```typescript locales/de.json
+{
+ "welcomeEmailSubject": "Willkommen {{firstName}} auf Facebook",
+ "welcomeEmailBody": "Willkommen bei Facebook. Sie können jetzt Ihr Profil {{username}} bearbeiten und mit Ihren Freunden und Ihrer Familie in Kontakt treten",
+}
+```
+
+More advanced example on GitHub: [translations](https://github.com/novuhq/examples/tree/main/novu-workflows/react/translation)
\ No newline at end of file
diff --git a/sdks/angular.mdx b/sdks/angular.mdx
new file mode 100644
index 00000000..5c972aed
--- /dev/null
+++ b/sdks/angular.mdx
@@ -0,0 +1,13 @@
+---
+title: "Angular Component Library"
+description: "Novu’s Angular library provides an Angular component wrapper over the Notification Center Web that you can use to integrate the notification center into your Angular application. "
+icon: "angular"
+---
+
+[Explore the source code on GitHub](https://github.com/novuhq/novu/tree/next/packages/notification-center-angular)
+
+## Installation
+
+```javascript
+npm install @novu/notification-center-angular
+```
\ No newline at end of file
diff --git a/sdks/client/headless/api-reference.mdx b/sdks/client/headless/api-reference.mdx
new file mode 100644
index 00000000..20dc1f49
--- /dev/null
+++ b/sdks/client/headless/api-reference.mdx
@@ -0,0 +1,5 @@
+---
+title: "Headless SDK Reference"
+icon: "js"
+sidebarTitle: "Headless"
+---
diff --git a/sdks/client/react-native/api-reference.mdx b/sdks/client/react-native/api-reference.mdx
new file mode 100644
index 00000000..ee68d550
--- /dev/null
+++ b/sdks/client/react-native/api-reference.mdx
@@ -0,0 +1,5 @@
+---
+title: "React Native SDK Reference"
+icon: "mobile"
+sidebarTitle: "React Native"
+---
diff --git a/sdks/client/react/api-reference.mdx b/sdks/client/react/api-reference.mdx
new file mode 100644
index 00000000..272a77f8
--- /dev/null
+++ b/sdks/client/react/api-reference.mdx
@@ -0,0 +1,5 @@
+---
+title: "React SDK Reference"
+icon: "react"
+sidebarTitle: "React"
+---
diff --git a/sdks/dotnet.mdx b/sdks/dotnet.mdx
new file mode 100644
index 00000000..ff0bbdc5
--- /dev/null
+++ b/sdks/dotnet.mdx
@@ -0,0 +1,70 @@
+---
+title: "C#/.NET"
+description: "Connect a .NET application to Novu"
+icon: "microsoft"
+---
+
+
+ This SDK is built and maintained by our community and might not be up to date
+ with the latest Novu features. We are currently working on a new official SDK
+ for C#.
+
+
+Novu's .NET SDK provides simple, yet comprehensive notification management, and delivery capabilities through multiple channels that you can implement using code that integrates seamlessly with your C#/.NET application.
+
+[Explore the source code on GitHub](https://github.com/novuhq/novu-dotnet)
+
+## Installation
+
+```dotnet
+dotnet add package Novu
+```
+
+## Usage
+
+```dotnet
+using Novu.DTO;
+using Novu.Models;
+using Novu;
+...
+
+var novuConfiguration = new NovuClientConfiguration
+{
+ // Defaults to https://api.novu.co/v1
+ Url = "https://novu-api.my-domain.com/v1",
+ ApiKey = "12345",
+};
+
+var novu = new NovuClient(novuConfiguration);
+
+// OnboardEventPayload.cs
+public class OnboardEventPayload
+{
+ [JsonProperty("username")]
+ public string Username { get; set; }
+
+ [JsonProperty("welcomeMessage")]
+ public string WelcomeMessage { get; set; }
+}
+
+// MyFile.cs
+var onboardingMessage = new OnboardEventPayload
+{
+ Username = "jdoe",
+ WelcomeMessage = "Welcome to novu-dotnet"
+};
+
+var payload = new EventTriggerDataDto()
+{
+ EventName = "onboarding",
+ To = { SubscriberId = "subscriberId" },
+ Payload = onboardingMessage
+};
+
+var trigger = await novu.Event.Trigger(payload);
+
+if (trigger.TriggerResponsePayloadDto.Acknowledged)
+{
+ Console.WriteLine("Trigger has been created.");
+}
+```
diff --git a/sdks/go.mdx b/sdks/go.mdx
new file mode 100644
index 00000000..1d05dfd9
--- /dev/null
+++ b/sdks/go.mdx
@@ -0,0 +1,121 @@
+---
+title: "Go SDK"
+description: "Connect a Go application to Novu"
+icon: "golang"
+---
+
+Novu's Go SDK provides simple, yet comprehensive notification management, and delivery capabilities through multiple channels that you can implement using code that integrates seamlessly with your Go application.
+
+[Explore the source code on GitHub](https://github.com/novuhq/novu-go)
+
+## Installation
+
+```go
+go get github.com/novuhq/novu-go
+```
+
+## Usage
+
+
+```go
+package main
+
+import (
+ "context"
+ novugo "github.com/novuhq/novu-go"
+ "github.com/novuhq/novu-go/models/components"
+ "log"
+ "os"
+)
+
+func main() {
+ ctx := context.Background()
+
+ s := novugo.New(
+ novugo.WithSecurity(os.Getenv("NOVU_SECRET_KEY")),
+ )
+
+ res, err := s.Trigger(ctx, components.TriggerEventRequestDto{
+ WorkflowID: "workflow_identifier",
+ Payload: map[string]any{
+ "comment_id": "string",
+ "post": map[string]any{
+ "text": "string",
+ },
+ },
+ Overrides: map[string]map[string]any{
+ "email": map[string]any{
+ "bcc": "no-reply@novu.co",
+ },
+ },
+ To: components.CreateToSubscriberPayloadDto(
+ components.SubscriberPayloadDto{
+ SubscriberID: "subscriber_uniuqe_identifier",
+ FirstName: "Albert",
+ LastName: "Einstein",
+ Email: "albert@einstein.com",
+ },
+ ),
+ }, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if res.TriggerEventResponseDto != nil {
+ // handle response
+ }
+}
+```
+
+
+```go
+package main
+
+import (
+ "context"
+ novugo "github.com/novuhq/novu-go"
+ "github.com/novuhq/novu-go/models/components"
+ "log"
+ "os"
+)
+
+func main() {
+ ctx := context.Background()
+
+ s := novugo.New(
+ novugo.WithServerURL("https://eu.api.novu.co"),
+ novugo.WithSecurity(os.Getenv("NOVU_SECRET_KEY")),
+ )
+
+ res, err := s.Trigger(ctx, components.TriggerEventRequestDto{
+ WorkflowID: "workflow_identifier",
+ Payload: map[string]any{
+ "comment_id": "string",
+ "post": map[string]any{
+ "text": "string",
+ },
+ },
+ Overrides: map[string]map[string]any{
+ "email": map[string]any{
+ "bcc": "no-reply@novu.co",
+ },
+ },
+ To: components.CreateToSubscriberPayloadDto(
+ components.SubscriberPayloadDto{
+ SubscriberID: "subscriber_uniuqe_identifier",
+ FirstName: "Albert",
+ LastName: "Einstein",
+ Email: "albert@einstein.com",
+ },
+ ),
+ }, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if res.TriggerEventResponseDto != nil {
+ // handle response
+ }
+}
+```
+
+
+
diff --git a/sdks/introduction.mdx b/sdks/introduction.mdx
new file mode 100644
index 00000000..affc852d
--- /dev/null
+++ b/sdks/introduction.mdx
@@ -0,0 +1,117 @@
+---
+title: "SDKs & Client Libraries"
+sidebarTitle: "Overview"
+description: "You can interface with Novu's API either over REST, or via libraries for certain languages. Below is a list of our official, supported libraries."
+---
+
+
+ It's important to note that our API and backend SDK are intended for use
+ exclusively in server-side applications. **Attempting to use them in a
+ client-side application will result in Cross-Origin Resource Sharing (CORS)
+ errors.** This restriction ensures the security and integrity of our services.
+
+
+## Client Side
+
+The Client side libraries and components helps to add a fully functional notification center to your application.
+
+
+
+ Embed a real-time notification center component in your React application.
+
+
+ Embed a real-time notification center component in your Angular application.
+
+
+ Embed a notification system into any framework or vanilla JavaScript
+ project, without being constrained by our default UI or dependencies.
+
+
+
+## Server Side
+
+The Server side libraries helps to trigger notifications and interact with all of Novu’s API seamlessly.
+
+
+
+ } color="#ea5a0c" href="/sdks/nodejs">
+ Connect your Node app to Novu via the Node.js SDK.
+
+
+ } color="#16a34a" href="/sdks/php">
+ Connect your PHP app to Novu via the PHP SDK.
+
+
+ Connect your Laravel app to Novu via the Laravel SDK.
+
+
+ } color="#dc2626" href="/sdks/python">
+ Connect your Python app to Novu via the Python SDK.
+
+
+
+
+
+
+
+
+
+
+ }
+ color="#dc2626"
+ href="/sdks/kotlin"
+ >
+ Connect your Kotlin app to Novu via the Kotlin SDK.
+
+
+ } color="#dc2626" href="/sdks/java">
+ Connect your Java app to Novu via the Java SDK.
+
+
+
+ }
+
+ color="#dc2626"
+ href="/sdks/ruby"
+
+>
+
+ Connect your Ruby app to Novu via the Ruby SDK.
+
+
+
+ } color="#0285c7" href="/sdks/go">
+ Connect your Golang app to Novu via the Go SDK.
+
+
+ } color="#dc2626" href="/sdks/dotnet">
+ Connect your C#/.NET app to Novu via the .NET SDK.
+
+
diff --git a/sdks/java.mdx b/sdks/java.mdx
new file mode 100644
index 00000000..d9571bf4
--- /dev/null
+++ b/sdks/java.mdx
@@ -0,0 +1,60 @@
+---
+title: "Java SDK"
+description: "Connect a Java application to Novu"
+icon: "java"
+---
+
+
+ This SDK is built and maintained by our community and might not be up to date with the latest Novu features. We are currently working on a new official SDK for Java.
+
+
+
+Novu's Java SDK provides simple, yet comprehensive notification management, and delivery capabilities through multiple channels that you can implement using code that integrates seamlessly with your Java application.
+
+[Explore the source code on GitHub.](https://github.com/novuhq/novu-java)
+
+## Installation
+
+Maven users:
+
+```java
+
+
+ co.novu
+ novu-java
+ {use-latest-version}
+
+```
+
+The latest version can be found [on GitHub.](https://github.com/novuhq/novu-java#installation)
+
+Gradle users:
+
+```groovy
+implementation 'co.novu:novu-java:{latest-version}'
+```
+
+Sync your project, and you should have the artifacts downloaded.
+
+## Usage
+
+```java
+import co.novu.sdk.Novu;
+
+public class Main {
+ public static void main(String[] args) {
+ String apiKey = "INSERT_API_KEY_HERE";
+ // Using the API Key only
+ Novu novu = new Novu(apiKey);
+
+ // Using the Config Param
+ NovuConfig novuConfig = new NovuConfig(apiKey);
+ Novu novu = new Novu(novuConfig);
+
+ // Sample usage
+ novu.triggerEvent(event);
+ }
+}
+
+// Sign up on https://dashboard-v2.novu.co and grab your API key from https://dashboard-v2.novu.co/settings
+```
diff --git a/sdks/kotlin.mdx b/sdks/kotlin.mdx
new file mode 100644
index 00000000..2bad7bf3
--- /dev/null
+++ b/sdks/kotlin.mdx
@@ -0,0 +1,54 @@
+---
+title: "Kotlin SDK"
+description: "Connect a Kotlin application to Novu"
+icon: "android"
+---
+
+
+ This SDK is built and maintained by our community and might not be up to date with the latest Novu features.
+
+
+
+Novu's Kotlin SDK provides simple, yet comprehensive notification management, and delivery capabilities through multiple channels that you can implement using code that integrates seamlessly with your Kotlin application.
+
+[Explore the source code on GitHub](https://github.com/novuhq/novu-kotlin)
+
+## Installation
+
+Maven users:
+
+```xml
+
+ co.novu
+ novu-kotlin
+ {use-latest-version}
+
+```
+
+The latest version can be found [on GitHub.](https://github.com/novuhq/novu-kotlin#installation)
+
+Gradle users:
+
+```groovy
+implementation 'co.novu:novu-kotlin:{use-latest-version}' //Groovy
+
+implementation ("co.novu:novu-kotlin:{use-latest-version}") //Kotlin
+```
+
+Sync your project, and you should have the artifacts downloaded.
+
+## Usage
+
+```kotlin
+
+// without changing the backend URL
+import co.novu.Novu
+import co.novu.extentions.environments
+
+fun main() {
+ val novu = Novu(apiKey = "NOVU_SECRET_KEY")
+ val environment = novu.environments()
+ println(environment)
+}
+
+```
diff --git a/sdks/laravel.mdx b/sdks/laravel.mdx
new file mode 100644
index 00000000..e2aec65b
--- /dev/null
+++ b/sdks/laravel.mdx
@@ -0,0 +1,50 @@
+---
+title: "Laravel SDK"
+description: "Connect a Laravel application to Novu"
+icon: "laravel"
+---
+
+
+
+ This SDK is built and maintained by our community and might not be up to date with the latest Novu features.
+
+
+Novu's Laravel SDK provides simple, yet comprehensive notification management, and delivery capabilities through multiple channels that you can implement using code that integrates seamlessly with your Laravel application.
+
+[Explore the source code on GitHub](https://github.com/novuhq/novu-laravel)
+
+## Installation
+
+```php
+composer require novu/novu-laravel
+```
+
+
+ If you're ready to start integrating in your Laravel app, jump straight to
+ our [Laravel quickstart.]()
+
+
+## Usage
+
+```php
+use Novu\Laravel\Facades\Novu;
+
+$response = Novu::triggerEvent([
+ 'name' => '',
+ 'payload' => ['customVariables' => 'Hello'],
+ 'to' => [
+ 'subscriberId' => '',
+ 'phone' => '07983887777'
+ ]
+])->toArray();
+
+// or you can use the novu() helper function like so:
+novu()->triggerEvent([
+ 'name' => '',
+ 'payload' => ['customVariables' => 'Hello'],
+ 'to' => [
+ 'subscriberId' => '',
+ 'phone' => '07983887777'
+ ]
+])->toArray();
+```
diff --git a/sdks/nodejs.mdx b/sdks/nodejs.mdx
new file mode 100644
index 00000000..74f37fe4
--- /dev/null
+++ b/sdks/nodejs.mdx
@@ -0,0 +1,119 @@
+---
+title: "NodeJS SDK"
+description: "Connect a Node.js application to Novu"
+icon: "node"
+---
+
+Novu's Node.js SDK provides simple, yet comprehensive notification management, and delivery capabilities through multiple channels that you can implement using code that integrates seamlessly with your Node.js application.
+
+[Explore the source code on GitHub](https://github.com/novuhq/novu-ts)
+
+## Installation
+
+
+
+```bash
+npm add @novu/api
+```
+
+
+```bash
+pnpm add @novu/api
+```
+
+
+```bash
+yarn add @novu/api zod
+
+# Note that Yarn does not install peer dependencies automatically. You will need
+# to install zod as shown above.
+```
+
+
+```bash
+bun add @novu/api
+```
+
+
+
+## Usage
+
+
+
+```javascript
+import { Novu } from "@novu/api";
+
+const novu = new Novu({
+ secretKey: "",
+});
+
+async function run() {
+ const result = await novu.trigger({
+ to: {
+ "subscriberId": "subscriber_unique_identifier",
+ "firstName": "Albert",
+ "lastName": "Einstein",
+ "email": "albert@einstein.com",
+ "phone": "+1234567890",
+ },
+ workflowId: "workflow_identifier",
+ payload: {
+ "comment_id": "string",
+ "post": {
+ "text": "string",
+ },
+ },
+ overrides: {
+ "email": {
+ "bcc": "no-reply@novu.co"
+ },
+ },
+ });
+
+ // Handle the result
+ console.log(result);
+}
+
+run();
+```
+
+
+```javascript
+import { Novu } from "@novu/api";
+
+const novu = new Novu({
+ secretKey: "",
+ serverUrl: "https://eu.api.novu.co",
+});
+
+async function run() {
+ const result = await novu.trigger({
+ to: {
+ "subscriberId": "subscriber_unique_identifier",
+ "firstName": "Albert",
+ "lastName": "Einstein",
+ "email": "albert@einstein.com",
+ "phone": "+1234567890",
+ },
+ workflowId: "workflow_identifier",
+ payload: {
+ "comment_id": "string",
+ "post": {
+ "text": "string",
+ },
+ },
+ overrides: {
+ "email": {
+ "bcc": "no-reply@novu.co"
+ },
+ },
+ });
+
+ // Handle the result
+ console.log(result);
+}
+
+run();
+```
+
+
diff --git a/sdks/overview.mdx b/sdks/overview.mdx
new file mode 100644
index 00000000..e732f29b
--- /dev/null
+++ b/sdks/overview.mdx
@@ -0,0 +1,59 @@
+---
+title: "Novu SDKs and Libraries"
+sidebarTitle: "Overview"
+description: "Explore Novu's comprehensive collection of server-side and client-side SDKs for seamless notification integration across multiple programming languages and frameworks."
+---
+
+## Server-side SDKs
+
+### API SDKs
+
+Novu's server-side SDKs simplify the integration with Novu's REST APIs.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+### Framework SDK
+
+The Framework SDK is a TypeScript library that allows you to build notification workflows and execute them in your own runtime environment.
+
+
+
+
+
+
+
+ While triggering notifications is supported in all SDKs, creating and managing notification workflows is only supported in the Framework Typescript SDK.
+
+
+## Web and Mobile SDKs
+
+Novu provides the following web client SDKs to enable integrations with Novu's prebuilt UI components, allowing you to easily add notification functionality to your applications without handling complex notification logic manually.
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sdks/php.mdx b/sdks/php.mdx
new file mode 100644
index 00000000..785fc00b
--- /dev/null
+++ b/sdks/php.mdx
@@ -0,0 +1,118 @@
+---
+title: "PHP SDK"
+description: "Connect a PHP application to Novu"
+icon: "php"
+---
+
+Novu's PHP SDK provides simple, yet comprehensive notification management, and delivery capabilities through multiple channels that you can implement using code that integrates seamlessly with your PHP application.
+
+[Explore the source code on GitHub](https://github.com/novuhq/php-novu)
+
+## Installation
+The SDK relies on [Composer](https://getcomposer.org/) to manage its dependencies.
+
+To install the SDK and add it as a dependency to an existing composer.json file:
+
+```bash
+composer require "novuhq/novu"
+```
+
+## Usage
+
+
+
+```php
+declare(strict_types=1);
+
+require 'vendor/autoload.php';
+
+use novu;
+use novu\Models\Components;
+
+$sdk = novu\Novu::builder()
+ ->setSecurity(
+ ''
+ )
+ ->build();
+
+$triggerEventRequestDto = new Components\TriggerEventRequestDto(
+ workflowId: 'workflow_identifier',
+ to: new Components\SubscriberPayloadDto(
+ subscriberId: 'subscriber_unique_identifier',
+ firstName: 'Albert',
+ lastName: 'Einstein',
+ email: 'albert@einstein.com',
+ ),
+ payload: [
+ 'comment_id' => 'string',
+ 'post' => [
+ 'text' => 'string',
+ ],
+ ],
+ overrides: [
+ 'email' => [
+ 'bcc' => 'no-reply@novu.co',
+ ],
+ ],
+);
+
+$response = $sdk->trigger(
+ triggerEventRequestDto: $triggerEventRequestDto,
+ idempotencyKey: ''
+
+);
+
+if ($response->triggerEventResponseDto !== null) {
+ // handle response
+}
+```
+
+
+```php
+declare(strict_types=1);
+
+require 'vendor/autoload.php';
+
+use novu;
+use novu\Models\Components;
+
+$sdk = novu\Novu::builder()
+ ->setServerURL('https://eu.api.novu.co')
+ ->setSecurity(
+ ''
+ )
+ ->build();
+
+$triggerEventRequestDto = new Components\TriggerEventRequestDto(
+ workflowId: 'workflow_identifier',
+ to: new Components\SubscriberPayloadDto(
+ subscriberId: 'subscriber_unique_identifier',
+ firstName: 'Albert',
+ lastName: 'Einstein',
+ email: 'albert@einstein.com',
+ ),
+ payload: [
+ 'comment_id' => 'string',
+ 'post' => [
+ 'text' => 'string',
+ ],
+ ],
+ overrides: [
+ 'email' => [
+ 'bcc' => 'no-reply@novu.co',
+ ],
+ ],
+);
+
+$response = $sdk->trigger(
+ triggerEventRequestDto: $triggerEventRequestDto,
+ idempotencyKey: ''
+
+);
+
+if ($response->triggerEventResponseDto !== null) {
+ // handle response
+}
+```
+
+
diff --git a/sdks/python.mdx b/sdks/python.mdx
new file mode 100644
index 00000000..f1ecb478
--- /dev/null
+++ b/sdks/python.mdx
@@ -0,0 +1,161 @@
+---
+title: "Python SDK"
+description: "Connect a Python application to Novu"
+icon: "python"
+---
+
+Novu's Python SDK provides simple, yet comprehensive notification management, and delivery capabilities through multiple channels that you can implement using code that integrates seamlessly with your Python application.
+
+[Explore the source code on GitHub](https://github.com/novuhq/novu-py)
+
+## Installation
+
+### PIP
+PIP is the default package installer for Python, enabling easy installation and management of packages from PyPI via the command line.
+```
+pip install novu-py
+```
+
+### Poetry
+Poetry is a modern tool that simplifies dependency management and package publishing by using a single pyproject.toml file to handle project metadata and dependencies.
+```
+poetry add novu-py
+```
+
+### Shell and script usage with uv
+You can use this SDK in a Python shell with uv and the uvx command that comes with it like so:
+```
+uvx --from novu-py python
+````
+
+It's also possible to write a standalone Python script without needing to set up a whole project like so:
+```bash
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.9"
+# dependencies = [
+# "novu-py",
+# ]
+# ///
+
+from novu_py import Novu
+
+sdk = Novu(
+ # SDK arguments
+)
+
+# Rest of script here...
+```
+Once that is saved to a file, you can run it with uv run `script.py` where`script.py` can be replaced with the actual file name.
+
+## Usage
+
+
+```python
+# Synchronous Example
+import novu_py
+from novu_py import Novu
+import os
+
+with Novu(
+ secret_key=os.getenv("NOVU_SECRET_KEY", ""),
+) as novu:
+
+ res = novu.trigger(trigger_event_request_dto=novu_py.TriggerEventRequestDto(
+ workflow_id="workflow_identifier",
+ to={
+ "subscriber_id": "subscriber_unique_identifier",
+ "first_name": "Albert",
+ "last_name": "Einstein",
+ "email": "albert@einstein.com",
+ },
+ payload={
+ "comment_id": "string",
+ "post": {
+ "text": "string",
+ },
+ },
+ overrides={
+ "email": {
+ "bcc": "no-reply@novu.co"
+ }
+ },
+ ))
+
+ # Handle response
+ print(res)
+
+# Asynchronous Example
+import asyncio
+import novu_py
+from novu_py import Novu
+import os
+
+async def main():
+ async with Novu(
+ secret_key=os.getenv("NOVU_SECRET_KEY", ""),
+ ) as novu:
+
+ res = await novu.trigger_async(trigger_event_request_dto=novu_py.TriggerEventRequestDto(
+ workflow_id="workflow_identifier",
+ to={
+ "subscriber_id": "subscriber_unique_identifier",
+ "first_name": "Albert",
+ "last_name": "Einstein",
+ "email": "albert@einstein.com",
+ },
+ payload={
+ "comment_id": "string",
+ "post": {
+ "text": "string",
+ },
+ },
+ overrides={
+ "email": {
+ "bcc": "no-reply@novu.co",
+ }
+ },
+ ))
+
+ # Handle response
+ print(res)
+
+asyncio.run(main())
+```
+
+
+```python
+import novu_py
+from novu_py import Novu
+import os
+
+with Novu(
+ server_url="https:/eu.api.novu.co",
+ secret_key=os.getenv("NOVU_SECRET_KEY", ""),
+) as novu:
+
+ res = novu.trigger(trigger_event_request_dto=novu_py.TriggerEventRequestDto(
+ workflow_id="workflow_identifier",
+ to={
+ "subscriber_id": "subscriber_unique_identifier",
+ },
+ payload={
+ "comment_id": "string",
+ "post": {
+ "text": "string",
+ },
+ },
+ overrides={
+ "email": {
+ "bcc": "no-reply@novu.co"
+ }
+ },
+ ))
+
+ # Handle response
+ print(res)
+
+```
+
+
+
diff --git a/sdks/ruby.mdx b/sdks/ruby.mdx
new file mode 100644
index 00000000..7b702749
--- /dev/null
+++ b/sdks/ruby.mdx
@@ -0,0 +1,40 @@
+---
+title: "Ruby SDK"
+description: "Connect a Ruby application to Novu."
+icon: "gem"
+---
+
+
+ This SDK is built and maintained by our community and might not be up to date with the latest Novu features.
+
+
+Novu's Ruby SDK provides simple, yet comprehensive notification management, and delivery capabilities through multiple channels that you can implement using code that integrates seamlessly with your Ruby application.
+
+[Explore the source code on GitHub](https://github.com/novuhq/novu-ruby)
+
+## Installation
+
+```javascript
+gem install novu
+```
+
+## Usage
+
+```ruby
+require 'novu'
+
+client = Novu::Client.new(access_token: 'YOUR_NOVU_API_TOKEN')
+
+payload = {
+ 'name' => 'Trigger1',
+ 'payload' => { # optional
+ 'first-name' => 'Adam' # optional
+ },
+ 'to' => {
+ 'subscriberId' => '7789'
+ },
+ 'transactionId' => '89kjfke9893' #optional
+}
+
+client.trigger_event(payload)
+```
diff --git a/snippets/apikey-warning.mdx b/snippets/apikey-warning.mdx
new file mode 100644
index 00000000..9d7ea157
--- /dev/null
+++ b/snippets/apikey-warning.mdx
@@ -0,0 +1,6 @@
+
+Enter your API key in the `Authorization` field like the example shown below:
+
+E.g `ApiKey 18d2e625f05d80e`
+
+
diff --git a/snippets/cloud-only-feature.mdx b/snippets/cloud-only-feature.mdx
new file mode 100644
index 00000000..0f3b681f
--- /dev/null
+++ b/snippets/cloud-only-feature.mdx
@@ -0,0 +1,4 @@
+
+ This feature is available only in Novu cloud. This feature will not work in
+ community self hosted version and local environment.
+
diff --git a/snippets/components.mdx b/snippets/components.mdx
new file mode 100644
index 00000000..c017ed64
--- /dev/null
+++ b/snippets/components.mdx
@@ -0,0 +1,9 @@
+export const HeroCard = ({ img, title, description, href }) => {
+ return (
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/snippets/contact-support.mdx b/snippets/contact-support.mdx
new file mode 100644
index 00000000..9582bae0
--- /dev/null
+++ b/snippets/contact-support.mdx
@@ -0,0 +1,4 @@
+
+ If your question is not answered here, feel free to reach out us at
+ support@novu.co
+
diff --git a/snippets/framework-terminal.mdx b/snippets/framework-terminal.mdx
new file mode 100644
index 00000000..222d29e3
--- /dev/null
+++ b/snippets/framework-terminal.mdx
@@ -0,0 +1,13 @@
+export const FrameworkTerminal = () => {
+ if (typeof document === "undefined") {
+ return null;
+ } else {
+ const clientComponent = document.getElementById("nv-framework-terminal-snippet");
+ if (clientComponent) {
+ clientComponent.innerHTML = '';
+ }
+
+ return
+
+}
+}
diff --git a/snippets/hooks/use-counts-content.mdx b/snippets/hooks/use-counts-content.mdx
new file mode 100644
index 00000000..4284257d
--- /dev/null
+++ b/snippets/hooks/use-counts-content.mdx
@@ -0,0 +1,44 @@
+The `useCounts` hook is used to fetch the notification counts based on the provided filters.
+You can use this hook to fetch the total count of unread, archived notifications or any other custom count based on the filters you provide.
+
+The hook listens for changes in notification counts and will automatically refetch the counts whenever they are updated, for example, when a new notification is received.
+
+### `useCounts` props
+
+
+ Filters to be applied to notifications when calculating counts, allowing you
+ to filter notifications based on workflow tags, read status, and archived
+ status.
+
+
+ Callback function that will be called when the notification counts are
+ successfully fetched. In the callback argument, you will receive the fetched
+ counts.
+
+
+ Callback function that will be called when there is an error fetching the
+ notification counts. In the callback argument, you will receive the error
+ object.
+
+
+### `useCounts` returns
+
+
+ An array of notification counts fetched by the hook. If there are no
+ notification counts fetched yet, it will be `undefined`.
+
+
+ A boolean value indicating the first fetch for notification counts is
+ in-flight.
+
+
+ A boolean value indicating whether the hook is fetching more notification
+ counts.
+
+
+ An error object that will be populated if there is an error fetching the
+ notification counts.
+
+
+ A function that can be called to refetch the notification counts.
+
\ No newline at end of file
diff --git a/snippets/hooks/use-notifications-content.mdx b/snippets/hooks/use-notifications-content.mdx
new file mode 100644
index 00000000..d7ea4cdf
--- /dev/null
+++ b/snippets/hooks/use-notifications-content.mdx
@@ -0,0 +1,77 @@
+The `useNotifications` hook is a convenient way to fetch the list of notifications.
+By default, it fetches both unread and read notifications, but you can apply [filters](/inbox/react/hooks/use-notifications#usenotifications-props) to retrieve only the relevant notifications for your specific use case.
+
+The notifications fetched are paginated, and the hook provides a `fetchMore` function to load more notifications.
+The fetched notifications are stored in the notifications array and are appended to the existing notifications as more are loaded.
+
+Any action performed on a notification instance, like marking it as read, will optimistically update the notification, triggering a re-render in the hook and updating the notifications list accordingly, so you don't need to refetch and manually update your UI.
+
+### `useNotifications` props
+
+
+ Workflow tags can be used to filter notifications, and organize them into
+ different groups. Read more about how can you define [workflow
+ tags](/workflow/overview#tags).
+
+
+ Filter notifications based on their read status. If not provided, all
+ read/unread notifications will be returned.
+
+
+ Filter notifications based on their archived status. Archived notifications
+ are also read at the same time. If not provided, all archived/unarchived
+ notifications will be returned.
+
+
+ Limit the number of notifications to be fetched. It can take any number
+ between 1 and 100. Default is 10.
+
+
+ Callback function that will be called when the notifications are successfully
+ fetched. In the callback argument, you will receive the fetched notifications.
+
+
+ Callback function that will be called when there is an error fetching the
+ notifications. In the callback argument, you will receive the error object.
+
+
+### `useNotifications` returns
+
+
+ An array of notifications fetched by the hook. If there are no notifications
+ fetched yet, it will be `undefined`.
+
+
+ A boolean value indicating the first fetch for notifications is in-flight.
+
+
+ A boolean value indicating whether the hook is fetching more notifications.
+
+
+ An error object that will be populated if there is an error fetching the
+ notifications.
+
+
+ A boolean value indicating whether there are more notifications available to
+ fetch.
+
+
+ A function that can be called to fetch more notifications. It will fetch the
+ next page of notifications and append them to the existing notifications.
+
+
+ A function that can be called to refetch the notifications. It will clear the
+ existing notifications and fetch the first page of notifications.
+
+
+ A function that can be called to mark all notifications as read, including
+ those that have not been fetched.
+
+
+ A function that can be called to archive all notifications, including those
+ that have not been fetched.
+
+
+ A function that can be called to archive all read notifications, including
+ those that have not been fetched.
+
\ No newline at end of file
diff --git a/snippets/hooks/use-novu-content.mdx b/snippets/hooks/use-novu-content.mdx
new file mode 100644
index 00000000..f2e870fd
--- /dev/null
+++ b/snippets/hooks/use-novu-content.mdx
@@ -0,0 +1,8 @@
+
+The `useNovu` hook is used to obtain the [Novu instance](/inbox/headless/api-reference#novu), which allows you to interact with the headless SDK interface.
+
+The hook returns the [Novu](/inbox/headless/api-reference#novu) class instance with multiple methods to interact with the Novu API.
+
+## Example usage
+
+The below example demonstrates how to use the `useNovu` hook to listen for new notifications and show a toast notification.
\ No newline at end of file
diff --git a/snippets/hooks/use-preferences-content.mdx b/snippets/hooks/use-preferences-content.mdx
new file mode 100644
index 00000000..7d373f95
--- /dev/null
+++ b/snippets/hooks/use-preferences-content.mdx
@@ -0,0 +1,42 @@
+
+The `usePreferences` hook is used to retrieve the subscriber preferences. The first item in the list will represent the subscriber global preferences,
+while the remaining items will correspond to the preferences for each workflow.
+
+The hook returns both critical and non-critical preferences. Critical preferences are those that are required for the subscriber to receive notifications,
+while non-critical preferences are those that are optional and could be controlled by the subscriber. You can learn more about the preferences [here](/concepts/preferences).
+
+By default the preferences are cached, but you can use the `refetch` function to fetch the latest preferences.
+
+### `usePreferences` props
+
+
+ Callback function that will be called when the subscriber preferences are
+ successfully fetched. In the callback argument, you will receive the fetched
+ preferences.
+
+
+ Callback function that will be called when there is an error fetching the
+ subscriber preferences. In the callback argument, you will receive the error
+ object.
+
+
+### `usePreferences` returns
+
+
+ An array of subscriber preferences fetched by the hook. If there are no
+ preferences fetched yet, it will be `undefined`.
+
+
+ A boolean value indicating the first fetch for preferences is in-flight.
+
+
+ A boolean value indicating whether the hook is fetching more preferences.
+
+
+ An error object that will be populated if there is an error fetching the
+ preferences.
+
+
+ A function that can be called to refetch the preferences.
+
+
diff --git a/snippets/icons/expressjs.mdx b/snippets/icons/expressjs.mdx
new file mode 100644
index 00000000..e08cae2f
--- /dev/null
+++ b/snippets/icons/expressjs.mdx
@@ -0,0 +1 @@
+export const ExpressjsIcon = () => ()
diff --git a/snippets/icons/h3.mdx b/snippets/icons/h3.mdx
new file mode 100644
index 00000000..bc640771
--- /dev/null
+++ b/snippets/icons/h3.mdx
@@ -0,0 +1 @@
+export const H3Icon = () => ()
diff --git a/snippets/icons/lambda.mdx b/snippets/icons/lambda.mdx
new file mode 100644
index 00000000..6257767f
--- /dev/null
+++ b/snippets/icons/lambda.mdx
@@ -0,0 +1 @@
+export const LambdaIcon = () => ()
diff --git a/snippets/icons/nestjs.mdx b/snippets/icons/nestjs.mdx
new file mode 100644
index 00000000..66cf51bc
--- /dev/null
+++ b/snippets/icons/nestjs.mdx
@@ -0,0 +1 @@
+export const NestjsIcon = () => ()
diff --git a/snippets/icons/nextjs.mdx b/snippets/icons/nextjs.mdx
new file mode 100644
index 00000000..60c19fa8
--- /dev/null
+++ b/snippets/icons/nextjs.mdx
@@ -0,0 +1 @@
+export const NextjsIcon = () => ()
diff --git a/snippets/icons/nuxt.mdx b/snippets/icons/nuxt.mdx
new file mode 100644
index 00000000..8ef152b5
--- /dev/null
+++ b/snippets/icons/nuxt.mdx
@@ -0,0 +1 @@
+export const NuxtIcon = () => ()
diff --git a/snippets/icons/remix.mdx b/snippets/icons/remix.mdx
new file mode 100644
index 00000000..25151b12
--- /dev/null
+++ b/snippets/icons/remix.mdx
@@ -0,0 +1 @@
+export const RemixIcon = () => ()
diff --git a/snippets/icons/svelte.mdx b/snippets/icons/svelte.mdx
new file mode 100644
index 00000000..e69d28d9
--- /dev/null
+++ b/snippets/icons/svelte.mdx
@@ -0,0 +1 @@
+export const SvelteIcon = () => ()
diff --git a/snippets/inbox/headless/api-reference/function-params.mdx b/snippets/inbox/headless/api-reference/function-params.mdx
new file mode 100644
index 00000000..abe19863
--- /dev/null
+++ b/snippets/inbox/headless/api-reference/function-params.mdx
@@ -0,0 +1,9 @@
+#### Params
+
+
+ Notification ID is a unique identifier for the notification. It can be taken
+ from the [notification object](/inbox/headless/api-reference#notification-interface).
+
+
+ Notification object represents the message sent to the subscriber.
+
diff --git a/snippets/inbox/headless/api-reference/novu-params.mdx b/snippets/inbox/headless/api-reference/novu-params.mdx
new file mode 100644
index 00000000..fb170871
--- /dev/null
+++ b/snippets/inbox/headless/api-reference/novu-params.mdx
@@ -0,0 +1,30 @@
+
+ `applicationIdentifier` is a unique identifier for the application. This is is
+ public credential that is used to authenticate the application with the Novu
+ API. `applicationIdentifier` can be copied from [API
+ Keys](https://dashboard-v2.novu.co/api-keys) page.
+
+
+
+ `subscriberId` is a unique identifier for the subscriber. Read more about
+ [subscribers](/concepts/subscribers).
+
+
+
+ `subscriberHash` is a unique identifier for the subscriber. It is used for
+ HMAC encryption. Read more about [HMAC
+ Encryption](/inbox/react/production#hmac-encryption).
+
+
+
+ Use `https://eu.api.novu.co` value for EU region.
+
+
+
+ Use `https://eu.ws.novu.co` value for EU region.
+
+
+
+ The field is used to enable/disable the cache for the notifications and
+ preferences. By default, the cache is enabled.
+
diff --git a/snippets/inbox/react/novu-provider-example.mdx b/snippets/inbox/react/novu-provider-example.mdx
new file mode 100644
index 00000000..75658e74
--- /dev/null
+++ b/snippets/inbox/react/novu-provider-example.mdx
@@ -0,0 +1,15 @@
+```tsx
+import { NovuProvider } from '@novu/react/hooks';
+
+function App() {
+ return (
+
+ {/* Your app components where you want to use the hooks */}
+
+
+ );
+}
+```
diff --git a/snippets/inbox/react/use-notifications-example.mdx b/snippets/inbox/react/use-notifications-example.mdx
new file mode 100644
index 00000000..332943ca
--- /dev/null
+++ b/snippets/inbox/react/use-notifications-example.mdx
@@ -0,0 +1,32 @@
+```tsx
+import { useNotifications } from '@novu/react/hooks';
+
+const NotificationsList = () => {
+ const { notifications, isLoading, fetchMore, hasMore } = useNotifications();
+
+ return (
+ }>
+ 0}
+ fallback={}
+ >
+ }
+ >
+ {notifications?.map((notification) => {
+ return (
+
+ );
+ })}
+
+
+
+ );
+};
+```
diff --git a/snippets/inbox/react/use-unread-count-example.mdx b/snippets/inbox/react/use-unread-count-example.mdx
new file mode 100644
index 00000000..2770b370
--- /dev/null
+++ b/snippets/inbox/react/use-unread-count-example.mdx
@@ -0,0 +1,14 @@
+```tsx
+import { useCounts } from '@novu/react/hooks';
+
+const BellButton = () => {
+ const { counts } = useCounts({ filters: [{ read: false }] });
+
+ return (
+
+ );
+};
+```
diff --git a/snippets/missing-provider.mdx b/snippets/missing-provider.mdx
new file mode 100644
index 00000000..2495236e
--- /dev/null
+++ b/snippets/missing-provider.mdx
@@ -0,0 +1,14 @@
+export const MissingProvider = ({ channelName }) => (
+
+ Are we missing a provider you'd like to use for{" "}
+ {channelName ? channelName : "this"} channel? Please consider adding a new
+ feature request on{" "}
+
+ github
+ {" "}
+ or help us upvoting the existing ones on our public{" "}
+ roadmap
+
+);
+
+;
diff --git a/snippets/provider-implementation.mdx b/snippets/provider-implementation.mdx
new file mode 100644
index 00000000..9c744b1e
--- /dev/null
+++ b/snippets/provider-implementation.mdx
@@ -0,0 +1,5 @@
+
+ If you are looking to use {provider} provider in different way or your usecase
+ is not covered by this documentation, you can always reach out to our team at
+ support@novu.co or via [Discord](https://discord.novu.co)
+
diff --git a/snippets/quickstart/deploy.mdx b/snippets/quickstart/deploy.mdx
new file mode 100644
index 00000000..c2e87de5
--- /dev/null
+++ b/snippets/quickstart/deploy.mdx
@@ -0,0 +1,6 @@
+
+ Once you have finished refining your first workflow, it’s time to sync your local changes to Novu Cloud. Novu recommends deploying your workflows similarly to how you will deploy the features that generate those notifications using your CI/CD pipeline or our CLI command.
+
+ Read more about [syncing your changes to the cloud](/framework/deployment/production).
+
+
diff --git a/snippets/quickstart/next-steps.mdx b/snippets/quickstart/next-steps.mdx
new file mode 100644
index 00000000..245741f8
--- /dev/null
+++ b/snippets/quickstart/next-steps.mdx
@@ -0,0 +1,19 @@
+## Next Steps
+
+
+
+ Learn how to build workflows with the Novu Framework.
+
+
+ Learn more about the Novu Framework SDK
+
+
+ Add the Inbox component to your application with our front-end SDK.
+
+
+ Deploy your workflows to Development and Production
+
+
diff --git a/snippets/quickstart/packages.mdx b/snippets/quickstart/packages.mdx
new file mode 100644
index 00000000..b733adcf
--- /dev/null
+++ b/snippets/quickstart/packages.mdx
@@ -0,0 +1,12 @@
+
+
+```bash
+npm install @novu/framework zod zod-to-json-schema
+```
+
+This will install the following packages
+
+- **`@novu/framework`** SDK Package
+- **Zod** (Recommended) - For end-to-end type safety for your Payload and Step Controls
+
+
diff --git a/snippets/quickstart/remix-workflow.mdx b/snippets/quickstart/remix-workflow.mdx
new file mode 100644
index 00000000..4db97337
--- /dev/null
+++ b/snippets/quickstart/remix-workflow.mdx
@@ -0,0 +1,22 @@
+```tsx app/novu/workflows.ts
+import { workflow } from '@novu/framework';
+import { z } from 'zod';
+
+export const testWorkflow = workflow('test-workflow', async ({ step, payload }) => {
+ await step.email('send-email', async (controls) => {
+ return {
+ subject: controls.subject,
+ body: 'This is your first Novu Email ' + payload.userName,
+ };
+ },
+ {
+ controlSchema: z.object({
+ subject: z.string().default('A Successful Test on Novu from {{userName}}'),
+ }),
+ });
+ }, {
+ payloadSchema: z.object({
+ userName: z.string().default('John Doe'),
+ }),
+});
+```
diff --git a/snippets/quickstart/secret.mdx b/snippets/quickstart/secret.mdx
new file mode 100644
index 00000000..86cc1dab
--- /dev/null
+++ b/snippets/quickstart/secret.mdx
@@ -0,0 +1,8 @@
+
+ Add `NOVU_SECRET_KEY` environment variable to your `.env`
+
+ ```env
+ NOVU_SECRET_KEY=
+ ```
+
+
diff --git a/snippets/quickstart/start-studio.mdx b/snippets/quickstart/start-studio.mdx
new file mode 100644
index 00000000..c250bd6b
--- /dev/null
+++ b/snippets/quickstart/start-studio.mdx
@@ -0,0 +1,22 @@
+
+ The [Local Studio](/framework/studio) is where you will build your notification workflows and craft the controls that will be
+ exposed for your non-technical peers to maintain after your workflow is pushed to your Development or Production
+ environments.
+
+
+
+ ```bash
+ npx novu@latest dev
+ ```
+
+
+ ```bash
+ npx novu@latest dev -d https://eu.dashboard.novu.co
+ ```
+
+
+
+ The `dev` command is your go-to command whenever you want to build and preview your changes before syncing to cloud. By default, it will start a secure tunnel that our durable cloud workflow engine will
+ be able to communicate with, and the [Local Studio](/framework/studio) web service listening on `http://localhost:2022`
+
+
diff --git a/snippets/quickstart/test.mdx b/snippets/quickstart/test.mdx
new file mode 100644
index 00000000..c5f3d083
--- /dev/null
+++ b/snippets/quickstart/test.mdx
@@ -0,0 +1,6 @@
+
+ After your {framework} application is up and running, visit the [Local Studio](/framework/studio) interface that was started on `http://localhost:2022` by running the `npx novu dev` command on the first step.
+
+ The onboarding guide will guide you to send the newly created sample workflow to your e-mail address.
+
+
diff --git a/snippets/quickstart/workflow.mdx b/snippets/quickstart/workflow.mdx
new file mode 100644
index 00000000..03c3d108
--- /dev/null
+++ b/snippets/quickstart/workflow.mdx
@@ -0,0 +1,22 @@
+```tsx
+import { workflow } from '@novu/framework';
+import { z } from 'zod';
+
+export const testWorkflow = workflow('test-workflow', async ({ step, payload }) => {
+ await step.email('send-email', async (controls) => {
+ return {
+ subject: controls.subject,
+ body: 'This is your first Novu Email ' + payload.userName,
+ };
+ },
+ {
+ controlSchema: z.object({
+ subject: z.string().default('A Successful Test on Novu from {{userName}}'),
+ }),
+ });
+}, {
+ payloadSchema: z.object({
+ userName: z.string().default('John Doe'),
+ }),
+});
+```
diff --git a/snippets/self-host-only-api.mdx b/snippets/self-host-only-api.mdx
new file mode 100644
index 00000000..a4354743
--- /dev/null
+++ b/snippets/self-host-only-api.mdx
@@ -0,0 +1,3 @@
+
+This API is available only in V1 self-hosted docker images. Novu Cloud uses [Clerk](https://clerk.com) to manage users and organizations. For more information contact support@novu.co.
+
diff --git a/snippets/supported-channels.mdx b/snippets/supported-channels.mdx
new file mode 100644
index 00000000..5bc27c7f
--- /dev/null
+++ b/snippets/supported-channels.mdx
@@ -0,0 +1,8 @@
+| Channel
| Content Style
| Custom Variables `{{handlebars}}` format |
+| ----------------- | ----------------------- | :------------------------------------------: |
+| **Email** | HTML | ✅ |
+| | Visual Editor | ✅ |
+| **SMS** | Text | ✅ |
+| **Chat** | Text | ✅ |
+| **In-App** | Text | ✅ |
+| **Push** | Text | ✅ |
diff --git a/style.css b/style.css
new file mode 100644
index 00000000..e69de29b
diff --git a/workflow/channel-steps.mdx b/workflow/channel-steps.mdx
new file mode 100644
index 00000000..0effb65d
--- /dev/null
+++ b/workflow/channel-steps.mdx
@@ -0,0 +1,46 @@
+---
+title: "Channel Steps"
+---
+
+A **channel step** within a workflow is the core building block for creating and delivering notifications to subscribers (End users). Each channel step is linked to a specific notification template and represents a notification to be sent through a single channel type (e.g., email, push, SMS, in-app, etc.).
+
+### Channel Step Execution
+
+When a channel step is executed, Novu performs the following actions:
+
+1. **Evaluates Step Conditions:**
+ Novu checks any conditions defined for the step to determine if it should be executed. This allows for dynamic notification workflows.
+
+2. **Verifies Subscriber's Information:**
+ Novu ensures the subscriber has the required information to receive notifications via this channel.
+
+ For example:
+ - For email, the recipient must have a valid email address.
+ - For push notifications, the required channel data (device token) must be configured.
+
+3. **Checks Subscriber's Preferences:**
+ Novu respects the recipient's notification preferences, verifying whether they’ve opted out of receiving notifications from this channel or workflow.
+
+### Rendering and Delivering Notifications
+
+If the step is valid and all conditions are met, Novu renders the associated template for the step and queues the message for delivery. The message is then sent to the selected provider using the credentials configured for the channel.
+
+### Available Channels
+
+
+
+ The most reliable way to reach users directly in their inbox for newsletters, updates, and transactional communications
+
+
+ Deliver notifications directly within your app or website, keeping users informed while they interact with your product
+
+
+ Instant alerts sent to users' mobile or desktop devices, even when they’re not actively using your app
+
+
+ Quick and direct text messages to users’ phones for urgent alerts, confirmations, or updates
+
+
+ Real-time messages delivered through popular chat platforms and messaging apps
+
+
\ No newline at end of file
diff --git a/workflow/content/content-basics.mdx b/workflow/content/content-basics.mdx
new file mode 100644
index 00000000..727b82d6
--- /dev/null
+++ b/workflow/content/content-basics.mdx
@@ -0,0 +1,52 @@
+---
+title: "Content Basics"
+sidebarTitle: "Content Basics"
+description: "Learn how to manage your notification workflow and allow no-code users to modify it."
+---
+
+Our philosophy is to allow developers to build and manage advanced components and customized views with modern technologies, and expose No-Code controls for non-technical users to modify and maintain.
+
+## Notification content management is hard
+
+One of the main challenges with transactional notifications and existing off-the-shelve solutions is that in a lot of cases they are built with either: A very technical developer oriented user in mind, or a complete no-code WYSIWYG solution.
+
+Both paths lead to frustration.
+
+### Code Based Editors
+
+This path allow developers to craft tailored experiences that are consistent with the company brand, but often this leads to require engineers time for any modification needed.
+
+With a lot of UI based notification platforms, you will be bound and limited by their choice of templating language (Liquid, handlebars, etc...) which will force engineers to do some really complex manipulations to build the usecase they need.
+
+Which leads to un-maintainable notification content that will require engineer for every change.
+
+### No-Code Editors
+
+Those platforms give a lot of control and flexibility to a non-technical user with a great looking WYSIWIG interface, but most often this can lead to:
+
+- The loss of confidence in the end result, as a critical notification workflow can be easily broken.
+- A lot of limits on the implementation, that usually requires engineer intervention and using HTML directly. Which again, losses the whole point of a no-code editor.
+
+## The Novu Way
+
+History have shown us that catering to an individual persona can lead to a lot of frustration, which in the end generated a non-consistent end-user experience.
+
+We strongly believe that for a truly amazing notificaton experience, both functions need to work in harmony.
+
+### Your notification design-system
+
+Instead of building isolated emails, messages and workflows, we recommend thinking about your notification strategy as carefully crafted Design System Components.
+
+This will allow you to build a consistent and maintainable notification experience that can be easily consumed by your internal teams.
+
+### Abstract complex logic
+
+Step Controls are the `props` in your design system components. They are the knobs and dials that allow you to modify the content and behavior of notification template.
+
+There might a lot of complex logic that goes into a notification template, and for mission-critical notifications like your `Password reset` email,
+you might want to expose controls for the static content before and after the password reset link, but to not accidentally to delete the dynamic `{{resetLinkUrl}}` button.
+
+### Use modern frameworks
+
+With Novu we recommend using a modern content framework SDK such as: MJML, React Email, Vue Email, Maizzle and etc...
+Using a technical stack that is close to your front-end stack will allow reusing a lot of existing application logic and design tokens as part of your workflows.
diff --git a/workflow/content/editor.mdx b/workflow/content/editor.mdx
new file mode 100644
index 00000000..e69de29b
diff --git a/workflow/content/handlebars-and-helpers.mdx b/workflow/content/handlebars-and-helpers.mdx
new file mode 100644
index 00000000..e69de29b
diff --git a/workflow/content/variables.mdx b/workflow/content/variables.mdx
new file mode 100644
index 00000000..e69de29b
diff --git a/workflow/delay.mdx b/workflow/delay.mdx
new file mode 100644
index 00000000..cab6edd2
--- /dev/null
+++ b/workflow/delay.mdx
@@ -0,0 +1,21 @@
+---
+title: "Delay Steps in Workflows"
+sidebarTitle: "Delay Action"
+description: "Learn how to use delay steps to control timing and pacing in your notification workflows."
+---
+
+The delay action awaits a specified amount of time before moving on to trigger the following steps of the workflow.
+
+## Common usecases
+
+- Waiting for X amount of time before sending the message
+- Wait for a short period of time before sending a push message in case the user seen the notification in the Inbox Component
+- Allow the user some time to cancel an action that generated a notification
+
+## Adding a delay step
+
+Delay steps can be inserted at any stage of your workflow execution, they can happen after or before any action. The workflow execution will be halted for the given amount of time and then resumed to the next step in the flow.
+
+The action can also be skipped using the `skip` parameter conditionally to allow more complex usecases of when to wait and when to send an email immediately.
+
+Changing the step content after triggering the workflow with delay step will not affect the existing pending delayed notification content.
\ No newline at end of file
diff --git a/workflow/digest.mdx b/workflow/digest.mdx
new file mode 100644
index 00000000..d7e6bd7b
--- /dev/null
+++ b/workflow/digest.mdx
@@ -0,0 +1,83 @@
+---
+title: "Collect Multiple Events to a Single Message with the Digest Engine"
+sidebarTitle: "Digest action"
+---
+
+The digest engine collects multiple trigger events, aggregates them into a single message, and delivers that new payload to the next workflow step.
+This becomes useful when a user would otherwise receive a large number of notifications, and you want to avoid over-notifying. When you use a Digest action step, Novu automatically batches the incoming trigger events based on the `subscriberId` and an **optional** `digestKey` that can be added to control the digest logic of the events.
+
+## Digest action step
+
+After adding a digest step to a workflow, each node that will be below the digest node will be only triggered once in the specified digest interval.
+You can decide to send messages before adding a digest node and they will be triggered in real-time.
+
+
+
+## Digest configuration
+
+### Digest key
+
+If specified, the digest engine will group the events based on the `digestKey` and `subscriberId`, otherwise the digest engine will group the events based only on the subscriberId.
+
+The digest key might come useful when you want a particular subscriber to get events grouped on a custom field. For example when an actor likes the user's post, you might want to digest based on the `post_id` key.
+
+### Time interval
+
+The time interval determines how long the digest engine will wait before sending the message once created. You can specify the amount and the unit that best suits your needs.
+
+Here, in the image below, `5` is the interval amount, and `mins` is the interval unit. Interval units can be `sec(s)`, `min(s)`, `hour(s)`, or `day(s)`.
+
+## Digest strategy types
+
+The strategy which Novu should use to handle the digest step. More details on available strategies below.
+
+Novu allows you to define different digest strategies depending on the actual use-case you are trying to achieve. At this point we allow you to select from 2 strategies:
+
+- Regular
+- Look-back
+- Scheduled
+
+Let's explore them in detail:
+
+### Regular strategy
+
+In regular strategy, a digest will always be created for the specified window time. Which means that from the first event trigger, if no active digest exists for this subscriber, one will be created and the user will receive the message only when the digest window time is reached.
+
+### Look-back strategy
+
+In the Look-Back strategy, before creating a digest, Novu will check if a message was sent to the user in the Look-back period. If a message was sent, a digest will be created. Otherwise, a message will be sent directly to the user and the digest creation will be skipped.
+
+Look-back digest has two intervals, `digest interval` and `look-back window`. First, it checks if any event is triggered within the past look-back window, only then a digest is created for the digest interval. If not, the event is considered non-digest and workflow execution continues to the next step.
+
+#### Example
+
+Let's set the digest interval as 20 minutes and the look-back interval as 15 minutes.
+
+If we trigger the first event. Since it is the first event and there was no event triggered in the past 15 minutes (look-back interval), this event will send a message immediately (without digest).
+Now, if we trigger a second event within 15 minutes range, then a new digest will be created with this second event. From now on, for the next 20 minutes (digest interval), all triggers will be digested, and after 20 minutes, the workflow will carry forward to the next step with digested events as a payload.
+
+### Scheduled digest
+
+All digest times are in UTC time
+
+Digest incoming events for the specified time. Once that time threshold since the first event has passed, proceed to the next workflow step.
+
+Available timeframes:
+- Minutes
+- Hours
+- Daily
+- Weekly
+
+## Frequently Asked Questions
+
+
+
+ If scheduled digest is sent for 9:00 daily, then novu will collect all events triggered between 9:00 AM today till 9:00 AM tomorrow and send the digest at 9:00 AM tomorrow. This process is repeated daily. If there is no any event triggered between 9:00 AM today and 9:00 AM tomorrow, then no digest will be sent.
+
+
+
+ Both digests are same in this case.
+
+
+
+
diff --git a/workflow/how-to/build-a-workflow.mdx b/workflow/how-to/build-a-workflow.mdx
new file mode 100644
index 00000000..267db5be
--- /dev/null
+++ b/workflow/how-to/build-a-workflow.mdx
@@ -0,0 +1,93 @@
+---
+title: "Build a Workflow"
+sidebarTitle: "Build a workflow"
+description: "Learn how to build a Novu Workflow"
+---
+
+
+
+ This is where you can find and manage your existing workflows.
+
+
+ This will open the initial workflow creation screen.
+ Here you define:
+ - Name of the workflow
+ - This is also referred to as the workflow `Identifier`
+ - Tags (optional)
+ - Description of the workflow (optional)
+
+ Once you've filled in the required fields, click on the **"Create Workflow"** button.
+
+
+ This is where you can start adding steps to your workflow. **The first step will always be the **"Workflow Trigger"**.
+
+ You can add the following steps by clicking on the **"Add Step"** (+) button:
+
+ **Channels**
+ - Email
+ - In-app
+ - Push
+ - Web push
+ - Mobile push
+ - Chat
+ - Enterprise messaging platforms (e.g. Slack, Microsoft Teams, etc.)
+ - Consumer messaging tools (e.g. WhatsApp, Telegram, Discord, etc.)
+ - SMS
+
+ If a channel step you've added is not configured in your Novu account, you'll see an Error.
+
+
+ **Actions**
+ - Digest
+ - Delay
+ - Custom (Coming soon, currently only available in the [Novu Framework](/framework/custom))
+
+
+ Each step in the workflow requires a template that defines the notification or message content and payload. The editor enables you to preview the rendered output of your content.
+
+ **Content creation and templates**
+
+ - Each channel step has its own template configuration options, tailored to the limitations and requirements of the specific channel.
+ - The editor provides a live preview to show how the final message will appear.
+ - You can use system variables for personalization, including:
+ - Subscriber variables: firstName, lastName, email, phone, avatar.
+ - Actor variables: for details about the event initiator.
+ - Step variables: for data specific to the workflow execution.
+ - Brand variables: for aligning with your organization's visual identity.
+ - Tenant variables: for organization-specific data.
+
+**Dynamic content**
+
+ - Inject dynamic data into your templates using payload variables.
+ - These variables can be utilized in message content, subjects, and sender names for enhanced personalization.
+
+**Important:** When using dynamic content with payload variables, ensure that the required payload is passed when triggering the workflow.
+
+ Once you’ve finished configuring your template, don’t forget to click the `Save step` button to apply your changes.
+
+
+
+
+ There are three main ways to trigger a workflow:
+
+ - **Via the Novu Dashboard**
+ This method is ideal for conducting quick tests directly from the Dashboard. It’s a simple and convenient way to verify basic functionality.
+
+ - **Using the trigger code snippet**
+ Copy the code snippet and execute it in your local environment or an online sandbox. This approach allows for more thorough testing, enabling you to integrate the trigger with your application logic and live data for a realistic evaluation.
+
+ - **Integrating the trigger in your application**
+ Once all tests are complete, you can implement the trigger method directly in your application. This allows you to test the workflow in a real-world scenario, ensuring it functions seamlessly with your app's actual environment and users.
+
+
+Novu operates in a multi environment setup, with the currently available environments:
+
+- **Development**: Acts as a staging and test environment, where your non-technical peers can view and modify controls.
+- **Production**: For triggering workflows to your customers.
+
+After you've tested your workflow in the **Development** environment, you can promote it to **Production**.
+
+[Learn more about promoting workflows to production](/framework/deployment/production)
+
+
+
diff --git a/workflow/media-assets/digest-engine.png b/workflow/media-assets/digest-engine.png
new file mode 100644
index 00000000..e3fc9af1
Binary files /dev/null and b/workflow/media-assets/digest-engine.png differ
diff --git a/workflow/media-assets/how-it-works.png b/workflow/media-assets/how-it-works.png
new file mode 100644
index 00000000..cba34ef0
Binary files /dev/null and b/workflow/media-assets/how-it-works.png differ
diff --git a/workflow/media-assets/step-conditions.png b/workflow/media-assets/step-conditions.png
new file mode 100644
index 00000000..81553646
Binary files /dev/null and b/workflow/media-assets/step-conditions.png differ
diff --git a/workflow/media-assets/step-conditions1.png b/workflow/media-assets/step-conditions1.png
new file mode 100644
index 00000000..3d83fa02
Binary files /dev/null and b/workflow/media-assets/step-conditions1.png differ
diff --git a/workflow/media-assets/workflow-editor.png b/workflow/media-assets/workflow-editor.png
new file mode 100644
index 00000000..232e1dd4
Binary files /dev/null and b/workflow/media-assets/workflow-editor.png differ
diff --git a/workflow/overview.mdx b/workflow/overview.mdx
new file mode 100644
index 00000000..81608147
--- /dev/null
+++ b/workflow/overview.mdx
@@ -0,0 +1,51 @@
+---
+title: "Workflow Editor Overview"
+sidebarTitle: "Overview"
+description: "The workflow Editor combines no-code simplicity and code-based flexibility, enabling users to design and manage advanced notification workflows tailored to their needs."
+---
+
+The workflow Editor is a robust visual tool that empowers both no-code users and developers to design advanced notification workflows.
+It seamlessly combines the intuitive simplicity of no-code building blocks with the adaptability and precision of code-based customization.
+### What is a workflow?
+
+A workflow in Novu is a container for all notification/message logic and templates within your system.
+
+Each workflow:
+
+- Has a unique identifier (key)
+- Executes for one subscriber at a time (e.g. end user, recipient, customer, etc.)
+- Contains complete notification/message logic and templates
+- Supports subscriber preference management
+- Can be triggered via API calls, events, or scheduled operations
+
+
+Learn what workflows are and how they work in Novu
+
+
+## Different types of Novu Workflows
+
+### Visual workflow editor (No-Code)
+
+**Best suited for:**
+
+- Straightforward use cases without complex logic
+- Building emails using Novu's Email WYSIWYG Editor
+- Modifying existing workflows
+- Quick prototyping, testing, and iteration
+- Collaboration with non-technical stakeholders
+
+### Framework SDK (Code-Based)
+
+**Best suited for:**
+
+- Complex workflow logic implementation
+- External API integration
+- Custom data transformation
+- Advanced routing rules
+- Type safe workflow payloads
+- Specialized business logic
+- Complex conditional branches
+- Custom email templates (React Email, Vue Email, MJML etc.)
+- Workflow versioning
+
+[Learn more about Novu Framework](/framework/overview)
\ No newline at end of file
diff --git a/workflow/step-conditions.mdx b/workflow/step-conditions.mdx
new file mode 100644
index 00000000..9b09695c
--- /dev/null
+++ b/workflow/step-conditions.mdx
@@ -0,0 +1,180 @@
+---
+title: "Step Conditions"
+sidebarTitle: "Step Conditions"
+description: "Create dynamic notification workflows using rule-based conditions. Control message delivery based on subscriber data, payload information, and workflow outcomes."
+---
+
+The Step Condition feature in Novu enables you to define conditional logic for each workflow step (node), ensuring a precise and tailored notification experience. This feature adds flexibility and control by allowing you to determine whether a step should execute based on subscriber data, payload data, or conditions stemming from previous workflow steps.
+
+---
+
+## What Are Workflow Step Conditions?
+
+When adding a workflow step (node) in Novu, it can either be:
+
+1. **Channel Step**: In-App, Email, SMS, Push, or Chat.
+2. **Action Step**: Digest or Delay.
+
+Each step includes the ability to configure **step conditions** that define whether the step is executed. The conditions can combine multiple logical rules using **AND** and **OR** operators.
+
+---
+
+## Areas for Configuring Step Conditions
+
+### 1. **Subscriber Variables**
+
+Conditions can leverage subscriber-related fields to tailor notifications based on user-specific data. Examples of subscriber variables include:
+
+- `subscriber.firstName`
+- `subscriber.lastName`
+- `subscriber.email`
+- `subscriber.phone`
+- `subscriber.avatar`
+- `subscriber.locale`
+- `subscriber.data`
+- `subscriber.subscriberId`
+- `subscriber.isOnline`
+- `subscriber.lastOnlineAt`
+
+
+
+For instance, you might want to send an SMS only to users whose `subscriber.isOnline` is `false`.
+
+
+
+---
+
+### 2. **Payload Data**
+
+Conditions can also depend on custom payload data passed during the workflow trigger call to the Novu API. This allows you to define dynamic rules based on the data unique to each workflow execution.
+
+Example payload data:
+
+```json
+{
+ "orderId": "12345",
+ "totalAmount": 150,
+ "orderStatus": "completed"
+}
+```
+
+You can configure conditions such as:
+
+- `payload.orderStatus = "completed"`
+- `payload.totalAmount > 100`
+
+This makes it possible to create dynamic notifications based on context-specific information.
+
+---
+
+### 3. **Previous Step Conditions**
+
+For workflows involving sequential steps, conditions can also depend on the **outcome of a previous step**. For example, if the prior step was an **In-App Notification**, the condition could check:
+
+- `steps.in-app-step.seen`
+- `steps.in-app-step.read`
+- `steps.in-app-step.lastSeenDate`
+- `steps.in-app-step.lastReadDate`
+
+This is especially useful for tailoring follow-up notifications. For instance, send an email only if the In-App notification was not read within 24 hours.
+
+---
+
+### 4. **Advanced Application State Calculations**
+
+For more complex scenarios where you need to perform advanced calculations based on your application state, you can use the [Novu Framework Skip Step](/framework/typescript/steps/overview).
+
+This approach allows you to:
+
+- Access your application's database or external services
+- Perform complex business logic calculations
+- Make API calls to external services
+- Execute custom validation rules
+
+The skip step gives you full programmatic control over whether a notification step should be executed, going beyond the built-in condition builder capabilities.
+
+---
+
+## Query Builder Options
+
+The query builder enables you to construct powerful logical expressions for your step conditions. Supported operators include:
+
+| Operator | Description | Example |
+| --- | --- | --- |
+| `=` | Equal to | `subscriber.locale = "en-US"` |
+| `!=` | Not equal to | `subscriber.isOnline != true` |
+| `<` | Less than | `payload.totalAmount < 100` |
+| `>` | Greater than | `payload.totalAmount > 100` |
+| `<=` | Less than or equal to | `payload.totalAmount <= 200` |
+| `>=` | Greater than or equal to | `payload.totalAmount >= 200` |
+| `contains` | Contains a substring | `payload.orderId contains "123"` |
+| `begins with` | Starts with | `subscriber.firstName begins with "J"` |
+| `ends with` | Ends with | `subscriber.email ends with "@xyz.com"` |
+| `does not contain` | Does not contain a substring | `payload.orderId does not contain "456"` |
+| `does not begin with` | Does not start with | `subscriber.firstName does not begin with "M"` |
+| `does not end with` | Does not end with | `subscriber.lastName does not end with "Smith"` |
+| `is null` | Is null | `subscriber.phone is null` |
+| `is not null` | Is not null | `subscriber.email is not null` |
+| `in` | Matches one of several values | `subscriber.locale in ["en-US", "es-ES"]` |
+| `not in` | Does not match any of the listed values | `subscriber.locale not in ["fr-FR", "de-DE"]` |
+| `between` | Within a range | `payload.totalAmount between [50, 200]` |
+| `not between` | Outside of a range | `payload.totalAmount not between [0, 50]` |
+
+---
+
+## Using Dynamic Data Fields for Comparison
+
+The **value** field in a condition can also be a dynamic data field. This allows you to compare two data points dynamically rather than using static values.
+
+For example:
+
+```json
+{
+ "operator": "AND",
+ "conditions": [
+ {
+ "field": "payload.foo",
+ "operator": "=",
+ "value": "{{payload.bar}}"
+ }
+ ]
+}
+```
+
+
+
+In this case, the step will execute only if `payload.foo` is equal to `payload.bar` at runtime.
+
+You can also use subscriber variables in the same way:
+
+```json
+{
+ "operator": "AND",
+ "conditions": [
+ {
+ "field": "subscriber.firstName",
+ "operator": "=",
+ "value": "{{payload.firstName}}"
+ }
+ ]
+}
+```
+
+
+
+This enables flexible condition logic based on real-time data comparisons.
+
+---
+
+## Building Condition Groups
+
+Novu allows you to group multiple conditions using **AND** and **OR** operators to create complex logic. For instance:
+
+- **AND Group**: All conditions in the group must be true for the step to execute.
+- **OR Group**: At least one condition in the group must be true.
+
+Condition groups can also be nested for advanced use cases.
+
+---
+
+Novu's Step Condition feature empowers you to build intelligent and dynamic workflows tailored to your specific use cases. By leveraging subscriber data, payload information, and step outcomes, you can ensure that each notification reaches the right audience at the right time with the appropriate content.
\ No newline at end of file
diff --git a/workflow/tags.mdx b/workflow/tags.mdx
new file mode 100644
index 00000000..4018a140
--- /dev/null
+++ b/workflow/tags.mdx
@@ -0,0 +1,40 @@
+---
+title: "Tags: Organizing Your Notifications"
+sidebarTitle: "Tags"
+description: "Learn how to organize and manage notification tags using Novu's visual workflow editor for better user experience and notification management."
+---
+
+**Tags** act like labels or categories that help you organize and manage notifications in your app. By grouping notifications under specific tags, you can better control how they're filtered, displayed, or managed by both your app and your users.
+
+For example, you might want to group all security-related notifications together, separate from updates about account activity or promotional offers.
+
+## Why Use Tags?
+
+- **Filtering Notifications**: Tags make it easy to filter and display notifications based on categories.
+For instance, you can create a UI that allows users to view only security or promotional notifications.
+- **[Preference Management](/inbox/react/components/preferences)**: With tags, users can manage their notification preferences by category, choosing to enable or mute specific types of notifications.
+
+Think of it as organizing emails into folders—tags help keep things tidy and manageable for both you and your users.
+
+
+## How to Add Tags to Notifications
+
+
+
+ Navigate to the **"Workflows"** tab
+
+
+ Create a new workflow or edit an existing workflow
+
+
+ One of the fields in the workflow is the **Tags** field. You can add one or more (max. 16) tags to a notification
+
+
+
+## Best Practices for Using Tags
+
+- **Define Categories Early**: Identify key notification categories for your app, such as security, promotions, or updates.
+- **Consistent Naming**: Use clear and consistent tag names to avoid confusion (e.g., prefer security over sec_alert).
+- **Keep It Manageable**: Avoid overloading with too many tags. Focus on meaningful groupings that provide real value.
+
+Tags are a powerful way to streamline your notification system, helping users stay organized and informed while giving you greater control over your app's notification behavior.
\ No newline at end of file
diff --git a/workflow/template-editor.mdx b/workflow/template-editor.mdx
new file mode 100644
index 00000000..1701dbfc
--- /dev/null
+++ b/workflow/template-editor.mdx
@@ -0,0 +1,221 @@
+---
+title: "Template Editor"
+description: "Learn how to use the Novu notification template editor to design notifications"
+---
+
+Each channel step in a Novu workflow comes with its own notification template. This template defines how notifications appear for a specific channel.
+
+Quality templates are used to create personalized, visually appealing, and effective notifications.
+
+- **Injecting variables from your trigger data into your notification template.**
+- **Using Liquid syntax for logic and control flow within templates.**
+- **Previewing and testing your notification templates.**
+
+## Personalizing Notifications with Template Variables
+
+To insert a variable into your Novu notification template, use double curly braces: `{{ variable_name }}`.
+
+Novu templates allow you to reference several types of variables:
+
+### Data payload variables
+These variables originate from the data payload in your workflow trigger.
+For example, if you include `{ "order_id": "12345" }` in your payload, you can reference it in your template as `{{ payload.order_id }}`.
+
+### User properties
+You can access user properties (like `firstName` or custom subscriber properties) using `{{ subscriber.* }}`. For instance:
+
+```liquid
+Hi {{ subscriber.firstName }},
+
+You’ve been upgraded to the {{ subscriber.data.plan }} plan.
+
+Thanks,
+The Novu Team
+```
+
+## Variable Popover
+
+When clicking on a variable in the template editor, a popover will appear. This popover can be used to easily manipulate the variable formatting by applying default values or Liquid Filters.
+
+
+
+
+
+### Applying Liquid Filters
+
+The variable popover will display a list of suggested Liquid Filters based on the variable type, you can apply one or more filters to the variable and re-order using drag and drop.
+Re-ordering filters is useful as the filters are applied in the order they are listed, and the output of each filter is passed to the next one.
+
+### Previewing filters output
+
+With more advanced filter logic, you can preview the output of the filters by clicking on the **Preview** button and pass the variable value to see how the filters will be applied.
+
+### Raw Liquid.js syntax
+
+You can also apply raw Liquid.js syntax to the variable by clicking on the **Raw** button which will reveal the raw Liquid.js content that will be applied to the variable.
+
+## Adding logic with Liquid Filters
+
+Novu supports Liquid filters to add dynamic and conditional content to your notifications. Below are examples of how to use the top 10 Liquid filters in real-world notification templates.
+
+Learn more about the Liquid Templating Language.
+
+
+### `capitalize`
+
+Use `capitalize` to ensure proper formatting for user names.
+
+```liquid
+Hello {{ subscriber.firstName | capitalize }},
+Welcome to Novu! We're excited to have you on board.
+```
+
+**Output**:
+`Hello John,
+Welcome to Novu! We're excited to have you on board.`
+
+---
+
+### `upcase`
+
+Use `upcase` for emphasizing specific information like workspace names.
+
+```liquid
+Your workspace {{ payload.workspaceName | upcase }} has been successfully created.
+```
+
+**Output**:
+`Your workspace TEAM ALPHA has been successfully created.`
+
+---
+
+### `downcase`
+
+Use `downcase` for consistent email formatting or usernames.
+
+```liquid
+Hi {{ subscriber.email | downcase }},
+We’ve sent a confirmation to your inbox.
+```
+
+**Output**:
+`Hi john.doe@example.com,
+We’ve sent a confirmation to your inbox.`
+
+---
+
+### `date`
+
+Use `date` to format subscription or event dates.
+
+```liquid
+Your subscription will renew on {{ payload.renewalDate | date: "%B %d, %Y" }}.
+```
+
+**Output**:
+`Your subscription will renew on December 31, 2024.`
+
+---
+
+### `truncate`
+
+Use `truncate` to shorten long content like notification messages.
+
+```liquid
+New comment on your post: {{ payload.commentText | truncate: 20 }}
+Click here to read more.
+```
+
+**Output**:
+`New comment on your post: Great work on your...
+Click here to read more.`
+
+---
+
+### `truncatewords`
+
+Use `truncatewords` to limit the number of words in a preview.
+
+```liquid
+{{ subscriber.firstName }}, here's a preview of the article:
+{{ payload.articleExcerpt | truncatewords: 5 }}
+```
+
+**Output**:
+`John, here's a preview of the article:
+Novu is a flexible and...`
+
+---
+
+### `replace`
+
+Use `replace` to dynamically update template content.
+
+```liquid
+Hi {{ subscriber.firstName }},
+Your {{ payload.subscriptionType | replace: "basic", "premium" }} subscription is active.
+```
+
+**Output**:
+`Hi John,
+Your premium subscription is active.`
+
+---
+
+### `split`
+
+Use `split` to parse tags or interests.
+
+```liquid
+You have new updates in {{ payload.tags | split: "," | join: ", " }}.
+```
+
+**Input**:
+`"announcements,updates,offers"`
+
+**Output**:
+`You have new updates in announcements, updates, offers.`
+
+---
+
+### `join`
+
+Use `join` to list multiple items in a human-readable way.
+
+```liquid
+Hello {{ subscriber.firstName }},
+You have the following items pending: {{ payload.tasks | join: ", " }}.
+```
+
+**Input**:
+`["Upload documents", "Confirm email", "Schedule meeting"]`
+
+**Output**:
+`Hello John,
+You have the following items pending: Upload documents, Confirm email, Schedule meeting.`
+
+---
+
+### `default`
+
+Use `default` to provide fallback values.
+
+```liquid
+Hi {{ subscriber.nickname | default: subscriber.firstName }},
+Your account settings are updated.
+```
+
+**Output (when nickname is null)**:
+`Hi John,
+Your account settings are updated.`
+
+
+ Learn more about 40+ filters supported by LiquidJS
+
+
+## Previewing and testing notification templates
+
+When your notification template is ready, use the **Preview** mode to visualize how your notification will look. You can:
+
+- **Test dynamic payload data:** Provide sample data to see how your template renders with different values.
+- **Send test notifications:** Save your template, return to the workflow canvas, and run a test with real trigger data.