diff --git a/legacy/docs/novu.md b/legacy/docs/novu.md
new file mode 100644
index 00000000..76b6f4fd
--- /dev/null
+++ b/legacy/docs/novu.md
@@ -0,0 +1,86 @@
+
Novu Agentic Service
+
+## Intro
+
+[Novu][novu] provides open-source notification infrastructure for all communication channels in one place: Email, SMS, Direct, and Push. It integrates with almost all major email providers (Mailgun, Sendgrid, Postmark, etc.), SMS providers (e.g., Twilio or Plivo), and a large selection of push and chat providers (such as OneSignal or Slack) while providing a unified API for sending notifications.
+
+## Pre-requisites
+
+Ensure the following environment variable is set:
+
+- `NOVU_API_KEY` - Novu API key.
+
+Otherwise, you can pass it in as an argument to the `Novu` constructor.
+
+### How to Retrieve API Key on Novu.co
+
+1. Open https://web.novu.co and sign in with your existing Novu account credentials (create a new account with your email address and a password or sign in with GitHub if you don't have an account yet)
+
+2. Navigate to "Settings"
+
+
+
+3. Click "API Keys"
+
+
+
+4. Click here to copy your API key to your clipboard
+
+
+
+### Create a Notification Template
+
+For each notification type you want to send, you need to create a template in Novu. This is a one-time setup step that you can do on the Novu web interface.
+
+It is possible to customize the notification content at each invocation via handlebars-style placeholders. For example, you can create a template for an email notification that looks like this:
+
+```
+Hello {{name}},
+{{content}}
+```
+
+The placeholders will be replaced with the actual values of the `payload` object you pass to the `send` method.
+
+To create a template, follow these steps:
+
+1. Open https://web.novu.co and sign in with your Novu account credentials
+
+2. Click "Notifications"
+
+
+
+3. Click "Create Workflow" on the top-right
+
+
+
+4. Double-click the "notification name" field and enter the name of the template. For this example, we choose the name `send-sms`. This is the event name with which the respective notification may be triggered from the API.
+
+
+
+5. To create, for example, a SMS notification template, click and hold "SMS" and drag it to the left underneath the trigger.
+
+
+
+6. Click inside the "SMS message content" text field on the right and enter the content of your SMS, e.g. a handlebars placeholder such as `{{content}}`.
+
+
+
+7. When you're done, click "Update" on the top-right. You are now ready to send SMS notifications via the API. For example, to manually trigger a notification via the Agentic Novu Service client:
+
+```ts
+import { NovuClient } from '@agentic/core'
+
+const client = new NovuClient()
+
+client.triggerEvent('send-sms', { content: 'Hello World!' }, [{
+ subscriberId: '1',
+ name: 'Jane Doe',
+ email: 'jane.doe-123@hotmail.com'
+ phone: '+11234567890'
+}])
+```
+
+The `subscriberId` is a required field with the ID of the subscriber in Novu. If a subscriber with a provided `subscriberId` does not exist yet in Novu, a new subscriber will be created before the trigger will be executed synchronously. You can find more information about subscribers [in the official Novu documentation][novu-subscribers].
+
+[novu]: https://novu.co/
+[novu-subscribers]: https://docs.novu.co/platform/subscribers
diff --git a/legacy/src/index.ts b/legacy/src/index.ts
index 33b3a843..5c853df9 100644
--- a/legacy/src/index.ts
+++ b/legacy/src/index.ts
@@ -6,4 +6,6 @@ export * from './human-feedback'
export * from './services/metaphor'
export * from './services/serpapi'
+export * from './services/novu'
export * from './tools/metaphor'
+export * from './tools/novu'
diff --git a/legacy/src/services/novu.ts b/legacy/src/services/novu.ts
new file mode 100644
index 00000000..e02c0746
--- /dev/null
+++ b/legacy/src/services/novu.ts
@@ -0,0 +1,61 @@
+import ky from 'ky'
+
+export type NovuSubscriber = {
+ subscriberId: string
+ email?: string
+ firstName?: string
+ lastName?: string
+ phone?: string
+}
+
+export type NovuTriggerEventResponse = {
+ data: {
+ acknowledged?: boolean
+ status?: string
+ transactionId?: string
+ }
+}
+
+export class NovuClient {
+ apiKey: string
+ baseUrl: string
+
+ constructor({
+ apiKey = process.env.NOVU_API_KEY,
+ baseUrl = 'https://api.novu.co/v1'
+ }: {
+ apiKey?: string
+ baseUrl?: string
+ } = {}) {
+ if (!apiKey) {
+ throw new Error(`Error NovuClient missing required "apiKey"`)
+ }
+ this.apiKey = apiKey
+ this.baseUrl = baseUrl
+ }
+
+ async triggerEvent({
+ name,
+ payload,
+ to
+ }: {
+ name: string
+ payload: Record
+ to: NovuSubscriber[]
+ }) {
+ const url = `${this.baseUrl}/events/trigger`
+ const headers = {
+ Authorization: `ApiKey ${this.apiKey}`,
+ 'Content-Type': 'application/json'
+ }
+ const response = await ky.post(url, {
+ headers,
+ json: {
+ name,
+ payload,
+ to
+ }
+ })
+ return response.json()
+ }
+}
diff --git a/legacy/src/tools/novu.ts b/legacy/src/tools/novu.ts
new file mode 100644
index 00000000..ff80eb3d
--- /dev/null
+++ b/legacy/src/tools/novu.ts
@@ -0,0 +1,78 @@
+import { z } from 'zod'
+
+import * as types from '../types'
+import { Agentic } from '../agentic'
+import { NovuClient } from '../services/novu'
+import { BaseTask } from '../task'
+
+export const NovuNotificationToolInputSchema = z.object({
+ name: z.string(),
+ payload: z.record(z.unknown()),
+ to: z.array(
+ z.object({
+ subscriberId: z.string(),
+ email: z.string().optional(),
+ firstName: z.string().optional(),
+ lastName: z.string().optional(),
+ phone: z.string().optional()
+ })
+ )
+})
+
+export type NovuNotificationToolInput = z.infer<
+ typeof NovuNotificationToolInputSchema
+>
+
+export const NovuNotificationToolOutputSchema = z.object({
+ data: z.object({
+ acknowledged: z.boolean().optional(),
+ status: z.string().optional(),
+ transactionId: z.string().optional()
+ })
+})
+
+export type NovuNotificationToolOutput = z.infer<
+ typeof NovuNotificationToolOutputSchema
+>
+
+export class NovuNotificationTool extends BaseTask<
+ typeof NovuNotificationToolInputSchema,
+ typeof NovuNotificationToolOutputSchema
+> {
+ _novuClient: NovuClient
+
+ constructor({
+ agentic,
+ novuClient = new NovuClient()
+ }: {
+ agentic: Agentic
+ novuClient?: NovuClient
+ }) {
+ super({
+ agentic
+ })
+
+ this._novuClient = novuClient
+ }
+
+ public override get inputSchema() {
+ return NovuNotificationToolInputSchema
+ }
+
+ public override get outputSchema() {
+ return NovuNotificationToolOutputSchema
+ }
+
+ protected override async _call(
+ input: NovuNotificationToolInput
+ ): Promise> {
+ // TODO: handle errors gracefully
+ input = this.inputSchema.parse(input)
+
+ const result = await this._novuClient.triggerEvent(input)
+ return {
+ result,
+ metadata: {}
+ }
+ }
+}
diff --git a/legacy/test/novu.test.ts b/legacy/test/novu.test.ts
new file mode 100644
index 00000000..5e48496b
--- /dev/null
+++ b/legacy/test/novu.test.ts
@@ -0,0 +1,27 @@
+import test from 'ava'
+
+import { NovuClient } from '../src/services/novu'
+import './_utils'
+
+test('NovuClient.triggerEvent', async (t) => {
+ if (!process.env.NOVU_API_KEY) {
+ return t.pass()
+ }
+
+ t.timeout(2 * 60 * 1000)
+ const client = new NovuClient()
+
+ const result = await client.triggerEvent({
+ name: 'send-email',
+ payload: {
+ content: 'Hello World!'
+ },
+ to: [
+ {
+ subscriberId: '123456',
+ email: 'pburckhardt@outlook.com'
+ }
+ ]
+ })
+ t.truthy(result)
+})