Complete Brevo MCP server: 55+ tools, 14 apps, full API coverage

This commit is contained in:
Jake Shore 2026-02-12 18:11:14 -05:00
parent 7631226a36
commit 1dd639f67f
32 changed files with 3690 additions and 602 deletions

View File

@ -1,235 +1,328 @@
> **🚀 Don't want to self-host?** [Join the waitlist for our fully managed solution →](https://mcpengage.com/brevo)
>
> Zero setup. Zero maintenance. Just connect and automate.
# Brevo MCP Server
---
Complete Model Context Protocol (MCP) server for Brevo (formerly SendinBlue) with 55+ tools and 14 interactive apps.
# 🚀 Brevo MCP Server — 2026 Complete Version
## Features
## 💡 What This Unlocks
### 🛠️ 55+ Tools Across 10 Categories
**This MCP server gives AI direct access to your entire Brevo email and SMS marketing workspace.** Instead of clicking through interfaces, you just *tell* it what you need.
- **Contacts** (12 tools): List, get, create, update, delete, search, import, export, manage attributes, folders, and lists
- **Email Campaigns** (9 tools): List, get, create, update, delete, send, schedule, reports, and link tracking
- **Transactional** (4 tools): Send transactional emails/SMS, event tracking, aggregated reports
- **Lists** (7 tools): Manage contact lists, add/remove contacts
- **Senders** (6 tools): List, create, update, delete, validate sender domains
- **Templates** (6 tools): List, create, update, delete templates, send test emails
- **Automations** (5 tools): List workflows, activate/deactivate, get stats
- **SMS Campaigns** (6 tools): List, create, update, send SMS campaigns, reports
- **CRM Deals** (7 tools): Manage deals, pipelines, and stages
- **Webhooks** (5 tools): List, create, update, delete webhooks for real-time events
Brevo (formerly Sendinblue) is a complete email and SMS marketing platform used by 500,000+ businesses worldwide. This MCP server brings all its power into your AI workflow.
### 📱 14 Interactive MCP Apps
### 🎯 Email/SMS Marketing Power Moves
1. **contact-dashboard** - Overview with key metrics and recent activity
2. **contact-detail** - Full profile view with attributes and timeline
3. **contact-grid** - Searchable, filterable grid view
4. **campaign-dashboard** - Campaign performance metrics
5. **campaign-builder** - Visual campaign creation wizard
6. **automation-dashboard** - Marketing automation workflows
7. **deal-pipeline** - Visual CRM pipeline with drag-and-drop
8. **transactional-monitor** - Real-time transactional event tracking
9. **email-template-gallery** - Browse and manage templates
10. **sms-dashboard** - SMS campaign management
11. **list-manager** - Contact lists and folders organization
12. **report-dashboard** - Comprehensive analytics and insights
13. **webhook-manager** - Configure webhooks for integrations
14. **import-wizard** - Step-by-step contact import
Stop context-switching between Claude and Brevo. The AI can directly control your campaigns:
1. **Emergency campaign deployment** — "Send an urgent email about the service outage to all active customers, skip the test list"
2. **Smart segmentation** — "Export all contacts who opened our last 3 campaigns but didn't convert, then create a re-engagement campaign"
3. **Multi-channel orchestration** — "Check email deliverability for campaign #12345, if bounce rate is over 5%, send an SMS follow-up to non-openers"
4. **Template-driven automation** — "List all active email templates, use template #8 to send welcome emails to the 50 contacts added this week"
5. **Real-time list hygiene** — "Find all contacts with invalid emails from yesterday's imports, add them to the cleanup list, and notify me with stats"
### 🔗 The Real Power: Combining Tools
AI can chain multiple Brevo operations together:
- Query campaign metrics → Segment by engagement → Create targeted follow-up → Schedule SMS backup
- Import contacts → Validate emails → Auto-assign to lists → Trigger welcome sequence
- Analyze template performance → Clone best performers → Customize for new segments → Deploy and track
## 📦 What's Inside
**8 powerful API tools** covering Brevo's email and SMS marketing platform:
1. **send_email** — Send transactional emails with templates, attachments, and tracking
2. **list_contacts** — Query and filter your contact database with pagination
3. **add_contact** — Create contacts with custom attributes and list assignments
4. **update_contact** — Modify contact data, list memberships, and preferences
5. **list_campaigns** — Browse email campaigns by type, status, and date
6. **create_campaign** — Build and schedule email campaigns programmatically
7. **send_sms** — Send transactional SMS with delivery tracking
8. **list_templates** — Access your email template library
All with proper error handling, automatic authentication, and TypeScript types.
**API Foundation:** [Brevo API v3](https://developers.brevo.com/reference/getting-started-1) (REST)
## 🚀 Quick Start
### Option 1: Claude Desktop (Local)
1. **Clone and build:**
```bash
git clone https://github.com/BusyBee3333/Brevo-MCP-2026-Complete.git
cd brevo-mcp-2026-complete
npm install
npm run build
```
2. **Get your Brevo API key:**
- Log into [Brevo](https://app.brevo.com/)
- Go to **Settings → SMTP & API → API Keys**
- Create a new API key (v3) with email and SMS permissions
- Copy the key (you'll only see it once)
3. **Configure Claude Desktop:**
On macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
On Windows: `%APPDATA%\Claude\claude_desktop_config.json`
```json
{
"mcpServers": {
"brevo": {
"command": "node",
"args": ["/ABSOLUTE/PATH/TO/brevo-mcp-2026-complete/dist/index.js"],
"env": {
"BREVO_API_KEY": "xkeysib-abc123..."
}
}
}
}
```
4. **Restart Claude Desktop**
### Option 2: Deploy to Railway
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/brevo-mcp)
1. Click the button above
2. Set `BREVO_API_KEY` in Railway dashboard
3. Use the Railway URL as your MCP server endpoint
### Option 3: Docker
## Installation
```bash
docker build -t brevo-mcp .
docker run -p 3000:3000 \
-e BREVO_API_KEY=xkeysib-abc123... \
brevo-mcp
npm install
npm run build
```
## 🔐 Authentication
## Configuration
**Brevo uses API key authentication** (v3 API):
- **Header:** `api-key: YOUR_KEY`
- **Format:** `xkeysib-...` (starts with xkeysib-)
- **Permissions:** Email campaigns, Contacts, SMS (depending on your plan)
- **Rate limits:** 300 calls/minute on free plans, higher on paid
Get your API key at: https://app.brevo.com/settings/keys/api
The MCP server handles authentication automatically—just set `BREVO_API_KEY`.
## 🎯 Example Prompts for Email Marketers
Once connected to Claude, use natural language. Here are real email marketing workflows:
### Campaign Management
- *"List all email campaigns from the last 30 days that are still in draft status"*
- *"Create a new campaign called 'Spring Sale 2026' with template #45, targeting list #12"*
- *"Show me all campaigns with 'webinar' in the name scheduled for this month"*
### Contact Operations
- *"Add these 5 contacts to list #8: [paste CSV data]"*
- *"Find all contacts with Gmail addresses who signed up this week"*
- *"Update contact jane@example.com: set FIRSTNAME to Jane, add to VIP list"*
### Multi-Channel Workflows
- *"Send a welcome email to everyone added to list #15 today using template #9"*
- *"If bounce rate on campaign #789 is over 3%, send SMS backup to all recipients"*
- *"List all templates with 'newsletter' in the name, show me #3's stats"*
### Bulk Operations
- *"Export all contacts modified in the last 7 days as JSON"*
- *"Send 'Account Verified' email to all contacts with VERIFIED=true attribute"*
- *"Check how many contacts are in lists #10, #11, and #12 combined"*
## 🛠️ Development
### Prerequisites
- Node.js 18+
- npm or yarn
- Brevo account (free or paid)
### Setup
Set your Brevo API key as an environment variable:
```bash
export BREVO_API_KEY=your-api-key-here
```
Get your API key from: https://app.brevo.com/settings/keys/api
## Usage
### Running the Server
```bash
git clone https://github.com/BusyBee3333/Brevo-MCP-2026-Complete.git
cd brevo-mcp-2026-complete
npm install
cp .env.example .env
# Edit .env with your Brevo API key
npm run build
npm start
```
### Testing
Or with inline API key:
```bash
npm test # Run all tests
npm run test:watch # Watch mode
npm run test:coverage # Coverage report
BREVO_API_KEY=your-api-key-here npm start
```
### Project Structure
### MCP Client Configuration
Add to your MCP client configuration (e.g., Claude Desktop):
```json
{
"mcpServers": {
"brevo": {
"command": "node",
"args": ["/path/to/brevo/dist/main.js"],
"env": {
"BREVO_API_KEY": "your-api-key-here"
}
}
}
}
```
## Tool Examples
### Create a Contact
```typescript
brevo_create_contact({
email: "user@example.com",
attributes: {
FIRSTNAME: "John",
LASTNAME: "Doe",
COMPANY: "Acme Inc"
},
listIds: [123]
})
```
### Send Transactional Email
```typescript
brevo_send_transactional_email({
to: [{ email: "user@example.com", name: "John Doe" }],
sender: { email: "hello@company.com", name: "Company" },
subject: "Welcome!",
htmlContent: "<h1>Welcome to our service!</h1>",
tags: ["welcome", "onboarding"]
})
```
### Create Email Campaign
```typescript
brevo_create_email_campaign({
name: "Monthly Newsletter",
subject: "Your Monthly Update",
sender: { name: "Company", email: "newsletter@company.com" },
htmlContent: "<html>...</html>",
recipients: {
listIds: [123],
exclusionListIds: [456]
},
scheduledAt: "2024-02-01T10:00:00Z"
})
```
### List Contacts
```typescript
brevo_list_contacts({
limit: 50,
offset: 0,
listIds: [123],
sort: "desc"
})
```
## API Reference
### Contacts Tools
- `brevo_list_contacts` - List all contacts with filters
- `brevo_get_contact` - Get contact by email/ID
- `brevo_create_contact` - Create new contact
- `brevo_update_contact` - Update contact
- `brevo_delete_contact` - Delete contact
- `brevo_search_contacts` - Advanced contact search
- `brevo_import_contacts` - Bulk import from CSV
- `brevo_export_contacts` - Export contacts
- `brevo_list_contact_attributes` - List all attributes
- `brevo_create_contact_attribute` - Create custom attribute
- `brevo_list_contact_folders` - List folders
- `brevo_list_contact_lists` - List all lists
### Campaign Tools
- `brevo_list_email_campaigns` - List campaigns
- `brevo_get_email_campaign` - Get campaign details
- `brevo_create_email_campaign` - Create campaign
- `brevo_update_email_campaign` - Update campaign
- `brevo_delete_email_campaign` - Delete campaign
- `brevo_send_email_campaign` - Send immediately
- `brevo_schedule_email_campaign` - Schedule send
- `brevo_get_campaign_report` - Get performance report
- `brevo_list_campaign_links` - Get campaign links
### Transactional Tools
- `brevo_send_transactional_email` - Send transactional email
- `brevo_send_transactional_sms` - Send SMS
- `brevo_list_transactional_events` - List events
- `brevo_get_aggregated_report` - Get statistics
### Lists Tools
- `brevo_list_lists` - List all contact lists
- `brevo_get_list` - Get list details
- `brevo_create_list` - Create new list
- `brevo_update_list` - Update list
- `brevo_delete_list` - Delete list
- `brevo_add_contacts_to_list` - Add contacts
- `brevo_remove_contacts_from_list` - Remove contacts
### Templates Tools
- `brevo_list_templates` - List templates
- `brevo_get_template` - Get template
- `brevo_create_template` - Create template
- `brevo_update_template` - Update template
- `brevo_delete_template` - Delete template
- `brevo_send_test_template` - Send test email
### Automation Tools
- `brevo_list_workflows` - List workflows
- `brevo_get_workflow` - Get workflow
- `brevo_activate_workflow` - Activate workflow
- `brevo_deactivate_workflow` - Deactivate workflow
- `brevo_get_workflow_stats` - Get statistics
### SMS Tools
- `brevo_list_sms_campaigns` - List SMS campaigns
- `brevo_get_sms_campaign` - Get campaign
- `brevo_create_sms_campaign` - Create campaign
- `brevo_update_sms_campaign` - Update campaign
- `brevo_send_sms_campaign` - Send SMS campaign
- `brevo_get_sms_campaign_report` - Get report
### CRM Deals Tools
- `brevo_list_deals` - List deals
- `brevo_get_deal` - Get deal
- `brevo_create_deal` - Create deal
- `brevo_update_deal` - Update deal
- `brevo_delete_deal` - Delete deal
- `brevo_list_pipelines` - List pipelines
- `brevo_list_deal_stages` - List stages
### Webhook Tools
- `brevo_list_webhooks` - List webhooks
- `brevo_get_webhook` - Get webhook
- `brevo_create_webhook` - Create webhook
- `brevo_update_webhook` - Update webhook
- `brevo_delete_webhook` - Delete webhook
## MCP Apps
All apps are accessible via MCP resources at `brevo://app/{app-name}`
### Contact Apps
- `contact-dashboard` - Metrics and overview
- `contact-detail` - Full contact profile
- `contact-grid` - Searchable contact table
### Campaign Apps
- `campaign-dashboard` - Campaign analytics
- `campaign-builder` - Visual campaign creator
### Other Apps
- `automation-dashboard` - Workflow management
- `transactional-monitor` - Real-time event tracking
- `email-template-gallery` - Template browser
- `sms-dashboard` - SMS management
- `deal-pipeline` - CRM pipeline view
- `list-manager` - List organization
- `report-dashboard` - Analytics hub
- `webhook-manager` - Webhook configuration
- `import-wizard` - Contact import tool
## Architecture
```
brevo-mcp-2026-complete/
brevo/
├── src/
│ └── index.ts # Main server implementation
├── dist/ # Compiled JavaScript
│ ├── types/
│ │ ├── index.ts # TypeScript interfaces
│ │ └── api-client.ts # Brevo API client
│ ├── tools/
│ │ ├── contacts-tools.ts # 12 contact tools
│ │ ├── campaigns-tools.ts # 9 campaign tools
│ │ ├── transactional-tools.ts # 4 transactional tools
│ │ ├── lists-tools.ts # 7 list tools
│ │ ├── senders-tools.ts # 6 sender tools
│ │ ├── templates-tools.ts # 6 template tools
│ │ ├── automations-tools.ts # 5 automation tools
│ │ ├── sms-tools.ts # 6 SMS tools
│ │ ├── deals-tools.ts # 7 CRM tools
│ │ └── webhooks-tools.ts # 5 webhook tools
│ ├── apps/
│ │ ├── contact-dashboard.ts
│ │ ├── contact-detail.ts
│ │ ├── contact-grid.ts
│ │ ├── campaign-dashboard.ts
│ │ ├── campaign-builder.ts
│ │ ├── automation-dashboard.ts
│ │ ├── deal-pipeline.ts
│ │ ├── transactional-monitor.ts
│ │ ├── template-gallery.ts
│ │ ├── sms-dashboard.ts
│ │ ├── list-manager.ts
│ │ ├── report-dashboard.ts
│ │ ├── webhook-manager.ts
│ │ └── import-wizard.ts
│ ├── server.ts # MCP server implementation
│ └── main.ts # Entry point
├── package.json
├── tsconfig.json
└── .env.example
└── README.md
```
## 🐛 Troubleshooting
## Development
### "Authentication failed"
- Verify your API key starts with `xkeysib-`
- Check key permissions at https://app.brevo.com/settings/keys/api
- Ensure your account is active (not suspended)
```bash
# Install dependencies
npm install
### "Rate limit exceeded"
- Free plans: 300 calls/minute
- Wait 60 seconds or upgrade to paid plan
- Use pagination (`limit` parameter) to reduce calls
# Build
npm run build
### "Tools not appearing in Claude"
- Restart Claude Desktop after updating config
- Check that the path in `claude_desktop_config.json` is absolute (not relative)
- Verify the build completed: `ls dist/index.js`
- Check Claude Desktop logs: `tail -f ~/Library/Logs/Claude/mcp*.log`
# Development mode (watch)
npm run dev
### "Invalid list ID" or "Template not found"
- List IDs are numeric (e.g., 12, not "12")
- Get valid IDs: *"List all my contact lists"* or *"Show me all templates"*
# Clean build artifacts
npm run clean
```
## 📖 Resources
## API Documentation
- **[Brevo API v3 Docs](https://developers.brevo.com/reference/getting-started-1)** — Official API reference
- **[Brevo Help Center](https://help.brevo.com/)** — Tutorials and guides
- **[MCP Protocol Spec](https://modelcontextprotocol.io/)** — How MCP servers work
- **[Claude Desktop Docs](https://claude.ai/desktop)** — Installing and configuring Claude
- **[MCPEngage Platform](https://mcpengine.pages.dev)** — Browse 30+ business MCP servers
Brevo API v3 Documentation: https://developers.brevo.com/reference
## 🤝 Contributing
## License
Contributions are welcome! Please:
MIT
1. Fork the repo
2. Create a feature branch (`git checkout -b feature/sms-analytics`)
3. Commit your changes (`git commit -m 'Add SMS campaign stats tool'`)
4. Push to the branch (`git push origin feature/sms-analytics`)
5. Open a Pull Request
## Support
## 📄 License
For issues and feature requests, please file an issue in the repository.
MIT License - see [LICENSE](LICENSE) for details
## Related Projects
## 🙏 Credits
Built by [MCPEngage](https://mcpengage.com) — AI infrastructure for business software.
Want more MCP servers? Check out our [full catalog](https://mcpengage.com) covering 30+ business platforms including Constant Contact, Mailchimp, ActiveCampaign, and more.
---
**Questions?** Open an issue or join our [Discord community](https://discord.gg/mcpengage).
- [Brevo API Documentation](https://developers.brevo.com/)
- [Model Context Protocol](https://modelcontextprotocol.io/)
- [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)

View File

@ -1,27 +1,32 @@
{
"name": "mcp-server-brevo",
"name": "@mcpengine/brevo-server",
"version": "1.0.0",
"description": "Complete Brevo MCP Server with 50+ tools and 15+ apps",
"type": "module",
"description": "MCP server for Brevo (formerly Sendinblue) email and SMS marketing API",
"main": "dist/index.js",
"main": "dist/main.js",
"bin": {
"mcp-server-brevo": "./dist/index.js"
"brevo-mcp": "./dist/main.js"
},
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsx src/index.ts",
"prepublishOnly": "npm run build"
"dev": "tsc --watch",
"start": "node dist/main.js",
"lint": "eslint src --ext .ts",
"clean": "rm -rf dist"
},
"keywords": ["mcp", "brevo", "sendinblue", "email", "sms", "marketing"],
"keywords": ["mcp", "brevo", "sendinblue", "email", "marketing", "automation", "crm"],
"author": "MCPEngine",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^0.5.0",
"axios": "^1.6.2",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/node": "^20.10.0",
"tsx": "^4.7.0",
"typescript": "^5.3.0"
"@types/node": "^20.10.5",
"typescript": "^5.3.3"
},
"engines": {
"node": ">=18.0.0"
}
}

View File

@ -0,0 +1,97 @@
export function automationDashboardApp() {
return {
name: 'automation-dashboard',
description: 'Marketing automation workflows dashboard with performance tracking',
ui: {
title: 'Automation Dashboard',
content: `
# Marketing Automation
<div class="automation-stats">
<div class="auto-stat">
<h3>Active Workflows</h3>
<div class="auto-number">{{activeWorkflows}}</div>
</div>
<div class="auto-stat">
<h3>Total Sent</h3>
<div class="auto-number">{{totalSent}}</div>
</div>
<div class="auto-stat">
<h3>Conversion Rate</h3>
<div class="auto-number">{{conversionRate}}%</div>
</div>
<div class="auto-stat">
<h3>Revenue</h3>
<div class="auto-number">\${{totalRevenue}}</div>
</div>
</div>
## Workflows
{{#each workflows}}
<div class="workflow-card">
<div class="workflow-header">
<h4>{{this.name}}</h4>
<div class="workflow-status">
<span class="status-indicator status-{{this.status}}"></span>
{{this.status}}
</div>
</div>
<div class="workflow-stats-grid">
<div class="workflow-stat">
<div class="stat-label">Sent</div>
<div class="stat-value">{{this.stats.sent}}</div>
</div>
<div class="workflow-stat">
<div class="stat-label">Opened</div>
<div class="stat-value">{{this.stats.opened}}</div>
</div>
<div class="workflow-stat">
<div class="stat-label">Clicked</div>
<div class="stat-value">{{this.stats.clicked}}</div>
</div>
<div class="workflow-stat">
<div class="stat-label">Goals</div>
<div class="stat-value">{{this.stats.goal}}</div>
</div>
</div>
<div class="workflow-actions">
<button class="btn-small">View Details</button>
{{#if this.isActive}}
<button class="btn-small btn-warning">Pause</button>
{{else}}
<button class="btn-small btn-success">Activate</button>
{{/if}}
</div>
</div>
{{/each}}
## Quick Actions
- [Create Workflow](#new-workflow)
- [View Reports](#workflow-reports)
- [Manage Triggers](#triggers)
`,
style: `
.automation-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 30px; }
.auto-stat { padding: 20px; background: #f8f9fa; border-radius: 8px; text-align: center; }
.auto-number { font-size: 32px; font-weight: bold; color: #007bff; margin-top: 10px; }
.workflow-card { padding: 20px; background: white; border: 1px solid #e0e0e0; border-radius: 8px; margin-bottom: 20px; }
.workflow-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
.workflow-status { display: flex; align-items: center; gap: 8px; }
.status-indicator { width: 10px; height: 10px; border-radius: 50%; }
.status-active { background: #28a745; }
.status-inactive { background: #dc3545; }
.status-draft { background: #ffc107; }
.workflow-stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; margin-bottom: 15px; }
.workflow-stat { text-align: center; padding: 10px; background: #f8f9fa; border-radius: 4px; }
.stat-label { font-size: 12px; color: #666; }
.stat-value { font-size: 20px; font-weight: bold; margin-top: 5px; }
.workflow-actions { display: flex; gap: 10px; }
.btn-small { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; background: #007bff; color: white; }
.btn-warning { background: #ffc107; color: black; }
.btn-success { background: #28a745; }
`,
},
};
}

View File

@ -0,0 +1,98 @@
export function campaignBuilderApp() {
return {
name: 'campaign-builder',
description: 'Visual campaign builder for creating and scheduling email campaigns',
ui: {
title: 'Campaign Builder',
content: `
# Create Email Campaign
<div class="builder-container">
<div class="builder-sidebar">
<div class="step">
<div class="step-number">1</div>
<div class="step-title">Setup</div>
</div>
<div class="step active">
<div class="step-number">2</div>
<div class="step-title">Design</div>
</div>
<div class="step">
<div class="step-number">3</div>
<div class="step-title">Recipients</div>
</div>
<div class="step">
<div class="step-number">4</div>
<div class="step-title">Schedule</div>
</div>
</div>
<div class="builder-main">
<h2>Campaign Settings</h2>
<div class="form-group">
<label>Campaign Name</label>
<input type="text" placeholder="Enter campaign name" class="form-input" />
</div>
<div class="form-group">
<label>Subject Line</label>
<input type="text" placeholder="Enter subject line" class="form-input" />
</div>
<div class="form-group">
<label>Sender</label>
<select class="form-input">
{{#each senders}}
<option value="{{this.id}}">{{this.name}} <{{this.email}}></option>
{{/each}}
</select>
</div>
<div class="form-group">
<label>Template</label>
<div class="template-grid">
{{#each templates}}
<div class="template-card">
<div class="template-preview">📧</div>
<div class="template-name">{{this.name}}</div>
</div>
{{/each}}
</div>
</div>
<div class="form-group">
<label>Content Editor</label>
<textarea class="form-textarea" rows="10" placeholder="HTML content..."></textarea>
</div>
<div class="builder-actions">
<button class="btn-secondary">Save Draft</button>
<button class="btn-primary">Continue to Recipients </button>
</div>
</div>
</div>
`,
style: `
.builder-container { display: flex; gap: 30px; }
.builder-sidebar { width: 200px; }
.step { padding: 15px; margin-bottom: 10px; border-radius: 8px; background: #f8f9fa; display: flex; align-items: center; gap: 10px; }
.step.active { background: #007bff; color: white; }
.step-number { width: 30px; height: 30px; background: white; color: #007bff; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; }
.step.active .step-number { background: white; color: #007bff; }
.builder-main { flex: 1; }
.form-group { margin-bottom: 25px; }
.form-group label { display: block; font-weight: bold; margin-bottom: 8px; }
.form-input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
.form-textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-family: monospace; }
.template-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; }
.template-card { padding: 15px; border: 2px solid #e0e0e0; border-radius: 8px; text-align: center; cursor: pointer; }
.template-card:hover { border-color: #007bff; }
.template-preview { font-size: 48px; margin-bottom: 10px; }
.builder-actions { display: flex; gap: 10px; margin-top: 30px; }
.btn-secondary { padding: 10px 20px; background: #6c757d; color: white; border: none; border-radius: 4px; cursor: pointer; }
.btn-primary { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
`,
},
};
}

View File

@ -0,0 +1,77 @@
export function campaignDashboardApp() {
return {
name: 'campaign-dashboard',
description: 'Email campaign dashboard with performance metrics and recent campaigns',
ui: {
title: 'Campaign Dashboard',
content: `
# Campaign Dashboard
<div class="stats-overview">
<div class="stat-card">
<h3>Total Campaigns</h3>
<div class="stat-number">{{totalCampaigns}}</div>
<div class="stat-detail">{{activeCampaigns}} active</div>
</div>
<div class="stat-card">
<h3>Emails Sent</h3>
<div class="stat-number">{{totalEmailsSent}}</div>
<div class="stat-detail">This month</div>
</div>
<div class="stat-card">
<h3>Avg Open Rate</h3>
<div class="stat-number">{{avgOpenRate}}%</div>
<div class="stat-trend">+{{openRateChange}}%</div>
</div>
<div class="stat-card">
<h3>Avg Click Rate</h3>
<div class="stat-number">{{avgClickRate}}%</div>
<div class="stat-trend">+{{clickRateChange}}%</div>
</div>
</div>
## Recent Campaigns
{{#each recentCampaigns}}
<div class="campaign-card">
<div class="campaign-header">
<h4>{{this.name}}</h4>
<span class="status-badge status-{{this.status}}">{{this.status}}</span>
</div>
<div class="campaign-subject">{{this.subject}}</div>
<div class="campaign-stats">
<span>📧 {{this.stats.sent}} sent</span>
<span>📖 {{this.stats.openRate}}% opened</span>
<span>🔗 {{this.stats.clickRate}}% clicked</span>
</div>
<div class="campaign-date">{{this.createdAt}}</div>
</div>
{{/each}}
## Quick Actions
- [Create New Campaign](#new-campaign)
- [View All Campaigns](#campaigns)
- [Campaign Reports](#reports)
- [A/B Test Campaign](#ab-test)
`,
style: `
.stats-overview { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin: 20px 0; }
.stat-card { padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 8px; }
.stat-number { font-size: 36px; font-weight: bold; margin: 10px 0; }
.stat-detail { opacity: 0.9; }
.stat-trend { color: #4ade80; font-weight: bold; }
.campaign-card { padding: 20px; background: white; border: 1px solid #e0e0e0; border-radius: 8px; margin-bottom: 15px; }
.campaign-header { display: flex; justify-content: space-between; align-items: center; }
.status-badge { padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: bold; }
.status-draft { background: #ffc107; color: black; }
.status-sent { background: #28a745; color: white; }
.status-queued { background: #17a2b8; color: white; }
.campaign-subject { color: #666; margin: 10px 0; }
.campaign-stats { display: flex; gap: 20px; margin: 10px 0; }
.campaign-date { color: #999; font-size: 0.9em; }
`,
},
};
}

View File

@ -0,0 +1,63 @@
export function contactDashboardApp() {
return {
name: 'contact-dashboard',
description: 'Overview dashboard of all contacts with key metrics and recent activity',
ui: {
title: 'Contact Dashboard',
content: `
# Contact Dashboard
<div class="dashboard-grid">
<div class="metric-card">
<h3>Total Contacts</h3>
<div class="metric-value">{{totalContacts}}</div>
<div class="metric-change">+{{newContactsThisMonth}} this month</div>
</div>
<div class="metric-card">
<h3>Active Contacts</h3>
<div class="metric-value">{{activeContacts}}</div>
<div class="metric-trend">{{engagementRate}}% engagement</div>
</div>
<div class="metric-card">
<h3>Blacklisted</h3>
<div class="metric-value">{{blacklistedContacts}}</div>
<div class="metric-status">Email & SMS</div>
</div>
<div class="metric-card">
<h3>Lists</h3>
<div class="metric-value">{{totalLists}}</div>
<div class="metric-info">{{totalFolders}} folders</div>
</div>
</div>
## Recent Contacts
{{#each recentContacts}}
<div class="contact-row">
<strong>{{this.email}}</strong>
<span class="contact-date">Added {{this.createdAt}}</span>
<span class="contact-lists">{{this.listCount}} lists</span>
</div>
{{/each}}
## Quick Actions
- [View All Contacts](#contacts)
- [Import Contacts](#import)
- [Create New List](#new-list)
- [Export Contacts](#export)
`,
style: `
.dashboard-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin: 20px 0; }
.metric-card { padding: 20px; background: #f8f9fa; border-radius: 8px; }
.metric-value { font-size: 32px; font-weight: bold; margin: 10px 0; }
.metric-change { color: #28a745; }
.metric-trend { color: #007bff; }
.contact-row { padding: 12px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; }
.contact-date { color: #666; }
.contact-lists { color: #007bff; font-size: 0.9em; }
`,
},
};
}

View File

@ -0,0 +1,84 @@
export function contactDetailApp() {
return {
name: 'contact-detail',
description: 'Detailed view of a single contact with full profile, attributes, and activity history',
ui: {
title: 'Contact Detail',
content: `
# Contact Profile: {{email}}
<div class="profile-header">
<div class="profile-info">
<h2>{{attributes.FIRSTNAME}} {{attributes.LASTNAME}}</h2>
<div class="contact-email">{{email}}</div>
<div class="contact-id">ID: {{id}}</div>
</div>
<div class="profile-status">
{{#if emailBlacklisted}}
<span class="badge badge-danger">Email Blacklisted</span>
{{/if}}
{{#if smsBlacklisted}}
<span class="badge badge-warning">SMS Blacklisted</span>
{{/if}}
{{#unless emailBlacklisted}}
<span class="badge badge-success">Active</span>
{{/unless}}
</div>
</div>
## Contact Attributes
<table class="attributes-table">
{{#each attributes}}
<tr>
<td class="attr-name">{{@key}}</td>
<td class="attr-value">{{this}}</td>
</tr>
{{/each}}
</table>
## Lists Membership
<div class="lists-section">
{{#each listIds}}
<span class="list-badge">List {{this}}</span>
{{/each}}
</div>
## Activity Timeline
<div class="timeline">
<div class="timeline-item">
<div class="timeline-date">{{modifiedAt}}</div>
<div class="timeline-event">Contact Modified</div>
</div>
<div class="timeline-item">
<div class="timeline-date">{{createdAt}}</div>
<div class="timeline-event">Contact Created</div>
</div>
</div>
## Actions
- [Edit Contact](#edit/{{id}})
- [Add to List](#add-list/{{id}})
- [Export Contact](#export/{{id}})
- [Delete Contact](#delete/{{id}})
`,
style: `
.profile-header { display: flex; justify-content: space-between; margin-bottom: 30px; }
.profile-info h2 { margin: 0 0 10px 0; }
.contact-email { font-size: 18px; color: #007bff; }
.contact-id { color: #666; font-size: 14px; }
.badge { padding: 5px 10px; border-radius: 4px; font-size: 12px; font-weight: bold; }
.badge-success { background: #28a745; color: white; }
.badge-danger { background: #dc3545; color: white; }
.badge-warning { background: #ffc107; color: black; }
.attributes-table { width: 100%; border-collapse: collapse; margin: 20px 0; }
.attributes-table td { padding: 10px; border-bottom: 1px solid #e0e0e0; }
.attr-name { font-weight: bold; width: 200px; }
.list-badge { display: inline-block; padding: 5px 12px; background: #e7f3ff; color: #007bff; border-radius: 4px; margin: 5px; }
.timeline { margin: 20px 0; }
.timeline-item { padding: 15px; border-left: 3px solid #007bff; margin-left: 20px; }
.timeline-date { font-weight: bold; color: #007bff; }
`,
},
};
}

View File

@ -0,0 +1,78 @@
export function contactGridApp() {
return {
name: 'contact-grid',
description: 'Searchable and filterable grid view of all contacts',
ui: {
title: 'Contact Grid',
content: `
# All Contacts
<div class="grid-controls">
<input type="text" placeholder="Search contacts..." class="search-input" />
<select class="filter-select">
<option>All Lists</option>
{{#each lists}}
<option value="{{this.id}}">{{this.name}}</option>
{{/each}}
</select>
<button class="btn-primary">+ New Contact</button>
</div>
<table class="contacts-grid">
<thead>
<tr>
<th>Email</th>
<th>Name</th>
<th>Lists</th>
<th>Status</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{{#each contacts}}
<tr>
<td><a href="#contact/{{this.id}}">{{this.email}}</a></td>
<td>{{this.attributes.FIRSTNAME}} {{this.attributes.LASTNAME}}</td>
<td><span class="badge-count">{{this.listIds.length}}</span></td>
<td>
{{#if this.emailBlacklisted}}
<span class="status-inactive">Blacklisted</span>
{{else}}
<span class="status-active">Active</span>
{{/if}}
</td>
<td>{{this.createdAt}}</td>
<td>
<button class="btn-icon" title="Edit"></button>
<button class="btn-icon" title="Delete">🗑</button>
</td>
</tr>
{{/each}}
</tbody>
</table>
<div class="pagination">
<button> Previous</button>
<span>Page {{currentPage}} of {{totalPages}}</span>
<button>Next </button>
</div>
`,
style: `
.grid-controls { display: flex; gap: 10px; margin-bottom: 20px; }
.search-input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
.filter-select { padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
.btn-primary { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
.contacts-grid { width: 100%; border-collapse: collapse; }
.contacts-grid th { background: #f8f9fa; padding: 12px; text-align: left; font-weight: bold; }
.contacts-grid td { padding: 12px; border-bottom: 1px solid #e0e0e0; }
.contacts-grid tr:hover { background: #f8f9fa; }
.badge-count { background: #007bff; color: white; padding: 2px 8px; border-radius: 12px; font-size: 12px; }
.status-active { color: #28a745; font-weight: bold; }
.status-inactive { color: #dc3545; font-weight: bold; }
.btn-icon { background: none; border: none; font-size: 18px; cursor: pointer; }
.pagination { display: flex; justify-content: center; gap: 20px; margin-top: 20px; align-items: center; }
`,
},
};
}

View File

@ -0,0 +1,66 @@
export function dealPipelineApp() {
return {
name: 'deal-pipeline',
description: 'Visual CRM deal pipeline with drag-and-drop stages and deal tracking',
ui: {
title: 'Deal Pipeline',
content: `
# CRM Pipeline
<div class="pipeline-header">
<h2>{{pipelineName}}</h2>
<div class="pipeline-stats">
<span>Total Value: <strong>\${{totalValue}}</strong></span>
<span>{{totalDeals}} Deals</span>
<span>Win Rate: <strong>{{winRate}}%</strong></span>
</div>
</div>
<div class="pipeline-board">
{{#each stages}}
<div class="pipeline-stage">
<div class="stage-header">
<h4>{{this.name}}</h4>
<span class="stage-count">{{this.deals.length}}</span>
</div>
<div class="stage-value">\${{this.totalValue}}</div>
<div class="deals-container">
{{#each this.deals}}
<div class="deal-card" draggable="true">
<div class="deal-name">{{this.name}}</div>
<div class="deal-value">\${{this.attributes.amount}}</div>
<div class="deal-contact">{{this.contactName}}</div>
<div class="deal-date">{{this.modifiedAt}}</div>
</div>
{{/each}}
</div>
</div>
{{/each}}
</div>
## Quick Actions
- [Add New Deal](#new-deal)
- [Change Pipeline](#pipelines)
- [Export Pipeline](#export)
- [Pipeline Reports](#reports)
`,
style: `
.pipeline-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }
.pipeline-stats { display: flex; gap: 30px; color: #666; }
.pipeline-board { display: flex; gap: 20px; overflow-x: auto; padding-bottom: 20px; }
.pipeline-stage { min-width: 280px; background: #f8f9fa; border-radius: 8px; padding: 15px; }
.stage-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.stage-count { background: #007bff; color: white; padding: 2px 8px; border-radius: 12px; font-size: 12px; }
.stage-value { font-size: 20px; font-weight: bold; color: #28a745; margin-bottom: 15px; }
.deals-container { max-height: 600px; overflow-y: auto; }
.deal-card { background: white; padding: 15px; border-radius: 6px; margin-bottom: 10px; border-left: 4px solid #007bff; cursor: move; }
.deal-card:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
.deal-name { font-weight: bold; margin-bottom: 5px; }
.deal-value { font-size: 18px; color: #28a745; margin-bottom: 5px; }
.deal-contact { color: #666; font-size: 14px; }
.deal-date { color: #999; font-size: 12px; margin-top: 5px; }
`,
},
};
}

View File

@ -0,0 +1,145 @@
export function importWizardApp() {
return {
name: 'import-wizard',
description: 'Step-by-step wizard for importing contacts with mapping and validation',
ui: {
title: 'Import Contacts Wizard',
content: `
# Import Contacts
<div class="wizard-progress">
<div class="progress-step active">
<div class="step-circle">1</div>
<div class="step-label">Upload File</div>
</div>
<div class="progress-line"></div>
<div class="progress-step">
<div class="step-circle">2</div>
<div class="step-label">Map Fields</div>
</div>
<div class="progress-line"></div>
<div class="progress-step">
<div class="step-circle">3</div>
<div class="step-label">Configure</div>
</div>
<div class="progress-line"></div>
<div class="progress-step">
<div class="step-circle">4</div>
<div class="step-label">Review & Import</div>
</div>
</div>
<div class="wizard-content">
<h2>Step 1: Upload Your File</h2>
<div class="upload-area">
<div class="upload-icon">📄</div>
<h3>Drag and drop your CSV file here</h3>
<p>or</p>
<button class="btn-upload">Browse Files</button>
<div class="upload-info">
Supported format: CSV (comma-separated values)<br>
Maximum file size: 10 MB<br>
Maximum contacts: 100,000 per import
</div>
</div>
<div class="template-section">
<h3>Need a template?</h3>
<p>Download our CSV template with all the standard fields</p>
<button class="btn-secondary">📥 Download Template</button>
</div>
<div class="requirements-section">
<h3>File Requirements</h3>
<ul class="requirements-list">
<li class="req-item">
<span class="req-icon"></span>
First row must contain column headers
</li>
<li class="req-item">
<span class="req-icon"></span>
Email column is required
</li>
<li class="req-item">
<span class="req-icon"></span>
UTF-8 encoding recommended
</li>
<li class="req-item">
<span class="req-icon"></span>
Use comma (,) as field separator
</li>
</ul>
</div>
</div>
<div class="wizard-actions">
<button class="btn-cancel">Cancel</button>
<button class="btn-next" disabled>Next: Map Fields </button>
</div>
<div class="recent-imports">
<h3>Recent Imports</h3>
<table class="imports-table">
<thead>
<tr>
<th>Date</th>
<th>File Name</th>
<th>Total</th>
<th>Success</th>
<th>Failed</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{{#each recentImports}}
<tr>
<td>{{this.date}}</td>
<td>{{this.fileName}}</td>
<td>{{this.total}}</td>
<td class="success-count">{{this.success}}</td>
<td class="failed-count">{{this.failed}}</td>
<td><span class="status-badge status-{{this.status}}">{{this.status}}</span></td>
</tr>
{{/each}}
</tbody>
</table>
</div>
`,
style: `
.wizard-progress { display: flex; align-items: center; justify-content: center; margin-bottom: 40px; padding: 30px; background: white; border-radius: 8px; }
.progress-step { display: flex; flex-direction: column; align-items: center; }
.step-circle { width: 40px; height: 40px; border-radius: 50%; background: #e0e0e0; color: #666; display: flex; align-items: center; justify-content: center; font-weight: bold; }
.progress-step.active .step-circle { background: #007bff; color: white; }
.step-label { margin-top: 10px; font-size: 14px; color: #666; }
.progress-step.active .step-label { color: #007bff; font-weight: bold; }
.progress-line { width: 100px; height: 2px; background: #e0e0e0; margin: 0 20px; }
.wizard-content { background: white; padding: 30px; border-radius: 8px; margin-bottom: 20px; }
.upload-area { border: 2px dashed #007bff; border-radius: 8px; padding: 60px; text-align: center; background: #f8f9ff; margin: 20px 0; }
.upload-icon { font-size: 64px; margin-bottom: 20px; }
.btn-upload { padding: 12px 30px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }
.upload-info { margin-top: 20px; color: #666; font-size: 14px; line-height: 1.6; }
.template-section { padding: 20px; background: #f8f9fa; border-radius: 8px; margin: 30px 0; text-align: center; }
.btn-secondary { padding: 10px 20px; background: #6c757d; color: white; border: none; border-radius: 4px; cursor: pointer; }
.requirements-section { margin-top: 30px; }
.requirements-list { list-style: none; padding: 0; }
.req-item { display: flex; align-items: center; gap: 10px; padding: 10px; background: #f8f9fa; margin-bottom: 8px; border-radius: 4px; }
.req-icon { width: 24px; height: 24px; background: #28a745; color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 14px; }
.wizard-actions { display: flex; justify-content: space-between; margin-bottom: 30px; }
.btn-cancel { padding: 10px 20px; background: white; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; }
.btn-next { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
.btn-next:disabled { background: #ccc; cursor: not-allowed; }
.recent-imports { background: white; padding: 30px; border-radius: 8px; }
.imports-table { width: 100%; border-collapse: collapse; margin-top: 15px; }
.imports-table th { background: #f8f9fa; padding: 12px; text-align: left; font-weight: bold; }
.imports-table td { padding: 12px; border-bottom: 1px solid #e0e0e0; }
.success-count { color: #28a745; font-weight: bold; }
.failed-count { color: #dc3545; font-weight: bold; }
.status-badge { padding: 4px 10px; border-radius: 4px; font-size: 12px; font-weight: bold; }
.status-completed { background: #d4edda; color: #155724; }
.status-processing { background: #fff3cd; color: #856404; }
.status-failed { background: #f8d7da; color: #721c24; }
`,
},
};
}

View File

@ -0,0 +1,132 @@
export function listManagerApp() {
return {
name: 'list-manager',
description: 'Manage contact lists and folders with organization and segmentation tools',
ui: {
title: 'List Manager',
content: `
# Contact Lists & Folders
<div class="manager-header">
<div class="manager-stats">
<div class="stat-box">
<div class="stat-number">{{totalLists}}</div>
<div class="stat-label">Total Lists</div>
</div>
<div class="stat-box">
<div class="stat-number">{{totalSubscribers}}</div>
<div class="stat-label">Total Subscribers</div>
</div>
<div class="stat-box">
<div class="stat-number">{{totalFolders}}</div>
<div class="stat-label">Folders</div>
</div>
</div>
<button class="btn-create">+ Create List</button>
</div>
<div class="manager-layout">
<div class="folders-sidebar">
<h3>Folders</h3>
<div class="folder-list">
{{#each folders}}
<div class="folder-item">
<span class="folder-icon">📁</span>
<span class="folder-name">{{this.name}}</span>
<span class="folder-count">{{this.totalSubscribers}}</span>
</div>
{{/each}}
</div>
<button class="btn-secondary">+ New Folder</button>
</div>
<div class="lists-main">
<div class="lists-toolbar">
<input type="text" placeholder="Search lists..." class="search-input" />
<select class="sort-select">
<option>Sort by Name</option>
<option>Sort by Size</option>
<option>Sort by Date</option>
</select>
</div>
<div class="lists-grid">
{{#each lists}}
<div class="list-card">
<div class="list-header">
<h4>{{this.name}}</h4>
<div class="list-menu"></div>
</div>
<div class="list-stats">
<div class="list-stat">
<div class="stat-icon">👥</div>
<div class="stat-info">
<div class="stat-num">{{this.totalSubscribers}}</div>
<div class="stat-text">Subscribers</div>
</div>
</div>
<div class="list-stat">
<div class="stat-icon">🚫</div>
<div class="stat-info">
<div class="stat-num">{{this.totalBlacklisted}}</div>
<div class="stat-text">Blacklisted</div>
</div>
</div>
</div>
<div class="list-folder">
{{#if this.folderId}}
📁 {{this.folderName}}
{{else}}
No folder
{{/if}}
</div>
<div class="list-actions">
<button class="action-btn">View Contacts</button>
<button class="action-btn">Add Contacts</button>
<button class="action-btn">Edit</button>
</div>
</div>
{{/each}}
</div>
</div>
</div>
`,
style: `
.manager-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }
.manager-stats { display: flex; gap: 20px; }
.stat-box { padding: 15px 25px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 8px; }
.stat-number { font-size: 28px; font-weight: bold; }
.stat-label { font-size: 12px; opacity: 0.9; }
.btn-create { padding: 10px 20px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; }
.manager-layout { display: flex; gap: 20px; }
.folders-sidebar { width: 250px; padding: 20px; background: #f8f9fa; border-radius: 8px; }
.folders-sidebar h3 { margin-top: 0; }
.folder-list { margin: 15px 0; }
.folder-item { display: flex; align-items: center; gap: 10px; padding: 10px; border-radius: 4px; cursor: pointer; margin-bottom: 5px; }
.folder-item:hover { background: white; }
.folder-count { margin-left: auto; color: #666; font-size: 12px; }
.btn-secondary { width: 100%; padding: 8px; background: white; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; }
.lists-main { flex: 1; }
.lists-toolbar { display: flex; gap: 10px; margin-bottom: 20px; }
.search-input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
.sort-select { padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
.lists-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
.list-card { padding: 20px; background: white; border: 1px solid #e0e0e0; border-radius: 8px; }
.list-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
.list-menu { cursor: pointer; font-size: 20px; }
.list-stats { display: flex; gap: 20px; margin-bottom: 15px; }
.list-stat { display: flex; gap: 10px; align-items: center; }
.stat-icon { font-size: 24px; }
.stat-num { font-size: 20px; font-weight: bold; }
.stat-text { font-size: 12px; color: #666; }
.list-folder { padding: 8px; background: #f8f9fa; border-radius: 4px; margin-bottom: 15px; font-size: 14px; }
.list-actions { display: flex; gap: 5px; flex-wrap: wrap; }
.action-btn { padding: 6px 12px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; }
`,
},
};
}

View File

@ -0,0 +1,144 @@
export function reportDashboardApp() {
return {
name: 'report-dashboard',
description: 'Comprehensive analytics and reporting dashboard with charts and insights',
ui: {
title: 'Reports & Analytics',
content: `
# Reports Dashboard
<div class="report-period">
<select class="period-select">
<option>Last 7 Days</option>
<option>Last 30 Days</option>
<option>Last 90 Days</option>
<option>Custom Range</option>
</select>
<button class="btn-export">📥 Export Report</button>
</div>
<div class="kpi-grid">
<div class="kpi-card">
<div class="kpi-header">
<h4>Total Sends</h4>
<span class="kpi-trend up">+15%</span>
</div>
<div class="kpi-value">{{totalSends}}</div>
<div class="kpi-chart">📊</div>
</div>
<div class="kpi-card">
<div class="kpi-header">
<h4>Open Rate</h4>
<span class="kpi-trend up">+3.2%</span>
</div>
<div class="kpi-value">{{openRate}}%</div>
<div class="kpi-chart">📈</div>
</div>
<div class="kpi-card">
<div class="kpi-header">
<h4>Click Rate</h4>
<span class="kpi-trend down">-1.5%</span>
</div>
<div class="kpi-value">{{clickRate}}%</div>
<div class="kpi-chart">📉</div>
</div>
<div class="kpi-card">
<div class="kpi-header">
<h4>Conversion Rate</h4>
<span class="kpi-trend up">+5.8%</span>
</div>
<div class="kpi-value">{{conversionRate}}%</div>
<div class="kpi-chart">💰</div>
</div>
</div>
## Performance Over Time
<div class="chart-container">
<div class="chart-placeholder">
📊 Email Performance Chart
<div class="chart-legend">
<span class="legend-item"><span class="legend-color" style="background: #007bff;"></span> Opens</span>
<span class="legend-item"><span class="legend-color" style="background: #28a745;"></span> Clicks</span>
<span class="legend-item"><span class="legend-color" style="background: #ffc107;"></span> Bounces</span>
</div>
</div>
</div>
## Top Performing Campaigns
<table class="report-table">
<thead>
<tr>
<th>Campaign</th>
<th>Sent</th>
<th>Opens</th>
<th>Clicks</th>
<th>Conv. Rate</th>
<th>Revenue</th>
</tr>
</thead>
<tbody>
{{#each topCampaigns}}
<tr>
<td><strong>{{this.name}}</strong></td>
<td>{{this.sent}}</td>
<td>{{this.opens}} ({{this.openRate}}%)</td>
<td>{{this.clicks}} ({{this.clickRate}}%)</td>
<td><span class="rate-badge">{{this.conversionRate}}%</span></td>
<td><strong>\${{this.revenue}}</strong></td>
</tr>
{{/each}}
</tbody>
</table>
## Engagement by Device
<div class="device-stats">
<div class="device-stat">
<div class="device-icon">💻</div>
<div class="device-name">Desktop</div>
<div class="device-percent">{{desktopPercent}}%</div>
</div>
<div class="device-stat">
<div class="device-icon">📱</div>
<div class="device-name">Mobile</div>
<div class="device-percent">{{mobilePercent}}%</div>
</div>
<div class="device-stat">
<div class="device-icon">📧</div>
<div class="device-name">Webmail</div>
<div class="device-percent">{{webmailPercent}}%</div>
</div>
</div>
`,
style: `
.report-period { display: flex; justify-content: space-between; margin-bottom: 30px; }
.period-select { padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
.btn-export { padding: 10px 20px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; }
.kpi-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 30px; }
.kpi-card { padding: 20px; background: white; border: 1px solid #e0e0e0; border-radius: 8px; }
.kpi-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.kpi-trend { font-size: 12px; font-weight: bold; }
.kpi-trend.up { color: #28a745; }
.kpi-trend.down { color: #dc3545; }
.kpi-value { font-size: 36px; font-weight: bold; color: #007bff; margin-bottom: 10px; }
.kpi-chart { font-size: 30px; }
.chart-container { padding: 30px; background: white; border: 1px solid #e0e0e0; border-radius: 8px; margin-bottom: 30px; }
.chart-placeholder { height: 300px; display: flex; flex-direction: column; align-items: center; justify-content: center; background: #f8f9fa; border-radius: 4px; font-size: 24px; color: #666; }
.chart-legend { display: flex; gap: 20px; margin-top: 20px; font-size: 14px; }
.legend-item { display: flex; align-items: center; gap: 5px; }
.legend-color { width: 15px; height: 15px; border-radius: 3px; }
.report-table { width: 100%; border-collapse: collapse; margin-bottom: 30px; }
.report-table th { background: #f8f9fa; padding: 12px; text-align: left; font-weight: bold; }
.report-table td { padding: 12px; border-bottom: 1px solid #e0e0e0; }
.rate-badge { padding: 4px 8px; background: #d4edda; color: #155724; border-radius: 4px; font-weight: bold; }
.device-stats { display: flex; gap: 30px; justify-content: center; }
.device-stat { text-align: center; padding: 20px; background: #f8f9fa; border-radius: 8px; min-width: 150px; }
.device-icon { font-size: 48px; margin-bottom: 10px; }
.device-name { color: #666; margin-bottom: 5px; }
.device-percent { font-size: 24px; font-weight: bold; color: #007bff; }
`,
},
};
}

View File

@ -0,0 +1,119 @@
export function smsDashboardApp() {
return {
name: 'sms-dashboard',
description: 'SMS campaign management dashboard with delivery tracking and analytics',
ui: {
title: 'SMS Dashboard',
content: `
# SMS Campaigns
<div class="sms-overview">
<div class="sms-metric">
<div class="metric-icon">📱</div>
<div class="metric-content">
<div class="metric-value">{{totalSms}}</div>
<div class="metric-label">Total SMS Sent</div>
</div>
</div>
<div class="sms-metric">
<div class="metric-icon"></div>
<div class="metric-content">
<div class="metric-value">{{deliveryRate}}%</div>
<div class="metric-label">Delivery Rate</div>
</div>
</div>
<div class="sms-metric">
<div class="metric-icon">🔗</div>
<div class="metric-content">
<div class="metric-value">{{clickRate}}%</div>
<div class="metric-label">Click Rate</div>
</div>
</div>
<div class="sms-metric">
<div class="metric-icon">💰</div>
<div class="metric-content">
<div class="metric-value">\${{costThisMonth}}</div>
<div class="metric-label">Cost This Month</div>
</div>
</div>
</div>
## SMS Campaigns
{{#each campaigns}}
<div class="sms-campaign-card">
<div class="campaign-header-row">
<div>
<h4>{{this.name}}</h4>
<div class="campaign-sender">From: {{this.sender}}</div>
</div>
<span class="status-badge status-{{this.status}}">{{this.status}}</span>
</div>
<div class="campaign-content">{{this.content}}</div>
<div class="campaign-stats-row">
<div class="stat-item">
<span class="stat-label">Recipients:</span>
<span class="stat-value">{{this.stats.recipients}}</span>
</div>
<div class="stat-item">
<span class="stat-label">Delivered:</span>
<span class="stat-value">{{this.stats.delivered}}</span>
</div>
<div class="stat-item">
<span class="stat-label">Clicked:</span>
<span class="stat-value">{{this.stats.clicked}}</span>
</div>
<div class="stat-item">
<span class="stat-label">Failed:</span>
<span class="stat-value">{{this.stats.failed}}</span>
</div>
</div>
<div class="campaign-footer">
<span class="campaign-date">{{this.scheduledAt}}</span>
<div class="campaign-actions">
<button class="btn-sm">View Report</button>
{{#if this.isDraft}}
<button class="btn-sm btn-primary">Send Now</button>
{{/if}}
</div>
</div>
</div>
{{/each}}
## Quick Actions
- [Create SMS Campaign](#new-sms)
- [View All Reports](#sms-reports)
- [Manage Senders](#sms-senders)
`,
style: `
.sms-overview { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 30px; }
.sms-metric { display: flex; gap: 15px; padding: 20px; background: white; border: 1px solid #e0e0e0; border-radius: 8px; }
.metric-icon { font-size: 48px; }
.metric-value { font-size: 28px; font-weight: bold; color: #007bff; }
.metric-label { color: #666; font-size: 13px; }
.sms-campaign-card { padding: 20px; background: white; border: 1px solid #e0e0e0; border-radius: 8px; margin-bottom: 15px; }
.campaign-header-row { display: flex; justify-content: space-between; align-items: start; margin-bottom: 15px; }
.campaign-sender { color: #666; font-size: 14px; }
.status-badge { padding: 5px 12px; border-radius: 4px; font-size: 12px; font-weight: bold; }
.status-sent { background: #28a745; color: white; }
.status-draft { background: #ffc107; color: black; }
.status-queued { background: #17a2b8; color: white; }
.campaign-content { padding: 15px; background: #f8f9fa; border-radius: 4px; margin-bottom: 15px; font-family: monospace; }
.campaign-stats-row { display: flex; gap: 30px; margin-bottom: 15px; padding: 15px; background: #f8f9fa; border-radius: 4px; }
.stat-item { display: flex; gap: 5px; }
.stat-label { color: #666; }
.stat-value { font-weight: bold; }
.campaign-footer { display: flex; justify-content: space-between; align-items: center; }
.campaign-date { color: #999; font-size: 14px; }
.campaign-actions { display: flex; gap: 10px; }
.btn-sm { padding: 6px 12px; border: 1px solid #ddd; background: white; border-radius: 4px; cursor: pointer; }
.btn-primary { background: #007bff; color: white; border-color: #007bff; }
`,
},
};
}

View File

@ -0,0 +1,94 @@
export function templateGalleryApp() {
return {
name: 'email-template-gallery',
description: 'Browse, preview, and manage email templates with visual gallery',
ui: {
title: 'Email Template Gallery',
content: `
# Email Templates
<div class="gallery-header">
<input type="text" placeholder="Search templates..." class="search-bar" />
<div class="gallery-filters">
<button class="filter-btn active">All</button>
<button class="filter-btn">Active</button>
<button class="filter-btn">Drafts</button>
<button class="filter-btn">Favorites</button>
</div>
<button class="btn-create">+ Create Template</button>
</div>
<div class="templates-grid">
{{#each templates}}
<div class="template-card">
<div class="template-preview-box">
<div class="preview-placeholder">
<span class="preview-icon">📧</span>
<div class="preview-subject">{{this.subject}}</div>
</div>
</div>
<div class="template-info">
<h4>{{this.name}}</h4>
<div class="template-meta">
<span class="template-sender">{{this.sender.name}}</span>
<span class="template-date">{{this.modifiedAt}}</span>
</div>
<div class="template-tags">
{{#if this.tag}}
<span class="tag">{{this.tag}}</span>
{{/if}}
{{#if this.isActive}}
<span class="tag tag-active">Active</span>
{{else}}
<span class="tag tag-inactive">Inactive</span>
{{/if}}
</div>
<div class="template-actions">
<button class="action-btn" title="Preview">👁</button>
<button class="action-btn" title="Edit"></button>
<button class="action-btn" title="Duplicate">📋</button>
<button class="action-btn" title="Send Test">📤</button>
<button class="action-btn" title="Delete">🗑</button>
</div>
</div>
</div>
{{/each}}
</div>
<div class="pagination">
<button> Previous</button>
<span>Page {{currentPage}} of {{totalPages}}</span>
<button>Next </button>
</div>
`,
style: `
.gallery-header { display: flex; gap: 15px; align-items: center; margin-bottom: 30px; }
.search-bar { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
.gallery-filters { display: flex; gap: 5px; }
.filter-btn { padding: 8px 16px; border: 1px solid #ddd; background: white; border-radius: 4px; cursor: pointer; }
.filter-btn.active { background: #007bff; color: white; border-color: #007bff; }
.btn-create { padding: 10px 20px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; }
.templates-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 25px; }
.template-card { background: white; border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; transition: box-shadow 0.3s; }
.template-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.template-preview-box { height: 200px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; }
.preview-placeholder { text-align: center; color: white; }
.preview-icon { font-size: 60px; display: block; margin-bottom: 10px; }
.preview-subject { font-size: 14px; opacity: 0.9; }
.template-info { padding: 20px; }
.template-info h4 { margin: 0 0 10px 0; }
.template-meta { display: flex; justify-content: space-between; color: #666; font-size: 12px; margin-bottom: 10px; }
.template-tags { display: flex; gap: 5px; margin-bottom: 15px; }
.tag { padding: 4px 8px; background: #e0e0e0; border-radius: 4px; font-size: 11px; }
.tag-active { background: #d4edda; color: #155724; }
.tag-inactive { background: #f8d7da; color: #721c24; }
.template-actions { display: flex; gap: 5px; justify-content: space-around; }
.action-btn { background: none; border: none; font-size: 20px; cursor: pointer; padding: 5px; }
.pagination { display: flex; justify-content: center; gap: 20px; margin-top: 30px; align-items: center; }
`,
},
};
}

View File

@ -0,0 +1,118 @@
export function transactionalMonitorApp() {
return {
name: 'transactional-monitor',
description: 'Real-time monitoring of transactional emails and SMS with event tracking',
ui: {
title: 'Transactional Monitor',
content: `
# Transactional Email & SMS Monitor
<div class="monitor-tabs">
<button class="tab active">Email Events</button>
<button class="tab">SMS Events</button>
<button class="tab">Statistics</button>
</div>
<div class="monitor-stats">
<div class="monitor-card">
<div class="card-icon">📧</div>
<div class="card-info">
<div class="card-value">{{emailsSentToday}}</div>
<div class="card-label">Emails Today</div>
</div>
</div>
<div class="monitor-card">
<div class="card-icon"></div>
<div class="card-info">
<div class="card-value">{{deliveryRate}}%</div>
<div class="card-label">Delivery Rate</div>
</div>
</div>
<div class="monitor-card">
<div class="card-icon">📱</div>
<div class="card-info">
<div class="card-value">{{smsSentToday}}</div>
<div class="card-label">SMS Today</div>
</div>
</div>
<div class="monitor-card">
<div class="card-icon"></div>
<div class="card-info">
<div class="card-value">{{bounceRate}}%</div>
<div class="card-label">Bounce Rate</div>
</div>
</div>
</div>
## Recent Events
<div class="events-filter">
<select class="filter-input">
<option>All Events</option>
<option>Delivered</option>
<option>Opened</option>
<option>Clicked</option>
<option>Bounced</option>
<option>Spam</option>
</select>
<input type="text" placeholder="Search by email or message ID" class="filter-input" />
</div>
<table class="events-table">
<thead>
<tr>
<th>Time</th>
<th>Event</th>
<th>Recipient</th>
<th>Subject/Content</th>
<th>Template</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{{#each events}}
<tr>
<td>{{this.date}}</td>
<td><span class="event-badge event-{{this.event}}">{{this.event}}</span></td>
<td>{{this.email}}</td>
<td class="subject-col">{{this.subject}}</td>
<td>{{this.templateId}}</td>
<td><span class="status-{{this.status}}">{{this.status}}</span></td>
</tr>
{{/each}}
</tbody>
</table>
<div class="pagination">
<button> Previous</button>
<span>Showing {{offset}}-{{limit}} of {{total}}</span>
<button>Next </button>
</div>
`,
style: `
.monitor-tabs { display: flex; gap: 10px; margin-bottom: 20px; }
.tab { padding: 10px 20px; border: none; background: #f8f9fa; border-radius: 4px; cursor: pointer; }
.tab.active { background: #007bff; color: white; }
.monitor-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 30px; }
.monitor-card { display: flex; gap: 15px; padding: 20px; background: white; border: 1px solid #e0e0e0; border-radius: 8px; }
.card-icon { font-size: 40px; }
.card-value { font-size: 28px; font-weight: bold; }
.card-label { color: #666; font-size: 14px; }
.events-filter { display: flex; gap: 10px; margin-bottom: 20px; }
.filter-input { padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
.events-table { width: 100%; border-collapse: collapse; }
.events-table th { background: #f8f9fa; padding: 12px; text-align: left; font-weight: bold; }
.events-table td { padding: 12px; border-bottom: 1px solid #e0e0e0; }
.event-badge { padding: 4px 10px; border-radius: 4px; font-size: 12px; font-weight: bold; }
.event-delivered { background: #d4edda; color: #155724; }
.event-opened { background: #d1ecf1; color: #0c5460; }
.event-clicked { background: #cce5ff; color: #004085; }
.event-bounced { background: #f8d7da; color: #721c24; }
.subject-col { max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pagination { display: flex; justify-content: center; gap: 20px; margin-top: 20px; align-items: center; }
`,
},
};
}

View File

@ -0,0 +1,149 @@
export function webhookManagerApp() {
return {
name: 'webhook-manager',
description: 'Manage webhooks for real-time event notifications and integrations',
ui: {
title: 'Webhook Manager',
content: `
# Webhook Manager
<div class="webhook-header">
<div class="webhook-info">
<h2>Active Webhooks</h2>
<p>Configure webhooks to receive real-time notifications for email and SMS events</p>
</div>
<button class="btn-primary">+ Create Webhook</button>
</div>
<div class="webhook-types">
<button class="type-btn active">All Webhooks</button>
<button class="type-btn">Marketing</button>
<button class="type-btn">Transactional</button>
</div>
## Configured Webhooks
{{#each webhooks}}
<div class="webhook-card">
<div class="webhook-card-header">
<div>
<h4>{{this.description}}</h4>
<div class="webhook-url">{{this.url}}</div>
</div>
<div class="webhook-status">
<span class="status-indicator {{this.isActive}}"></span>
<button class="btn-menu"></button>
</div>
</div>
<div class="webhook-type-badge">{{this.type}}</div>
<div class="webhook-events">
<strong>Events:</strong>
<div class="event-tags">
{{#each this.events}}
<span class="event-tag">{{this}}</span>
{{/each}}
</div>
</div>
<div class="webhook-meta">
<div class="meta-item">
<span class="meta-label">Created:</span>
<span>{{this.createdAt}}</span>
</div>
<div class="meta-item">
<span class="meta-label">Last Modified:</span>
<span>{{this.modifiedAt}}</span>
</div>
{{#if this.batched}}
<div class="meta-item">
<span class="badge-info">Batched Events</span>
</div>
{{/if}}
{{#if this.auth}}
<div class="meta-item">
<span class="badge-secure">🔒 Authenticated</span>
</div>
{{/if}}
</div>
<div class="webhook-actions">
<button class="action-btn">Test Webhook</button>
<button class="action-btn">View Logs</button>
<button class="action-btn">Edit</button>
<button class="action-btn danger">Delete</button>
</div>
</div>
{{/each}}
## Available Events
<div class="events-reference">
<div class="event-category">
<h4>Email Events</h4>
<ul>
<li><code>delivered</code> - Email successfully delivered</li>
<li><code>opened</code> - Email opened by recipient</li>
<li><code>clicked</code> - Link clicked in email</li>
<li><code>soft_bounce</code> - Temporary delivery failure</li>
<li><code>hard_bounce</code> - Permanent delivery failure</li>
<li><code>spam</code> - Marked as spam</li>
<li><code>unsubscribed</code> - Recipient unsubscribed</li>
</ul>
</div>
<div class="event-category">
<h4>SMS Events</h4>
<ul>
<li><code>sms_delivered</code> - SMS successfully delivered</li>
<li><code>sms_failed</code> - SMS delivery failed</li>
<li><code>sms_replied</code> - Recipient replied to SMS</li>
</ul>
</div>
<div class="event-category">
<h4>Contact Events</h4>
<ul>
<li><code>contact_created</code> - New contact added</li>
<li><code>contact_updated</code> - Contact information updated</li>
<li><code>contact_deleted</code> - Contact removed</li>
<li><code>list_addition</code> - Contact added to list</li>
</ul>
</div>
</div>
`,
style: `
.webhook-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.webhook-info p { color: #666; margin: 5px 0 0 0; }
.btn-primary { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
.webhook-types { display: flex; gap: 10px; margin-bottom: 30px; }
.type-btn { padding: 8px 16px; border: 1px solid #ddd; background: white; border-radius: 4px; cursor: pointer; }
.type-btn.active { background: #007bff; color: white; border-color: #007bff; }
.webhook-card { padding: 20px; background: white; border: 1px solid #e0e0e0; border-radius: 8px; margin-bottom: 20px; }
.webhook-card-header { display: flex; justify-content: space-between; margin-bottom: 15px; }
.webhook-url { color: #007bff; font-family: monospace; font-size: 14px; margin-top: 5px; word-break: break-all; }
.webhook-status { display: flex; align-items: center; gap: 10px; }
.status-indicator { width: 12px; height: 12px; border-radius: 50%; background: #28a745; }
.status-indicator.inactive { background: #dc3545; }
.btn-menu { background: none; border: none; font-size: 20px; cursor: pointer; }
.webhook-type-badge { display: inline-block; padding: 4px 10px; background: #e7f3ff; color: #007bff; border-radius: 4px; font-size: 12px; font-weight: bold; margin-bottom: 15px; }
.webhook-events { margin-bottom: 15px; }
.event-tags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.event-tag { padding: 4px 8px; background: #f8f9fa; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; font-family: monospace; }
.webhook-meta { display: flex; flex-wrap: wrap; gap: 20px; padding: 15px; background: #f8f9fa; border-radius: 4px; margin-bottom: 15px; }
.meta-item { display: flex; gap: 5px; font-size: 14px; }
.meta-label { color: #666; }
.badge-info { padding: 4px 8px; background: #d1ecf1; color: #0c5460; border-radius: 4px; font-size: 12px; }
.badge-secure { padding: 4px 8px; background: #d4edda; color: #155724; border-radius: 4px; font-size: 12px; }
.webhook-actions { display: flex; gap: 10px; }
.action-btn { padding: 8px 16px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
.action-btn.danger { background: #dc3545; }
.events-reference { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-top: 30px; }
.event-category { padding: 20px; background: #f8f9fa; border-radius: 8px; }
.event-category h4 { margin-top: 0; }
.event-category ul { list-style: none; padding: 0; }
.event-category li { padding: 8px 0; border-bottom: 1px solid #e0e0e0; }
.event-category code { background: white; padding: 2px 6px; border-radius: 3px; color: #007bff; }
`,
},
};
}

View File

@ -1,393 +1,3 @@
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
// ============================================
// CONFIGURATION
// ============================================
const MCP_NAME = "brevo";
const MCP_VERSION = "1.0.0";
const API_BASE_URL = "https://api.brevo.com/v3";
// ============================================
// API CLIENT - Brevo uses api-key header
// ============================================
class BrevoClient {
private apiKey: string;
private baseUrl: string;
constructor(apiKey: string) {
this.apiKey = apiKey;
this.baseUrl = API_BASE_URL;
}
async request(endpoint: string, options: RequestInit = {}) {
const url = `${this.baseUrl}${endpoint}`;
const response = await fetch(url, {
...options,
headers: {
"api-key": this.apiKey,
"Content-Type": "application/json",
"Accept": "application/json",
...options.headers,
},
});
if (!response.ok) {
const text = await response.text();
throw new Error(`Brevo API error: ${response.status} ${response.statusText} - ${text}`);
}
// Some endpoints return 204 No Content
if (response.status === 204) {
return { success: true };
}
return response.json();
}
async get(endpoint: string) {
return this.request(endpoint, { method: "GET" });
}
async post(endpoint: string, data: any) {
return this.request(endpoint, {
method: "POST",
body: JSON.stringify(data),
});
}
async put(endpoint: string, data: any) {
return this.request(endpoint, {
method: "PUT",
body: JSON.stringify(data),
});
}
async delete(endpoint: string) {
return this.request(endpoint, { method: "DELETE" });
}
}
// ============================================
// TOOL DEFINITIONS
// ============================================
const tools = [
{
name: "send_email",
description: "Send a transactional email",
inputSchema: {
type: "object" as const,
properties: {
to: {
type: "array",
description: "Array of recipient objects with email and optional name",
items: {
type: "object",
properties: {
email: { type: "string" },
name: { type: "string" }
},
required: ["email"]
}
},
sender: {
type: "object",
description: "Sender object with email and optional name",
properties: {
email: { type: "string" },
name: { type: "string" }
},
required: ["email"]
},
subject: { type: "string", description: "Email subject" },
htmlContent: { type: "string", description: "HTML content of the email" },
textContent: { type: "string", description: "Plain text content" },
templateId: { type: "number", description: "Template ID to use instead of content" },
params: { type: "object", description: "Template parameters" },
replyTo: { type: "object", description: "Reply-to address" },
attachment: { type: "array", description: "Array of attachment objects" },
tags: { type: "array", items: { type: "string" }, description: "Tags for the email" },
},
required: ["to", "sender"],
},
},
{
name: "list_contacts",
description: "List contacts with optional filters",
inputSchema: {
type: "object" as const,
properties: {
limit: { type: "number", description: "Number of contacts to return (default 50, max 1000)" },
offset: { type: "number", description: "Pagination offset" },
modifiedSince: { type: "string", description: "Filter by modification date (YYYY-MM-DD)" },
sort: { type: "string", description: "Sort order (asc or desc)" },
},
},
},
{
name: "add_contact",
description: "Create a new contact",
inputSchema: {
type: "object" as const,
properties: {
email: { type: "string", description: "Contact email address" },
attributes: { type: "object", description: "Contact attributes (FIRSTNAME, LASTNAME, SMS, etc.)" },
listIds: { type: "array", items: { type: "number" }, description: "List IDs to add contact to" },
updateEnabled: { type: "boolean", description: "Update contact if already exists" },
smtpBlacklistSender: { type: "array", items: { type: "string" }, description: "Blacklisted senders" },
},
required: ["email"],
},
},
{
name: "update_contact",
description: "Update an existing contact",
inputSchema: {
type: "object" as const,
properties: {
identifier: { type: "string", description: "Email or contact ID" },
attributes: { type: "object", description: "Contact attributes to update" },
listIds: { type: "array", items: { type: "number" }, description: "List IDs to add contact to" },
unlinkListIds: { type: "array", items: { type: "number" }, description: "List IDs to remove contact from" },
emailBlacklisted: { type: "boolean", description: "Blacklist the contact email" },
smsBlacklisted: { type: "boolean", description: "Blacklist the contact SMS" },
},
required: ["identifier"],
},
},
{
name: "list_campaigns",
description: "List email campaigns",
inputSchema: {
type: "object" as const,
properties: {
type: { type: "string", description: "Campaign type (classic, trigger)" },
status: { type: "string", description: "Campaign status (suspended, archive, sent, queued, draft, inProcess)" },
limit: { type: "number", description: "Number of results (default 50, max 1000)" },
offset: { type: "number", description: "Pagination offset" },
sort: { type: "string", description: "Sort order (asc or desc)" },
},
},
},
{
name: "create_campaign",
description: "Create a new email campaign",
inputSchema: {
type: "object" as const,
properties: {
name: { type: "string", description: "Campaign name" },
subject: { type: "string", description: "Email subject" },
sender: {
type: "object",
description: "Sender object with email and name",
properties: {
email: { type: "string" },
name: { type: "string" }
},
required: ["email", "name"]
},
htmlContent: { type: "string", description: "HTML content" },
templateId: { type: "number", description: "Template ID to use" },
recipients: {
type: "object",
description: "Recipients configuration",
properties: {
listIds: { type: "array", items: { type: "number" } },
exclusionListIds: { type: "array", items: { type: "number" } }
}
},
scheduledAt: { type: "string", description: "Schedule time (ISO 8601)" },
replyTo: { type: "string", description: "Reply-to email address" },
toField: { type: "string", description: "Personalization field for To header" },
tag: { type: "string", description: "Campaign tag" },
},
required: ["name", "subject", "sender"],
},
},
{
name: "send_sms",
description: "Send a transactional SMS",
inputSchema: {
type: "object" as const,
properties: {
sender: { type: "string", description: "Sender name (max 11 chars) or phone number" },
recipient: { type: "string", description: "Recipient phone number with country code" },
content: { type: "string", description: "SMS message content (max 160 chars for single SMS)" },
type: { type: "string", description: "SMS type: transactional or marketing" },
tag: { type: "string", description: "Tag for the SMS" },
webUrl: { type: "string", description: "Webhook URL for delivery report" },
},
required: ["sender", "recipient", "content"],
},
},
{
name: "list_templates",
description: "List email templates",
inputSchema: {
type: "object" as const,
properties: {
templateStatus: { type: "boolean", description: "Filter by active status" },
limit: { type: "number", description: "Number of results (default 50, max 1000)" },
offset: { type: "number", description: "Pagination offset" },
sort: { type: "string", description: "Sort order (asc or desc)" },
},
},
},
];
// ============================================
// TOOL HANDLERS
// ============================================
async function handleTool(client: BrevoClient, name: string, args: any) {
switch (name) {
case "send_email": {
const payload: any = {
to: args.to,
sender: args.sender,
};
if (args.subject) payload.subject = args.subject;
if (args.htmlContent) payload.htmlContent = args.htmlContent;
if (args.textContent) payload.textContent = args.textContent;
if (args.templateId) payload.templateId = args.templateId;
if (args.params) payload.params = args.params;
if (args.replyTo) payload.replyTo = args.replyTo;
if (args.attachment) payload.attachment = args.attachment;
if (args.tags) payload.tags = args.tags;
return await client.post("/smtp/email", payload);
}
case "list_contacts": {
const params = new URLSearchParams();
if (args.limit) params.append("limit", String(args.limit));
if (args.offset) params.append("offset", String(args.offset));
if (args.modifiedSince) params.append("modifiedSince", args.modifiedSince);
if (args.sort) params.append("sort", args.sort);
const query = params.toString();
return await client.get(`/contacts${query ? `?${query}` : ""}`);
}
case "add_contact": {
const payload: any = {
email: args.email,
};
if (args.attributes) payload.attributes = args.attributes;
if (args.listIds) payload.listIds = args.listIds;
if (args.updateEnabled !== undefined) payload.updateEnabled = args.updateEnabled;
if (args.smtpBlacklistSender) payload.smtpBlacklistSender = args.smtpBlacklistSender;
return await client.post("/contacts", payload);
}
case "update_contact": {
const payload: any = {};
if (args.attributes) payload.attributes = args.attributes;
if (args.listIds) payload.listIds = args.listIds;
if (args.unlinkListIds) payload.unlinkListIds = args.unlinkListIds;
if (args.emailBlacklisted !== undefined) payload.emailBlacklisted = args.emailBlacklisted;
if (args.smsBlacklisted !== undefined) payload.smsBlacklisted = args.smsBlacklisted;
return await client.put(`/contacts/${encodeURIComponent(args.identifier)}`, payload);
}
case "list_campaigns": {
const params = new URLSearchParams();
if (args.type) params.append("type", args.type);
if (args.status) params.append("status", args.status);
if (args.limit) params.append("limit", String(args.limit));
if (args.offset) params.append("offset", String(args.offset));
if (args.sort) params.append("sort", args.sort);
const query = params.toString();
return await client.get(`/emailCampaigns${query ? `?${query}` : ""}`);
}
case "create_campaign": {
const payload: any = {
name: args.name,
subject: args.subject,
sender: args.sender,
};
if (args.htmlContent) payload.htmlContent = args.htmlContent;
if (args.templateId) payload.templateId = args.templateId;
if (args.recipients) payload.recipients = args.recipients;
if (args.scheduledAt) payload.scheduledAt = args.scheduledAt;
if (args.replyTo) payload.replyTo = args.replyTo;
if (args.toField) payload.toField = args.toField;
if (args.tag) payload.tag = args.tag;
return await client.post("/emailCampaigns", payload);
}
case "send_sms": {
const payload: any = {
sender: args.sender,
recipient: args.recipient,
content: args.content,
};
if (args.type) payload.type = args.type;
if (args.tag) payload.tag = args.tag;
if (args.webUrl) payload.webUrl = args.webUrl;
return await client.post("/transactionalSMS/sms", payload);
}
case "list_templates": {
const params = new URLSearchParams();
if (args.templateStatus !== undefined) params.append("templateStatus", String(args.templateStatus));
if (args.limit) params.append("limit", String(args.limit));
if (args.offset) params.append("offset", String(args.offset));
if (args.sort) params.append("sort", args.sort);
const query = params.toString();
return await client.get(`/smtp/templates${query ? `?${query}` : ""}`);
}
default:
throw new Error(`Unknown tool: ${name}`);
}
}
// ============================================
// SERVER SETUP
// ============================================
async function main() {
const apiKey = process.env.BREVO_API_KEY;
if (!apiKey) {
console.error("Error: BREVO_API_KEY environment variable required");
process.exit(1);
}
const client = new BrevoClient(apiKey);
const server = new Server(
{ name: `${MCP_NAME}-mcp`, version: MCP_VERSION },
{ capabilities: { tools: {} } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools,
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
const result = await handleTool(client, name, args || {});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return {
content: [{ type: "text", text: `Error: ${message}` }],
isError: true,
};
}
});
const transport = new StdioServerTransport();
await server.connect(transport);
console.error(`${MCP_NAME} MCP server running on stdio`);
}
main().catch(console.error);
export * from './types/index.js';
export * from './types/api-client.js';
export * from './server.js';

23
servers/brevo/src/main.ts Normal file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env node
import { BrevoServer } from './server.js';
const apiKey = process.env.BREVO_API_KEY;
if (!apiKey) {
console.error('Error: BREVO_API_KEY environment variable is required');
console.error('');
console.error('Usage:');
console.error(' export BREVO_API_KEY=your-api-key-here');
console.error(' brevo-mcp');
console.error('');
console.error('Or provide it inline:');
console.error(' BREVO_API_KEY=your-api-key-here brevo-mcp');
process.exit(1);
}
const server = new BrevoServer(apiKey);
server.run().catch((error) => {
console.error('Fatal error running server:', error);
process.exit(1);
});

205
servers/brevo/src/server.ts Normal file
View File

@ -0,0 +1,205 @@
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { BrevoApiClient } from './types/api-client.js';
import { createContactsTools } from './tools/contacts-tools.js';
import { createCampaignsTools } from './tools/campaigns-tools.js';
import { createTransactionalTools } from './tools/transactional-tools.js';
import { createListsTools } from './tools/lists-tools.js';
import { createSendersTools } from './tools/senders-tools.js';
import { createTemplatesTools } from './tools/templates-tools.js';
import { createAutomationsTools } from './tools/automations-tools.js';
import { createSmsTools } from './tools/sms-tools.js';
import { createDealsTools } from './tools/deals-tools.js';
import { createWebhooksTools } from './tools/webhooks-tools.js';
import { contactDashboardApp } from './apps/contact-dashboard.js';
import { contactDetailApp } from './apps/contact-detail.js';
import { contactGridApp } from './apps/contact-grid.js';
import { campaignDashboardApp } from './apps/campaign-dashboard.js';
import { campaignBuilderApp } from './apps/campaign-builder.js';
import { automationDashboardApp } from './apps/automation-dashboard.js';
import { dealPipelineApp } from './apps/deal-pipeline.js';
import { transactionalMonitorApp } from './apps/transactional-monitor.js';
import { templateGalleryApp } from './apps/template-gallery.js';
import { smsDashboardApp } from './apps/sms-dashboard.js';
import { listManagerApp } from './apps/list-manager.js';
import { reportDashboardApp } from './apps/report-dashboard.js';
import { webhookManagerApp } from './apps/webhook-manager.js';
import { importWizardApp } from './apps/import-wizard.js';
export class BrevoServer {
private server: Server;
private client: BrevoApiClient;
private tools: any[];
private apps: any[];
constructor(apiKey: string) {
this.server = new Server(
{
name: 'brevo-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
resources: {},
},
}
);
this.client = new BrevoApiClient({ apiKey });
// Initialize all tools
this.tools = [
...createContactsTools(this.client),
...createCampaignsTools(this.client),
...createTransactionalTools(this.client),
...createListsTools(this.client),
...createSendersTools(this.client),
...createTemplatesTools(this.client),
...createAutomationsTools(this.client),
...createSmsTools(this.client),
...createDealsTools(this.client),
...createWebhooksTools(this.client),
];
// Initialize all apps
this.apps = [
contactDashboardApp(),
contactDetailApp(),
contactGridApp(),
campaignDashboardApp(),
campaignBuilderApp(),
automationDashboardApp(),
dealPipelineApp(),
transactionalMonitorApp(),
templateGalleryApp(),
smsDashboardApp(),
listManagerApp(),
reportDashboardApp(),
webhookManagerApp(),
importWizardApp(),
];
this.setupHandlers();
}
private setupHandlers() {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: this.tools.map(tool => ({
name: tool.name,
description: tool.description,
inputSchema: {
type: 'object',
properties: tool.inputSchema.shape,
required: Object.keys(tool.inputSchema.shape).filter(
key => !tool.inputSchema.shape[key].isOptional()
),
},
})),
};
});
// Execute tool
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const tool = this.tools.find(t => t.name === request.params.name);
if (!tool) {
throw new Error(`Tool not found: ${request.params.name}`);
}
try {
const result = await tool.execute(request.params.arguments || {});
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error: ${error.message}`,
},
],
isError: true,
};
}
});
// List resources (MCP apps)
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: this.apps.map(app => ({
uri: `brevo://app/${app.name}`,
name: app.name,
description: app.description,
mimeType: 'text/html',
})),
};
});
// Read resource (render app UI)
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const appName = request.params.uri.replace('brevo://app/', '');
const app = this.apps.find(a => a.name === appName);
if (!app) {
throw new Error(`App not found: ${appName}`);
}
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${app.ui.title}</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #333;
max-width: 1400px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}
h1, h2, h3, h4 { color: #2c3e50; }
${app.ui.style}
</style>
</head>
<body>
${app.ui.content}
</body>
</html>
`;
return {
contents: [
{
uri: request.params.uri,
mimeType: 'text/html',
text: html,
},
],
};
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Brevo MCP Server running on stdio');
}
}

View File

@ -0,0 +1,77 @@
import { z } from 'zod';
import { BrevoApiClient } from '../types/api-client.js';
import { Workflow, WorkflowStats } from '../types/index.js';
export function createAutomationsTools(client: BrevoApiClient) {
return [
{
name: 'brevo_list_workflows',
description: 'List all marketing automation workflows',
inputSchema: z.object({
limit: z.number().optional().describe('Number of workflows (default: 50)'),
offset: z.number().optional().describe('Pagination offset'),
sort: z.enum(['asc', 'desc']).optional().describe('Sort by creation date'),
status: z.enum(['draft', 'active', 'inactive']).optional().describe('Filter by status'),
}),
execute: async (args: any) => {
const params: any = {
limit: args.limit || 50,
offset: args.offset || 0,
};
if (args.sort) params.sort = args.sort;
if (args.status) params.status = args.status;
const result = await client.getPaginated<Workflow>('/automation/workflows', params);
return {
workflows: result.items,
total: result.total,
count: result.items.length,
};
},
},
{
name: 'brevo_get_workflow',
description: 'Get details of a specific workflow',
inputSchema: z.object({
workflowId: z.number().describe('Workflow ID'),
}),
execute: async (args: any) => {
return await client.get<Workflow>(`/automation/workflows/${args.workflowId}`);
},
},
{
name: 'brevo_activate_workflow',
description: 'Activate a workflow',
inputSchema: z.object({
workflowId: z.number().describe('Workflow ID'),
}),
execute: async (args: any) => {
return await client.patch(`/automation/workflows/${args.workflowId}`, {
status: 'active',
});
},
},
{
name: 'brevo_deactivate_workflow',
description: 'Deactivate a workflow',
inputSchema: z.object({
workflowId: z.number().describe('Workflow ID'),
}),
execute: async (args: any) => {
return await client.patch(`/automation/workflows/${args.workflowId}`, {
status: 'inactive',
});
},
},
{
name: 'brevo_get_workflow_stats',
description: 'Get statistics for a workflow',
inputSchema: z.object({
workflowId: z.number().describe('Workflow ID'),
}),
execute: async (args: any) => {
return await client.get<WorkflowStats>(`/automation/workflows/${args.workflowId}/stats`);
},
},
];
}

View File

@ -0,0 +1,188 @@
import { z } from 'zod';
import { BrevoApiClient } from '../types/api-client.js';
import { EmailCampaign, CampaignReport } from '../types/index.js';
export function createCampaignsTools(client: BrevoApiClient) {
return [
{
name: 'brevo_list_email_campaigns',
description: 'List all email campaigns with filters',
inputSchema: z.object({
type: z.enum(['classic', 'trigger', 'ab']).optional().describe('Campaign type'),
status: z.enum(['draft', 'sent', 'queued', 'suspended', 'archive']).optional().describe('Campaign status'),
limit: z.number().optional().describe('Number of campaigns (default: 50)'),
offset: z.number().optional().describe('Pagination offset'),
sort: z.enum(['asc', 'desc']).optional().describe('Sort by creation date'),
}),
execute: async (args: any) => {
const params: any = { limit: args.limit || 50, offset: args.offset || 0 };
if (args.type) params.type = args.type;
if (args.status) params.status = args.status;
if (args.sort) params.sort = args.sort;
const result = await client.getPaginated<EmailCampaign>('/emailCampaigns', params);
return {
campaigns: result.items,
total: result.total,
count: result.items.length,
};
},
},
{
name: 'brevo_get_email_campaign',
description: 'Get details of a specific email campaign',
inputSchema: z.object({
campaignId: z.number().describe('Campaign ID'),
}),
execute: async (args: any) => {
return await client.get<EmailCampaign>(`/emailCampaigns/${args.campaignId}`);
},
},
{
name: 'brevo_create_email_campaign',
description: 'Create a new email campaign',
inputSchema: z.object({
name: z.string().describe('Campaign name'),
subject: z.string().describe('Email subject line'),
sender: z.object({
name: z.string().optional(),
email: z.string().email(),
}).describe('Sender information'),
type: z.enum(['classic', 'trigger', 'ab']).optional().describe('Campaign type (default: classic)'),
htmlContent: z.string().optional().describe('HTML content of the email'),
templateId: z.number().optional().describe('Template ID to use'),
scheduledAt: z.string().optional().describe('Schedule date-time (ISO 8601)'),
recipients: z.object({
listIds: z.array(z.number()).optional(),
exclusionListIds: z.array(z.number()).optional(),
segmentIds: z.array(z.number()).optional(),
}).optional().describe('Recipients configuration'),
replyTo: z.string().email().optional().describe('Reply-to email'),
toField: z.string().optional().describe('To field placeholder'),
inlineImageActivation: z.boolean().optional().describe('Enable inline images'),
mirrorActive: z.boolean().optional().describe('Enable mirror link'),
recurring: z.boolean().optional().describe('Is recurring campaign'),
abTesting: z.boolean().optional().describe('Enable A/B testing'),
subjectA: z.string().optional().describe('Subject line A for A/B test'),
subjectB: z.string().optional().describe('Subject line B for A/B test'),
splitRule: z.number().optional().describe('A/B test split percentage'),
}),
execute: async (args: any) => {
const data: any = {
name: args.name,
subject: args.subject,
sender: args.sender,
};
if (args.type) data.type = args.type;
if (args.htmlContent) data.htmlContent = args.htmlContent;
if (args.templateId) data.templateId = args.templateId;
if (args.scheduledAt) data.scheduledAt = args.scheduledAt;
if (args.recipients) data.recipients = args.recipients;
if (args.replyTo) data.replyTo = args.replyTo;
if (args.toField) data.toField = args.toField;
if (args.inlineImageActivation !== undefined) data.inlineImageActivation = args.inlineImageActivation;
if (args.mirrorActive !== undefined) data.mirrorActive = args.mirrorActive;
if (args.recurring !== undefined) data.recurring = args.recurring;
if (args.abTesting !== undefined) data.abTesting = args.abTesting;
if (args.subjectA) data.subjectA = args.subjectA;
if (args.subjectB) data.subjectB = args.subjectB;
if (args.splitRule) data.splitRule = args.splitRule;
return await client.post('/emailCampaigns', data);
},
},
{
name: 'brevo_update_email_campaign',
description: 'Update an email campaign',
inputSchema: z.object({
campaignId: z.number().describe('Campaign ID'),
name: z.string().optional().describe('Campaign name'),
subject: z.string().optional().describe('Email subject line'),
sender: z.object({
name: z.string().optional(),
email: z.string().email(),
}).optional().describe('Sender information'),
htmlContent: z.string().optional().describe('HTML content'),
scheduledAt: z.string().optional().describe('Schedule date-time'),
recipients: z.object({
listIds: z.array(z.number()).optional(),
exclusionListIds: z.array(z.number()).optional(),
}).optional().describe('Recipients'),
replyTo: z.string().email().optional().describe('Reply-to email'),
inlineImageActivation: z.boolean().optional().describe('Enable inline images'),
mirrorActive: z.boolean().optional().describe('Enable mirror link'),
}),
execute: async (args: any) => {
const data: any = {};
if (args.name) data.name = args.name;
if (args.subject) data.subject = args.subject;
if (args.sender) data.sender = args.sender;
if (args.htmlContent) data.htmlContent = args.htmlContent;
if (args.scheduledAt) data.scheduledAt = args.scheduledAt;
if (args.recipients) data.recipients = args.recipients;
if (args.replyTo) data.replyTo = args.replyTo;
if (args.inlineImageActivation !== undefined) data.inlineImageActivation = args.inlineImageActivation;
if (args.mirrorActive !== undefined) data.mirrorActive = args.mirrorActive;
return await client.put(`/emailCampaigns/${args.campaignId}`, data);
},
},
{
name: 'brevo_delete_email_campaign',
description: 'Delete an email campaign',
inputSchema: z.object({
campaignId: z.number().describe('Campaign ID'),
}),
execute: async (args: any) => {
return await client.delete(`/emailCampaigns/${args.campaignId}`);
},
},
{
name: 'brevo_send_email_campaign',
description: 'Send an email campaign immediately',
inputSchema: z.object({
campaignId: z.number().describe('Campaign ID'),
}),
execute: async (args: any) => {
return await client.post(`/emailCampaigns/${args.campaignId}/sendNow`);
},
},
{
name: 'brevo_schedule_email_campaign',
description: 'Schedule an email campaign',
inputSchema: z.object({
campaignId: z.number().describe('Campaign ID'),
scheduledAt: z.string().describe('Schedule date-time (ISO 8601)'),
}),
execute: async (args: any) => {
return await client.put(`/emailCampaigns/${args.campaignId}`, {
scheduledAt: args.scheduledAt,
});
},
},
{
name: 'brevo_get_campaign_report',
description: 'Get detailed report for an email campaign',
inputSchema: z.object({
campaignId: z.number().describe('Campaign ID'),
}),
execute: async (args: any) => {
return await client.get<CampaignReport>(`/emailCampaigns/${args.campaignId}/report`);
},
},
{
name: 'brevo_list_campaign_links',
description: 'Get all links from an email campaign',
inputSchema: z.object({
campaignId: z.number().describe('Campaign ID'),
}),
execute: async (args: any) => {
const report = await client.get<CampaignReport>(`/emailCampaigns/${args.campaignId}/report`);
return {
links: Object.keys(report.linksStats || {}),
linkStats: report.linksStats,
};
},
},
];
}

View File

@ -0,0 +1,239 @@
import { z } from 'zod';
import { BrevoApiClient } from '../types/api-client.js';
import { Contact, ContactAttribute, ContactList, Folder } from '../types/index.js';
export function createContactsTools(client: BrevoApiClient) {
return [
{
name: 'brevo_list_contacts',
description: 'List all contacts with optional filters and pagination',
inputSchema: z.object({
limit: z.number().optional().describe('Number of contacts to return (default: 50)'),
offset: z.number().optional().describe('Index of the first contact (default: 0)'),
modifiedSince: z.string().optional().describe('Filter contacts modified since date (ISO 8601)'),
listIds: z.array(z.number()).optional().describe('Filter by list IDs'),
sort: z.enum(['asc', 'desc']).optional().describe('Sort by creation date'),
}),
execute: async (args: any) => {
const params: any = {
limit: args.limit || 50,
offset: args.offset || 0,
};
if (args.modifiedSince) params.modifiedSince = args.modifiedSince;
if (args.listIds) params.listIds = args.listIds;
if (args.sort) params.sort = args.sort;
const result = await client.getPaginated<Contact>('/contacts', params);
return {
contacts: result.items,
total: result.total,
count: result.items.length,
};
},
},
{
name: 'brevo_get_contact',
description: 'Get details of a specific contact by email or ID',
inputSchema: z.object({
identifier: z.string().describe('Contact email address or ID'),
}),
execute: async (args: any) => {
return await client.get<Contact>(`/contacts/${encodeURIComponent(args.identifier)}`);
},
},
{
name: 'brevo_create_contact',
description: 'Create a new contact',
inputSchema: z.object({
email: z.string().email().describe('Contact email address'),
attributes: z.record(z.any()).optional().describe('Contact attributes (e.g., FIRSTNAME, LASTNAME)'),
emailBlacklisted: z.boolean().optional().describe('Whether contact is blacklisted for emails'),
smsBlacklisted: z.boolean().optional().describe('Whether contact is blacklisted for SMS'),
listIds: z.array(z.number()).optional().describe('List IDs to add the contact to'),
updateEnabled: z.boolean().optional().describe('Update contact if already exists'),
smtpBlacklistSender: z.array(z.string()).optional().describe('SMTP blacklist sender emails'),
}),
execute: async (args: any) => {
const data: any = { email: args.email };
if (args.attributes) data.attributes = args.attributes;
if (args.emailBlacklisted !== undefined) data.emailBlacklisted = args.emailBlacklisted;
if (args.smsBlacklisted !== undefined) data.smsBlacklisted = args.smsBlacklisted;
if (args.listIds) data.listIds = args.listIds;
if (args.updateEnabled !== undefined) data.updateEnabled = args.updateEnabled;
if (args.smtpBlacklistSender) data.smtpBlacklistSender = args.smtpBlacklistSender;
return await client.post('/contacts', data);
},
},
{
name: 'brevo_update_contact',
description: 'Update an existing contact',
inputSchema: z.object({
identifier: z.string().describe('Contact email address or ID'),
attributes: z.record(z.any()).optional().describe('Contact attributes to update'),
emailBlacklisted: z.boolean().optional().describe('Whether contact is blacklisted for emails'),
smsBlacklisted: z.boolean().optional().describe('Whether contact is blacklisted for SMS'),
listIds: z.array(z.number()).optional().describe('List IDs to add the contact to'),
unlinkListIds: z.array(z.number()).optional().describe('List IDs to remove the contact from'),
smtpBlacklistSender: z.array(z.string()).optional().describe('SMTP blacklist sender emails'),
}),
execute: async (args: any) => {
const data: any = {};
if (args.attributes) data.attributes = args.attributes;
if (args.emailBlacklisted !== undefined) data.emailBlacklisted = args.emailBlacklisted;
if (args.smsBlacklisted !== undefined) data.smsBlacklisted = args.smsBlacklisted;
if (args.listIds) data.listIds = args.listIds;
if (args.unlinkListIds) data.unlinkListIds = args.unlinkListIds;
if (args.smtpBlacklistSender) data.smtpBlacklistSender = args.smtpBlacklistSender;
return await client.put(`/contacts/${encodeURIComponent(args.identifier)}`, data);
},
},
{
name: 'brevo_delete_contact',
description: 'Delete a contact',
inputSchema: z.object({
identifier: z.string().describe('Contact email address or ID'),
}),
execute: async (args: any) => {
return await client.delete(`/contacts/${encodeURIComponent(args.identifier)}`);
},
},
{
name: 'brevo_search_contacts',
description: 'Search contacts using advanced filters',
inputSchema: z.object({
email: z.string().optional().describe('Email to search for'),
modifiedSince: z.string().optional().describe('Filter contacts modified since date'),
listIds: z.array(z.number()).optional().describe('Filter by list IDs'),
limit: z.number().optional().describe('Number of results (default: 50)'),
offset: z.number().optional().describe('Pagination offset'),
}),
execute: async (args: any) => {
const params: any = { limit: args.limit || 50, offset: args.offset || 0 };
if (args.email) params.email = args.email;
if (args.modifiedSince) params.modifiedSince = args.modifiedSince;
if (args.listIds) params.listIds = args.listIds;
const result = await client.getPaginated<Contact>('/contacts', params);
return {
contacts: result.items,
total: result.total,
count: result.items.length,
};
},
},
{
name: 'brevo_import_contacts',
description: 'Import contacts from a file URL',
inputSchema: z.object({
fileUrl: z.string().url().describe('URL of the file to import (CSV)'),
listIds: z.array(z.number()).optional().describe('List IDs to add contacts to'),
emailBlacklist: z.boolean().optional().describe('Blacklist emails'),
smsBlacklist: z.boolean().optional().describe('Blacklist SMS'),
updateExistingContacts: z.boolean().optional().describe('Update existing contacts'),
emptyContactsAttributes: z.boolean().optional().describe('Empty contact attributes'),
}),
execute: async (args: any) => {
const data: any = { fileUrl: args.fileUrl };
if (args.listIds) data.listIds = args.listIds;
if (args.emailBlacklist !== undefined) data.emailBlacklist = args.emailBlacklist;
if (args.smsBlacklist !== undefined) data.smsBlacklist = args.smsBlacklist;
if (args.updateExistingContacts !== undefined) data.updateExistingContacts = args.updateExistingContacts;
if (args.emptyContactsAttributes !== undefined) data.emptyContactsAttributes = args.emptyContactsAttributes;
return await client.post('/contacts/import', data);
},
},
{
name: 'brevo_export_contacts',
description: 'Export contacts to a file',
inputSchema: z.object({
exportAttributes: z.array(z.string()).optional().describe('Attributes to export'),
filter: z.object({
listIds: z.array(z.number()).optional(),
excludedListIds: z.array(z.number()).optional(),
modifiedSince: z.string().optional(),
}).optional().describe('Filter criteria for export'),
notifyUrl: z.string().url().optional().describe('Webhook URL to notify when export is ready'),
}),
execute: async (args: any) => {
const data: any = {};
if (args.exportAttributes) data.exportAttributes = args.exportAttributes;
if (args.filter) data.filter = args.filter;
if (args.notifyUrl) data.notifyUrl = args.notifyUrl;
return await client.post('/contacts/export', data);
},
},
{
name: 'brevo_list_contact_attributes',
description: 'List all contact attributes',
inputSchema: z.object({}),
execute: async () => {
return await client.get<{ attributes: ContactAttribute[] }>('/contacts/attributes');
},
},
{
name: 'brevo_create_contact_attribute',
description: 'Create a new contact attribute',
inputSchema: z.object({
name: z.string().describe('Attribute name (e.g., CUSTOM_FIELD)'),
category: z.enum(['normal', 'transactional', 'category', 'calculated', 'global']).describe('Attribute category'),
type: z.enum(['text', 'date', 'float', 'id', 'boolean']).optional().describe('Attribute type'),
enumeration: z.array(z.object({
value: z.number(),
label: z.string(),
})).optional().describe('Enumeration values for category type'),
}),
execute: async (args: any) => {
const data: any = {
name: args.name,
category: args.category,
};
if (args.type) data.type = args.type;
if (args.enumeration) data.enumeration = args.enumeration;
return await client.post('/contacts/attributes/normal/' + args.name, data);
},
},
{
name: 'brevo_list_contact_folders',
description: 'List all contact folders',
inputSchema: z.object({
limit: z.number().optional().describe('Number of folders (default: 50)'),
offset: z.number().optional().describe('Pagination offset'),
}),
execute: async (args: any) => {
const result = await client.getPaginated<Folder>('/contacts/folders', {
limit: args.limit || 50,
offset: args.offset || 0,
});
return {
folders: result.items,
total: result.total,
count: result.items.length,
};
},
},
{
name: 'brevo_list_contact_lists',
description: 'List all contact lists',
inputSchema: z.object({
limit: z.number().optional().describe('Number of lists (default: 50)'),
offset: z.number().optional().describe('Pagination offset'),
}),
execute: async (args: any) => {
const result = await client.getPaginated<ContactList>('/contacts/lists', {
limit: args.limit || 50,
offset: args.offset || 0,
});
return {
lists: result.items,
total: result.total,
count: result.items.length,
};
},
},
];
}

View File

@ -0,0 +1,122 @@
import { z } from 'zod';
import { BrevoApiClient } from '../types/api-client.js';
import { Deal, Pipeline, Stage } from '../types/index.js';
export function createDealsTools(client: BrevoApiClient) {
return [
{
name: 'brevo_list_deals',
description: 'List all CRM deals',
inputSchema: z.object({
limit: z.number().optional().describe('Number of deals (default: 50)'),
offset: z.number().optional().describe('Pagination offset'),
sort: z.enum(['asc', 'desc']).optional().describe('Sort by creation date'),
filters: z.object({
'attributes.pipeline': z.string().optional(),
'attributes.deal_stage': z.string().optional(),
}).optional().describe('Filter by pipeline or stage'),
}),
execute: async (args: any) => {
const params: any = {
limit: args.limit || 50,
offset: args.offset || 0,
};
if (args.sort) params.sort = args.sort;
if (args.filters) {
params.filters = JSON.stringify(args.filters);
}
const result = await client.getPaginated<Deal>('/crm/deals', params);
return {
deals: result.items,
total: result.total,
count: result.items.length,
};
},
},
{
name: 'brevo_get_deal',
description: 'Get details of a specific deal',
inputSchema: z.object({
dealId: z.string().describe('Deal ID'),
}),
execute: async (args: any) => {
return await client.get<Deal>(`/crm/deals/${args.dealId}`);
},
},
{
name: 'brevo_create_deal',
description: 'Create a new deal',
inputSchema: z.object({
name: z.string().describe('Deal name'),
attributes: z.record(z.any()).optional().describe('Deal attributes (pipeline, stage, amount, etc.)'),
linkedContactsIds: z.array(z.number()).optional().describe('Contact IDs to link'),
linkedCompaniesIds: z.array(z.string()).optional().describe('Company IDs to link'),
}),
execute: async (args: any) => {
const data: any = { name: args.name };
if (args.attributes) data.attributes = args.attributes;
if (args.linkedContactsIds) data.linkedContactsIds = args.linkedContactsIds;
if (args.linkedCompaniesIds) data.linkedCompaniesIds = args.linkedCompaniesIds;
return await client.post('/crm/deals', data);
},
},
{
name: 'brevo_update_deal',
description: 'Update a deal',
inputSchema: z.object({
dealId: z.string().describe('Deal ID'),
name: z.string().optional().describe('Deal name'),
attributes: z.record(z.any()).optional().describe('Deal attributes'),
linkedContactsIds: z.array(z.number()).optional().describe('Contact IDs'),
linkedCompaniesIds: z.array(z.string()).optional().describe('Company IDs'),
}),
execute: async (args: any) => {
const data: any = {};
if (args.name) data.name = args.name;
if (args.attributes) data.attributes = args.attributes;
if (args.linkedContactsIds) data.linkedContactsIds = args.linkedContactsIds;
if (args.linkedCompaniesIds) data.linkedCompaniesIds = args.linkedCompaniesIds;
return await client.patch(`/crm/deals/${args.dealId}`, data);
},
},
{
name: 'brevo_delete_deal',
description: 'Delete a deal',
inputSchema: z.object({
dealId: z.string().describe('Deal ID'),
}),
execute: async (args: any) => {
return await client.delete(`/crm/deals/${args.dealId}`);
},
},
{
name: 'brevo_list_pipelines',
description: 'List all CRM pipelines',
inputSchema: z.object({}),
execute: async () => {
const result = await client.get<{ pipelines: Pipeline[] }>('/crm/pipeline/details/all');
return {
pipelines: result.pipelines || [],
count: result.pipelines?.length || 0,
};
},
},
{
name: 'brevo_list_deal_stages',
description: 'List all stages for a specific pipeline',
inputSchema: z.object({
pipelineId: z.string().describe('Pipeline ID'),
}),
execute: async (args: any) => {
const result = await client.get<Pipeline>(`/crm/pipeline/details/${args.pipelineId}`);
return {
pipelineId: args.pipelineId,
stages: result || [],
};
},
},
];
}

View File

@ -0,0 +1,105 @@
import { z } from 'zod';
import { BrevoApiClient } from '../types/api-client.js';
import { ContactList } from '../types/index.js';
export function createListsTools(client: BrevoApiClient) {
return [
{
name: 'brevo_list_lists',
description: 'List all contact lists',
inputSchema: z.object({
limit: z.number().optional().describe('Number of lists (default: 50)'),
offset: z.number().optional().describe('Pagination offset'),
sort: z.enum(['asc', 'desc']).optional().describe('Sort by creation date'),
}),
execute: async (args: any) => {
const result = await client.getPaginated<ContactList>('/contacts/lists', {
limit: args.limit || 50,
offset: args.offset || 0,
sort: args.sort,
});
return {
lists: result.items,
total: result.total,
count: result.items.length,
};
},
},
{
name: 'brevo_get_list',
description: 'Get details of a specific contact list',
inputSchema: z.object({
listId: z.number().describe('List ID'),
}),
execute: async (args: any) => {
return await client.get<ContactList>(`/contacts/lists/${args.listId}`);
},
},
{
name: 'brevo_create_list',
description: 'Create a new contact list',
inputSchema: z.object({
name: z.string().describe('List name'),
folderId: z.number().optional().describe('Folder ID to place the list in'),
}),
execute: async (args: any) => {
const data: any = { name: args.name };
if (args.folderId) data.folderId = args.folderId;
return await client.post('/contacts/lists', data);
},
},
{
name: 'brevo_update_list',
description: 'Update a contact list',
inputSchema: z.object({
listId: z.number().describe('List ID'),
name: z.string().optional().describe('New list name'),
folderId: z.number().optional().describe('New folder ID'),
}),
execute: async (args: any) => {
const data: any = {};
if (args.name) data.name = args.name;
if (args.folderId) data.folderId = args.folderId;
return await client.put(`/contacts/lists/${args.listId}`, data);
},
},
{
name: 'brevo_delete_list',
description: 'Delete a contact list',
inputSchema: z.object({
listId: z.number().describe('List ID'),
}),
execute: async (args: any) => {
return await client.delete(`/contacts/lists/${args.listId}`);
},
},
{
name: 'brevo_add_contacts_to_list',
description: 'Add contacts to a list',
inputSchema: z.object({
listId: z.number().describe('List ID'),
emails: z.array(z.string().email()).describe('Array of contact emails to add'),
}),
execute: async (args: any) => {
return await client.post(`/contacts/lists/${args.listId}/contacts/add`, {
emails: args.emails,
});
},
},
{
name: 'brevo_remove_contacts_from_list',
description: 'Remove contacts from a list',
inputSchema: z.object({
listId: z.number().describe('List ID'),
emails: z.array(z.string().email()).describe('Array of contact emails to remove'),
}),
execute: async (args: any) => {
return await client.post(`/contacts/lists/${args.listId}/contacts/remove`, {
emails: args.emails,
});
},
},
];
}

View File

@ -0,0 +1,109 @@
import { z } from 'zod';
import { BrevoApiClient } from '../types/api-client.js';
import { Sender } from '../types/index.js';
export function createSendersTools(client: BrevoApiClient) {
return [
{
name: 'brevo_list_senders',
description: 'List all senders',
inputSchema: z.object({
ip: z.string().optional().describe('Filter by dedicated IP'),
domain: z.string().optional().describe('Filter by domain'),
}),
execute: async (args: any) => {
const params: any = {};
if (args.ip) params.ip = args.ip;
if (args.domain) params.domain = args.domain;
const result = await client.get<{ senders: Sender[] }>('/senders', params);
return {
senders: result.senders || [],
count: result.senders?.length || 0,
};
},
},
{
name: 'brevo_get_sender',
description: 'Get details of a specific sender',
inputSchema: z.object({
senderId: z.number().describe('Sender ID'),
}),
execute: async (args: any) => {
return await client.get<Sender>(`/senders/${args.senderId}`);
},
},
{
name: 'brevo_create_sender',
description: 'Create a new sender',
inputSchema: z.object({
name: z.string().describe('Sender name'),
email: z.string().email().describe('Sender email address'),
ips: z.array(z.object({
ip: z.string(),
domain: z.string(),
weight: z.number().optional(),
})).optional().describe('IPs configuration'),
}),
execute: async (args: any) => {
const data: any = {
name: args.name,
email: args.email,
};
if (args.ips) data.ips = args.ips;
return await client.post('/senders', data);
},
},
{
name: 'brevo_update_sender',
description: 'Update a sender',
inputSchema: z.object({
senderId: z.number().describe('Sender ID'),
name: z.string().optional().describe('Sender name'),
email: z.string().email().optional().describe('Sender email'),
ips: z.array(z.object({
ip: z.string(),
domain: z.string(),
weight: z.number().optional(),
})).optional().describe('IPs configuration'),
}),
execute: async (args: any) => {
const data: any = {};
if (args.name) data.name = args.name;
if (args.email) data.email = args.email;
if (args.ips) data.ips = args.ips;
return await client.put(`/senders/${args.senderId}`, data);
},
},
{
name: 'brevo_delete_sender',
description: 'Delete a sender',
inputSchema: z.object({
senderId: z.number().describe('Sender ID'),
}),
execute: async (args: any) => {
return await client.delete(`/senders/${args.senderId}`);
},
},
{
name: 'brevo_validate_sender',
description: 'Validate sender domain and authentication (SPF/DKIM)',
inputSchema: z.object({
senderId: z.number().describe('Sender ID'),
}),
execute: async (args: any) => {
const sender = await client.get<Sender>(`/senders/${args.senderId}`);
return {
senderId: args.senderId,
email: sender.email,
spf: sender.spf,
dkim: sender.dkim,
active: sender.active,
validated: sender.spf && sender.dkim,
};
},
},
];
}

View File

@ -0,0 +1,113 @@
import { z } from 'zod';
import { BrevoApiClient } from '../types/api-client.js';
import { SMSCampaign } from '../types/index.js';
export function createSmsTools(client: BrevoApiClient) {
return [
{
name: 'brevo_list_sms_campaigns',
description: 'List all SMS campaigns',
inputSchema: z.object({
status: z.enum(['draft', 'sent', 'queued', 'suspended', 'inProcess']).optional().describe('Filter by status'),
limit: z.number().optional().describe('Number of campaigns (default: 50)'),
offset: z.number().optional().describe('Pagination offset'),
sort: z.enum(['asc', 'desc']).optional().describe('Sort by creation date'),
}),
execute: async (args: any) => {
const params: any = {
limit: args.limit || 50,
offset: args.offset || 0,
};
if (args.status) params.status = args.status;
if (args.sort) params.sort = args.sort;
const result = await client.getPaginated<SMSCampaign>('/smsCampaigns', params);
return {
campaigns: result.items,
total: result.total,
count: result.items.length,
};
},
},
{
name: 'brevo_get_sms_campaign',
description: 'Get details of a specific SMS campaign',
inputSchema: z.object({
campaignId: z.number().describe('SMS campaign ID'),
}),
execute: async (args: any) => {
return await client.get<SMSCampaign>(`/smsCampaigns/${args.campaignId}`);
},
},
{
name: 'brevo_create_sms_campaign',
description: 'Create a new SMS campaign',
inputSchema: z.object({
name: z.string().describe('Campaign name'),
sender: z.string().describe('Sender name or phone number'),
content: z.string().describe('SMS content'),
recipients: z.object({
listIds: z.array(z.number()).optional(),
exclusionListIds: z.array(z.number()).optional(),
}).optional().describe('Recipients configuration'),
scheduledAt: z.string().optional().describe('Schedule date-time (ISO 8601)'),
}),
execute: async (args: any) => {
const data: any = {
name: args.name,
sender: args.sender,
content: args.content,
};
if (args.recipients) data.recipients = args.recipients;
if (args.scheduledAt) data.scheduledAt = args.scheduledAt;
return await client.post('/smsCampaigns', data);
},
},
{
name: 'brevo_update_sms_campaign',
description: 'Update an SMS campaign',
inputSchema: z.object({
campaignId: z.number().describe('Campaign ID'),
name: z.string().optional().describe('Campaign name'),
sender: z.string().optional().describe('Sender'),
content: z.string().optional().describe('SMS content'),
recipients: z.object({
listIds: z.array(z.number()).optional(),
exclusionListIds: z.array(z.number()).optional(),
}).optional().describe('Recipients'),
scheduledAt: z.string().optional().describe('Schedule date-time'),
}),
execute: async (args: any) => {
const data: any = {};
if (args.name) data.name = args.name;
if (args.sender) data.sender = args.sender;
if (args.content) data.content = args.content;
if (args.recipients) data.recipients = args.recipients;
if (args.scheduledAt) data.scheduledAt = args.scheduledAt;
return await client.put(`/smsCampaigns/${args.campaignId}`, data);
},
},
{
name: 'brevo_send_sms_campaign',
description: 'Send an SMS campaign immediately',
inputSchema: z.object({
campaignId: z.number().describe('Campaign ID'),
}),
execute: async (args: any) => {
return await client.post(`/smsCampaigns/${args.campaignId}/sendNow`);
},
},
{
name: 'brevo_get_sms_campaign_report',
description: 'Get report for an SMS campaign',
inputSchema: z.object({
campaignId: z.number().describe('Campaign ID'),
}),
execute: async (args: any) => {
return await client.get(`/smsCampaigns/${args.campaignId}/report`);
},
},
];
}

View File

@ -0,0 +1,124 @@
import { z } from 'zod';
import { BrevoApiClient } from '../types/api-client.js';
import { EmailTemplate } from '../types/index.js';
export function createTemplatesTools(client: BrevoApiClient) {
return [
{
name: 'brevo_list_templates',
description: 'List all email templates',
inputSchema: z.object({
limit: z.number().optional().describe('Number of templates (default: 50)'),
offset: z.number().optional().describe('Pagination offset'),
sort: z.enum(['asc', 'desc']).optional().describe('Sort by creation date'),
}),
execute: async (args: any) => {
const result = await client.getPaginated<EmailTemplate>('/smtp/templates', {
limit: args.limit || 50,
offset: args.offset || 0,
sort: args.sort,
});
return {
templates: result.items,
total: result.total,
count: result.items.length,
};
},
},
{
name: 'brevo_get_template',
description: 'Get details of a specific email template',
inputSchema: z.object({
templateId: z.number().describe('Template ID'),
}),
execute: async (args: any) => {
return await client.get<EmailTemplate>(`/smtp/templates/${args.templateId}`);
},
},
{
name: 'brevo_create_template',
description: 'Create a new email template',
inputSchema: z.object({
name: z.string().describe('Template name'),
subject: z.string().describe('Email subject line'),
sender: z.object({
name: z.string().optional(),
email: z.string().email(),
}).describe('Sender information'),
htmlContent: z.string().describe('HTML content with template variables'),
isActive: z.boolean().optional().describe('Is template active (default: true)'),
replyTo: z.string().email().optional().describe('Reply-to email'),
toField: z.string().optional().describe('To field placeholder'),
tag: z.string().optional().describe('Tag for categorization'),
attachmentUrl: z.string().url().optional().describe('URL of attachment'),
}),
execute: async (args: any) => {
const data: any = {
name: args.name,
subject: args.subject,
sender: args.sender,
htmlContent: args.htmlContent,
};
if (args.isActive !== undefined) data.isActive = args.isActive;
if (args.replyTo) data.replyTo = args.replyTo;
if (args.toField) data.toField = args.toField;
if (args.tag) data.tag = args.tag;
if (args.attachmentUrl) data.attachmentUrl = args.attachmentUrl;
return await client.post('/smtp/templates', data);
},
},
{
name: 'brevo_update_template',
description: 'Update an email template',
inputSchema: z.object({
templateId: z.number().describe('Template ID'),
name: z.string().optional().describe('Template name'),
subject: z.string().optional().describe('Email subject'),
sender: z.object({
name: z.string().optional(),
email: z.string().email(),
}).optional().describe('Sender information'),
htmlContent: z.string().optional().describe('HTML content'),
isActive: z.boolean().optional().describe('Is template active'),
replyTo: z.string().email().optional().describe('Reply-to email'),
tag: z.string().optional().describe('Tag'),
}),
execute: async (args: any) => {
const data: any = {};
if (args.name) data.name = args.name;
if (args.subject) data.subject = args.subject;
if (args.sender) data.sender = args.sender;
if (args.htmlContent) data.htmlContent = args.htmlContent;
if (args.isActive !== undefined) data.isActive = args.isActive;
if (args.replyTo) data.replyTo = args.replyTo;
if (args.tag) data.tag = args.tag;
return await client.put(`/smtp/templates/${args.templateId}`, data);
},
},
{
name: 'brevo_delete_template',
description: 'Delete an email template',
inputSchema: z.object({
templateId: z.number().describe('Template ID'),
}),
execute: async (args: any) => {
return await client.delete(`/smtp/templates/${args.templateId}`);
},
},
{
name: 'brevo_send_test_template',
description: 'Send a test email using a template',
inputSchema: z.object({
templateId: z.number().describe('Template ID'),
emailTo: z.array(z.string().email()).describe('Test recipient emails'),
}),
execute: async (args: any) => {
return await client.post(`/smtp/templates/${args.templateId}/sendTest`, {
emailTo: args.emailTo,
});
},
},
];
}

View File

@ -0,0 +1,139 @@
import { z } from 'zod';
import { BrevoApiClient } from '../types/api-client.js';
import { TransactionalEmail, TransactionalSMS } from '../types/index.js';
export function createTransactionalTools(client: BrevoApiClient) {
return [
{
name: 'brevo_send_transactional_email',
description: 'Send a transactional email',
inputSchema: z.object({
to: z.array(z.object({
email: z.string().email(),
name: z.string().optional(),
})).describe('Recipients'),
sender: z.object({
email: z.string().email(),
name: z.string().optional(),
}).describe('Sender information'),
subject: z.string().optional().describe('Email subject'),
htmlContent: z.string().optional().describe('HTML content'),
textContent: z.string().optional().describe('Text content'),
templateId: z.number().optional().describe('Template ID'),
params: z.record(z.any()).optional().describe('Template parameters'),
cc: z.array(z.object({
email: z.string().email(),
name: z.string().optional(),
})).optional().describe('CC recipients'),
bcc: z.array(z.object({
email: z.string().email(),
name: z.string().optional(),
})).optional().describe('BCC recipients'),
replyTo: z.object({
email: z.string().email(),
name: z.string().optional(),
}).optional().describe('Reply-to'),
attachment: z.array(z.object({
url: z.string().url().optional(),
content: z.string().optional(),
name: z.string(),
})).optional().describe('Attachments'),
headers: z.record(z.string()).optional().describe('Custom headers'),
tags: z.array(z.string()).optional().describe('Tags for categorization'),
}),
execute: async (args: any) => {
const data: TransactionalEmail = {
to: args.to,
sender: args.sender,
};
if (args.subject) data.subject = args.subject;
if (args.htmlContent) data.htmlContent = args.htmlContent;
if (args.textContent) data.textContent = args.textContent;
if (args.templateId) data.templateId = args.templateId;
if (args.params) data.params = args.params;
if (args.cc) data.cc = args.cc;
if (args.bcc) data.bcc = args.bcc;
if (args.replyTo) data.replyTo = args.replyTo;
if (args.attachment) data.attachment = args.attachment;
if (args.headers) data.headers = args.headers;
if (args.tags) data.tags = args.tags;
return await client.post('/smtp/email', data);
},
},
{
name: 'brevo_send_transactional_sms',
description: 'Send a transactional SMS',
inputSchema: z.object({
sender: z.string().describe('Sender name (max 11 characters) or phone number'),
recipient: z.string().describe('Recipient phone number (E.164 format)'),
content: z.string().describe('SMS content (max 160 characters for single SMS)'),
type: z.enum(['transactional', 'marketing']).optional().describe('SMS type (default: transactional)'),
tag: z.string().optional().describe('Tag for categorization'),
webUrl: z.string().url().optional().describe('URL for web version'),
}),
execute: async (args: any) => {
const data: TransactionalSMS = {
sender: args.sender,
recipient: args.recipient,
content: args.content,
};
if (args.type) data.type = args.type;
if (args.tag) data.tag = args.tag;
if (args.webUrl) data.webUrl = args.webUrl;
return await client.post('/transactionalSMS/sms', data);
},
},
{
name: 'brevo_list_transactional_events',
description: 'Get list of transactional email/SMS events',
inputSchema: z.object({
email: z.string().email().optional().describe('Filter by email'),
templateId: z.number().optional().describe('Filter by template ID'),
messageId: z.string().optional().describe('Filter by message ID'),
event: z.enum(['bounces', 'hardBounces', 'softBounces', 'delivered', 'spam', 'requests', 'opened', 'clicks', 'invalid', 'deferred', 'blocked', 'unsubscribed']).optional().describe('Event type'),
days: z.number().optional().describe('Number of days to retrieve (max 30)'),
limit: z.number().optional().describe('Number of events (default: 50)'),
offset: z.number().optional().describe('Pagination offset'),
sort: z.enum(['asc', 'desc']).optional().describe('Sort by date'),
}),
execute: async (args: any) => {
const params: any = {
limit: args.limit || 50,
offset: args.offset || 0,
};
if (args.email) params.email = args.email;
if (args.templateId) params.templateId = args.templateId;
if (args.messageId) params.messageId = args.messageId;
if (args.event) params.event = args.event;
if (args.days) params.days = args.days;
if (args.sort) params.sort = args.sort;
return await client.get('/smtp/statistics/events', params);
},
},
{
name: 'brevo_get_aggregated_report',
description: 'Get aggregated transactional email/SMS statistics',
inputSchema: z.object({
startDate: z.string().describe('Start date (YYYY-MM-DD)'),
endDate: z.string().describe('End date (YYYY-MM-DD)'),
days: z.number().optional().describe('Number of days (alternative to date range)'),
tag: z.string().optional().describe('Filter by tag'),
}),
execute: async (args: any) => {
const params: any = {};
if (args.days) {
params.days = args.days;
} else {
params.startDate = args.startDate;
params.endDate = args.endDate;
}
if (args.tag) params.tag = args.tag;
return await client.get('/smtp/statistics/aggregatedReport', params);
},
},
];
}

View File

@ -0,0 +1,103 @@
import { z } from 'zod';
import { BrevoApiClient } from '../types/api-client.js';
import { Webhook } from '../types/index.js';
export function createWebhooksTools(client: BrevoApiClient) {
return [
{
name: 'brevo_list_webhooks',
description: 'List all webhooks',
inputSchema: z.object({
type: z.enum(['marketing', 'transactional']).optional().describe('Filter by webhook type'),
sort: z.enum(['asc', 'desc']).optional().describe('Sort by creation date'),
}),
execute: async (args: any) => {
const params: any = {};
if (args.type) params.type = args.type;
if (args.sort) params.sort = args.sort;
const result = await client.get<{ webhooks: Webhook[] }>('/webhooks', params);
return {
webhooks: result.webhooks || [],
count: result.webhooks?.length || 0,
};
},
},
{
name: 'brevo_get_webhook',
description: 'Get details of a specific webhook',
inputSchema: z.object({
webhookId: z.number().describe('Webhook ID'),
}),
execute: async (args: any) => {
return await client.get<Webhook>(`/webhooks/${args.webhookId}`);
},
},
{
name: 'brevo_create_webhook',
description: 'Create a new webhook',
inputSchema: z.object({
url: z.string().url().describe('Webhook URL'),
description: z.string().optional().describe('Webhook description'),
events: z.array(z.string()).describe('Events to subscribe to (e.g., delivered, opened, clicked)'),
type: z.enum(['marketing', 'transactional']).describe('Webhook type'),
batched: z.boolean().optional().describe('Enable batched events'),
auth: z.object({
type: z.enum(['bearer', 'basic']),
token: z.string().optional(),
username: z.string().optional(),
password: z.string().optional(),
}).optional().describe('Authentication configuration'),
}),
execute: async (args: any) => {
const data: any = {
url: args.url,
events: args.events,
type: args.type,
};
if (args.description) data.description = args.description;
if (args.batched !== undefined) data.batched = args.batched;
if (args.auth) data.auth = args.auth;
return await client.post('/webhooks', data);
},
},
{
name: 'brevo_update_webhook',
description: 'Update a webhook',
inputSchema: z.object({
webhookId: z.number().describe('Webhook ID'),
url: z.string().url().optional().describe('Webhook URL'),
description: z.string().optional().describe('Description'),
events: z.array(z.string()).optional().describe('Events'),
batched: z.boolean().optional().describe('Batched events'),
auth: z.object({
type: z.enum(['bearer', 'basic']),
token: z.string().optional(),
username: z.string().optional(),
password: z.string().optional(),
}).optional().describe('Authentication'),
}),
execute: async (args: any) => {
const data: any = {};
if (args.url) data.url = args.url;
if (args.description) data.description = args.description;
if (args.events) data.events = args.events;
if (args.batched !== undefined) data.batched = args.batched;
if (args.auth) data.auth = args.auth;
return await client.put(`/webhooks/${args.webhookId}`, data);
},
},
{
name: 'brevo_delete_webhook',
description: 'Delete a webhook',
inputSchema: z.object({
webhookId: z.number().describe('Webhook ID'),
}),
execute: async (args: any) => {
return await client.delete(`/webhooks/${args.webhookId}`);
},
},
];
}

View File

@ -0,0 +1,92 @@
import axios, { AxiosInstance, AxiosError } from 'axios';
import { BrevoConfig, ApiError } from './index.js';
export class BrevoApiClient {
private client: AxiosInstance;
constructor(config: BrevoConfig) {
this.client = axios.create({
baseURL: config.baseUrl || 'https://api.brevo.com/v3',
headers: {
'api-key': config.apiKey,
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
// Response interceptor for error handling
this.client.interceptors.response.use(
(response) => response,
(error: AxiosError<ApiError>) => {
if (error.response) {
const apiError = error.response.data;
throw new Error(
`Brevo API Error (${error.response.status}): ${apiError?.message || error.message}`
);
}
throw error;
}
);
}
// Generic GET request with pagination support
async get<T>(endpoint: string, params?: Record<string, any>): Promise<T> {
const response = await this.client.get<T>(endpoint, { params });
return response.data;
}
// Generic POST request
async post<T>(endpoint: string, data?: any): Promise<T> {
const response = await this.client.post<T>(endpoint, data);
return response.data;
}
// Generic PUT request
async put<T>(endpoint: string, data?: any): Promise<T> {
const response = await this.client.put<T>(endpoint, data);
return response.data;
}
// Generic PATCH request
async patch<T>(endpoint: string, data?: any): Promise<T> {
const response = await this.client.patch<T>(endpoint, data);
return response.data;
}
// Generic DELETE request
async delete<T>(endpoint: string): Promise<T> {
const response = await this.client.delete<T>(endpoint);
return response.data;
}
// Paginated list helper
async getPaginated<T>(
endpoint: string,
params: Record<string, any> = {}
): Promise<{ items: T[]; total?: number }> {
const limit = params.limit || 50;
const offset = params.offset || 0;
const response = await this.get<any>(endpoint, { ...params, limit, offset });
// Handle different response structures
const items =
response.contacts ||
response.campaigns ||
response.lists ||
response.folders ||
response.templates ||
response.senders ||
response.workflows ||
response.webhooks ||
response.deals ||
response.pipelines ||
response.stages ||
[];
return {
items,
total: response.count,
};
}
}

View File

@ -0,0 +1,272 @@
// Brevo API Types
export interface BrevoConfig {
apiKey: string;
baseUrl?: string;
}
// Contacts
export interface Contact {
id: number;
email: string;
emailBlacklisted?: boolean;
smsBlacklisted?: boolean;
createdAt?: string;
modifiedAt?: string;
attributes?: Record<string, any>;
listIds?: number[];
listUnsubscribed?: number[];
}
export interface ContactAttribute {
name: string;
category: 'normal' | 'transactional' | 'category' | 'calculated' | 'global';
type?: 'text' | 'date' | 'float' | 'id' | 'boolean';
enumeration?: { value: number; label: string }[];
}
export interface ContactList {
id: number;
name: string;
totalBlacklisted?: number;
totalSubscribers?: number;
uniqueSubscribers?: number;
folderId?: number;
createdAt?: string;
}
export interface Folder {
id: number;
name: string;
totalBlacklisted?: number;
totalSubscribers?: number;
uniqueSubscribers?: number;
}
// Campaigns
export interface EmailCampaign {
id: number;
name: string;
subject?: string;
type: 'classic' | 'trigger' | 'ab';
status: 'draft' | 'sent' | 'queued' | 'suspended' | 'archive';
scheduledAt?: string;
abTesting?: boolean;
subjectA?: string;
subjectB?: string;
splitRule?: number;
winnerCriteria?: string;
winnerDelay?: number;
sendAtBestTime?: boolean;
htmlContent?: string;
sender?: { name?: string; email?: string; id?: number };
replyTo?: string;
toField?: string;
recipients?: { listIds?: number[]; exclusionListIds?: number[]; segmentIds?: number[] };
attachmentUrl?: string;
inlineImageActivation?: boolean;
mirrorActive?: boolean;
recurring?: boolean;
createdAt?: string;
modifiedAt?: string;
}
export interface CampaignReport {
globalStats: {
uniqueClicks: number;
clickers: number;
complaints: number;
delivered: number;
sent: number;
softBounces: number;
hardBounces: number;
uniqueViews: number;
trackableViews: number;
unsubscriptions: number;
viewed: number;
deferred: number;
returnBounce: number;
};
campaignStats: Array<{
listId: number;
uniqueClicks: number;
clickers: number;
complaints: number;
delivered: number;
sent: number;
softBounces: number;
hardBounces: number;
uniqueViews: number;
unsubscriptions: number;
viewed: number;
}>;
mirrorClick: number;
remaining: number;
linksStats: Record<string, number>;
statsByDomain: {
[domain: string]: {
delivered: number;
softBounces: number;
hardBounces: number;
complaints: number;
opened: number;
clicked: number;
unsubscribed: number;
};
};
statsByDevice: {
[device: string]: {
viewed: number;
clicked: number;
};
};
statsByBrowser: Record<string, number>;
}
// Transactional
export interface TransactionalEmail {
sender: { name?: string; email: string; id?: number };
to: Array<{ email: string; name?: string }>;
bcc?: Array<{ email: string; name?: string }>;
cc?: Array<{ email: string; name?: string }>;
htmlContent?: string;
textContent?: string;
subject?: string;
replyTo?: { email: string; name?: string };
attachment?: Array<{ url?: string; content?: string; name?: string }>;
headers?: Record<string, string>;
templateId?: number;
params?: Record<string, any>;
tags?: string[];
}
export interface TransactionalSMS {
sender: string;
recipient: string;
content: string;
type?: 'transactional' | 'marketing';
tag?: string;
webUrl?: string;
}
// Templates
export interface EmailTemplate {
id: number;
name: string;
subject?: string;
isActive?: boolean;
testSent?: boolean;
sender?: { name?: string; email?: string; id?: number };
replyTo?: string;
toField?: string;
htmlContent?: string;
createdAt?: string;
modifiedAt?: string;
tag?: string;
attachmentUrl?: string;
}
// Senders
export interface Sender {
id: number;
name: string;
email: string;
active?: boolean;
spf?: boolean;
dkim?: boolean;
}
// Automations/Workflows
export interface Workflow {
id: number;
name: string;
status: 'draft' | 'active' | 'inactive';
createdAt?: string;
modifiedAt?: string;
}
export interface WorkflowStats {
sent: number;
opened: number;
clicked: number;
unsubscribed: number;
hardBounced: number;
softBounced: number;
goal: number;
revenue: number;
}
// SMS Campaigns
export interface SMSCampaign {
id: number;
name: string;
status: 'draft' | 'sent' | 'queued' | 'suspended' | 'inProcess';
content: string;
scheduledAt?: string;
sender: string;
recipients?: {
listIds?: number[];
exclusionListIds?: number[];
};
createdAt?: string;
modifiedAt?: string;
}
// CRM Deals
export interface Deal {
id: string;
name: string;
attributes?: Record<string, any>;
linkedCompaniesIds?: string[];
linkedContactsIds?: number[];
}
export interface Pipeline {
id: string;
name: string;
}
export interface Stage {
id: string;
name: string;
}
// Webhooks
export interface Webhook {
id: number;
url: string;
description?: string;
events: string[];
type: 'marketing' | 'transactional';
createdAt?: string;
modifiedAt?: string;
batched?: boolean;
auth?: {
type: 'bearer' | 'basic';
token?: string;
username?: string;
password?: string;
};
}
// API Response Types
export interface PaginatedResponse<T> {
count?: number;
contacts?: T[];
campaigns?: T[];
lists?: T[];
folders?: T[];
templates?: T[];
senders?: T[];
workflows?: T[];
webhooks?: T[];
deals?: T[];
pipelines?: T[];
limit?: number;
offset?: number;
}
export interface ApiError {
code: string;
message: string;
}

View File

@ -1,14 +1,19 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"module": "Node16",
"moduleResolution": "Node16",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]