> ## Documentation Index
> Fetch the complete documentation index at: https://docs.novu.co/llms.txt
> Use this file to discover all available pages before exploring further.

# How to build a Mastra agent with Novu Connect

> Build a conversational agent with Mastra and Novu Connect. Connect Slack, Microsoft Teams, WhatsApp, Telegram, or email. Call Mastra generate() from onMessage and get your first reply.

Build a [Mastra](https://mastra.ai/) agent with Novu Connect. You call Mastra from a Novu `onMessage` handler and return the text. Novu delivers replies on Slack, Microsoft Teams, WhatsApp, Telegram, or email.

This walkthrough uses **Slack** for the first connection. The same handler works on other channels once you link them.

<Note>
  Novu does not ship a dedicated `@novu/framework/mastra` adapter yet. Use `@novu/framework`, call Mastra in `onMessage`, and return the text (or call `ctx.reply()`).
</Note>

## What do I need before I start?

* A [Novu account](https://dashboard.novu.co)
* Node.js 22+
* A Slack workspace where you can install apps (or another [supported channel](/agents/get-started/agents-and-providers))
* An `OPENAI_API_KEY` (or another model key [Mastra supports](https://mastra.ai/models))

## How do I get a working agent?

<Steps>
  <Step>
    ## Run the CLI

    In an empty folder:

    ```bash theme={null}
    npx novu connect
    ```

    When prompted:

    1. Sign in to Novu (or paste your secret key).
    2. Choose **Custom code** as the runtime (Mastra uses the base Framework path).
    3. Name your agent. Note the **identifier** the CLI prints. Your code must use the same value.
    4. You can skip LLM auth in the CLI. You will wire Mastra in the next steps after the scaffold.
    5. Choose **Slack** (or another channel).
  </Step>

  <Step>
    ## Connect Slack

    When the CLI asks for a Slack App Configuration Token:

    1. Open [Slack API apps](https://api.slack.com/apps).
    2. Under **App Configuration Tokens**, click **Generate Token**.
    3. Select your workspace, generate the token, and copy it (it starts with `xoxe.xoxp-`).
    4. Paste it into the CLI and finish the install / Allow flow in Slack.

    The token is used once to create the Slack app. Novu does not store it.

    For screenshots of this flow, see [Create a Slack app](/agents/custom-code-agent/quickstart#create-a-slack-app) in the custom code quickstart.
  </Step>

  <Step>
    ## Accept the scaffold, then wire Mastra

    Confirm creating a starter project when the CLI asks. It creates a Next.js app with a bridge route and a demo handler.

    Then:

    1. Install Mastra:

    ```bash theme={null}
    npm install @mastra/core
    ```

    2. Add your model key to `.env.local` (keep the `NOVU_SECRET_KEY` the CLI wrote):

    ```bash theme={null}
    OPENAI_API_KEY=sk-...
    ```

    3. Replace the demo handler with Mastra. Create `app/novu/agents/mastra-support.ts`:

    ```typescript theme={null}
    import { Agent } from '@mastra/core/agent';

    export const mastraSupport = new Agent({
      id: 'mastra-support',
      name: 'Support Agent',
      instructions: 'You are a helpful support agent. Keep answers short.',
      model: 'openai/gpt-4o',
    });
    ```

    4. Update your Novu agent file (use your real identifier) so it calls Mastra:

    ```typescript theme={null}
    import { agent } from '@novu/framework';
    import { mastraSupport } from './mastra-support';

    function toMastraMessages(history: { role: string; type: string; content: string }[]) {
      return history
        .filter((entry) => entry.type === 'message' && entry.content.trim())
        .map((entry) => ({
          role: entry.role === 'agent' || entry.role === 'assistant' ? 'assistant' : 'user',
          content: entry.content,
        }));
    }

    export const supportBot = agent('support-bot', {
      onMessage: async (_message, ctx) => {
        const result = await mastraSupport.generate(toMastraMessages(ctx.history));

        return result.text;
      },
    });
    ```

    5. Export that agent from `app/novu/agents/index.ts`.

    If you already have an app and skipped the scaffold, jump to [Wire an existing app](#wire-an-existing-app).
  </Step>

  <Step>
    ## Run the agent locally

    ```bash theme={null}
    npm run dev:novu
    ```

    This starts your app and tunnels it to Novu so channel messages reach your bridge. Keep the process running.

    If the script is missing:

    ```bash theme={null}
    npx novu dev --port 4000
    ```
  </Step>

  <Step>
    ## Send a message

    Message your bot in Slack (DM or @mention). You should get a reply from Mastra in the same thread.

    **Success looks like:** the tunnel is connected, and the reply is from your Mastra agent (not the demo scaffold text).

    If nothing comes back:

    * Confirm `npm run dev:novu` is still running.
    * Confirm the agent identifier in code matches the dashboard.
    * Confirm `OPENAI_API_KEY` is set.
    * Confirm Slack install completed and you are messaging the correct bot.
  </Step>
</Steps>

## Wire an existing app

Use this only if you skipped the CLI scaffold. Otherwise you can skip this section.

<AccordionGroup>
  <Accordion title="Install packages and env">
    ```bash theme={null}
    npm install @novu/framework @mastra/core
    ```

    Add to `.env.local`:

    ```bash theme={null}
    OPENAI_API_KEY=sk-...
    NOVU_SECRET_KEY=your-novu-secret-key
    ```

    Copy `NOVU_SECRET_KEY` from **API Keys** in the dashboard if needed.
  </Accordion>

  <Accordion title="Create the Mastra agent and Novu handler">
    Same code as [Accept the scaffold, then wire Mastra](#accept-the-scaffold-then-wire-mastra): a Mastra `Agent`, a small `toMastraMessages` helper, and a Novu `agent()` that returns `result.text`.
  </Accordion>

  <Accordion title="Add the bridge route">
    Create `app/api/novu/route.ts`:

    ```typescript theme={null}
    import { serve } from '@novu/framework/next';
    import { supportBot } from '../../novu/agents';

    export const { GET, POST, OPTIONS } = serve({
      agents: [supportBot],
    });
    ```

    Then run `npx novu dev` (or `npm run dev:novu`) and message the bot. For Express, Hono, and other servers, see [Connecting your app](/agents/custom-code-agent/connecting-your-app).
  </Accordion>

  <Accordion title="Add Mastra tools (optional)">
    ```typescript theme={null}
    import { Agent } from '@mastra/core/agent';
    import { createTool } from '@mastra/core/tools';
    import { z } from 'zod';

    const lookupOrder = createTool({
      id: 'lookup-order',
      description: 'Look up an order by ID',
      inputSchema: z.object({ orderId: z.string() }),
      execute: async ({ orderId }) => ({ orderId, status: 'shipped' }),
    });

    export const mastraSupport = new Agent({
      id: 'mastra-support',
      name: 'Support Agent',
      instructions: 'You are a helpful support agent. Use lookup-order when asked about an order.',
      model: 'openai/gpt-4o',
      tools: { lookupOrder },
    });
    ```

    For human-in-the-loop approval before a sensitive tool runs, use Novu's [tool approval](/agents/custom-code-agent/building-blocks/tool-approval) APIs (`ctx.toolApproval.request()`).
  </Accordion>
</AccordionGroup>

## Frequently asked questions

<AccordionGroup>
  <Accordion title="Which channels can I use with a Mastra agent?">
    Slack, Microsoft Teams, WhatsApp, Telegram, and email. One handler works for all of them. See [Agents and providers](/agents/get-started/agents-and-providers).
  </Accordion>

  <Accordion title="Does Novu have a Mastra adapter?">
    Not yet. Use `@novu/framework` and return text from `onMessage`. AI SDK and LangChain already have dedicated adapters if you prefer those stacks.
  </Accordion>

  <Accordion title="Why map ctx.history myself?">
    Adapters like `@novu/framework/ai-sdk` convert history for you. On the base Framework path, you map history into whatever format Mastra expects, then call `generate()`.
  </Accordion>

  <Accordion title="Can I stream tokens into the channel?">
    Yes, with care. Post an initial reply with `ctx.reply('')`, then update it as chunks arrive using [`ReplyHandle.edit()`](/agents/custom-code-agent/building-blocks/edit-sent-messages). The provider must support message edits.
  </Accordion>

  <Accordion title="Which CLI runtime should I pick?">
    Choose **Custom code**. That wires the base `@novu/framework` path Mastra uses.
  </Accordion>
</AccordionGroup>

## Set this up with an AI assistant

<Prompt description="Build a Mastra agent with Novu Connect" icon="plug" actions={["copy", "cursor"]}>
  # Build a Mastra agent with Novu Connect

  Follow [https://docs.novu.co/agents/get-started/mastra](https://docs.novu.co/agents/get-started/mastra)

  ## Goal

  Create a Novu custom code agent that replies on messaging channels using Mastra. Prefer the CLI happy path: `npx novu connect` with Custom code, Slack, scaffold, then install `@mastra/core` and replace the demo handler.

  ## Rules

  ALWAYS:

  * Match Novu `agent('id')` to the dashboard agent Identifier
  * Map only `type === 'message'` history entries into Mastra messages
  * Use Mastra model strings like `openai/gpt-4o`
  * Keep handlers channel-agnostic

  NEVER:

  * Hardcode API keys
  * Import `@novu/framework/ai-sdk` or `@novu/framework/langchain` for this path
</Prompt>

## Next steps

<Columns cols={2}>
  <Card icon="code" href="/agents/custom-code-agent/frameworks/other" title="Other frameworks">
    Patterns for any LLM stack without a Novu adapter.
  </Card>

  <Card icon="blocks" href="/agents/get-started/agents-and-providers" title="Agents and providers">
    Slack, Teams, WhatsApp, Telegram, and email capabilities.
  </Card>

  <Card icon="rocket" href="/agents/custom-code-agent/going-to-production" title="Going to production">
    Deploy the bridge and switch off local tunneling.
  </Card>

  <Card icon="sparkles" href="/agents/get-started/ai-sdk" title="AI SDK quickstart">
    Same flow with a first-class Novu adapter.
  </Card>
</Columns>
