Complete Brevo MCP server: 55+ tools, 14 apps, full API coverage
This commit is contained in:
parent
7631226a36
commit
1dd639f67f
@ -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
|
||||
|
||||
[](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)
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
97
servers/brevo/src/apps/automation-dashboard.ts
Normal file
97
servers/brevo/src/apps/automation-dashboard.ts
Normal 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; }
|
||||
`,
|
||||
},
|
||||
};
|
||||
}
|
||||
98
servers/brevo/src/apps/campaign-builder.ts
Normal file
98
servers/brevo/src/apps/campaign-builder.ts
Normal 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; }
|
||||
`,
|
||||
},
|
||||
};
|
||||
}
|
||||
77
servers/brevo/src/apps/campaign-dashboard.ts
Normal file
77
servers/brevo/src/apps/campaign-dashboard.ts
Normal 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; }
|
||||
`,
|
||||
},
|
||||
};
|
||||
}
|
||||
63
servers/brevo/src/apps/contact-dashboard.ts
Normal file
63
servers/brevo/src/apps/contact-dashboard.ts
Normal 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; }
|
||||
`,
|
||||
},
|
||||
};
|
||||
}
|
||||
84
servers/brevo/src/apps/contact-detail.ts
Normal file
84
servers/brevo/src/apps/contact-detail.ts
Normal 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; }
|
||||
`,
|
||||
},
|
||||
};
|
||||
}
|
||||
78
servers/brevo/src/apps/contact-grid.ts
Normal file
78
servers/brevo/src/apps/contact-grid.ts
Normal 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; }
|
||||
`,
|
||||
},
|
||||
};
|
||||
}
|
||||
66
servers/brevo/src/apps/deal-pipeline.ts
Normal file
66
servers/brevo/src/apps/deal-pipeline.ts
Normal 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; }
|
||||
`,
|
||||
},
|
||||
};
|
||||
}
|
||||
145
servers/brevo/src/apps/import-wizard.ts
Normal file
145
servers/brevo/src/apps/import-wizard.ts
Normal 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; }
|
||||
`,
|
||||
},
|
||||
};
|
||||
}
|
||||
132
servers/brevo/src/apps/list-manager.ts
Normal file
132
servers/brevo/src/apps/list-manager.ts
Normal 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; }
|
||||
`,
|
||||
},
|
||||
};
|
||||
}
|
||||
144
servers/brevo/src/apps/report-dashboard.ts
Normal file
144
servers/brevo/src/apps/report-dashboard.ts
Normal 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; }
|
||||
`,
|
||||
},
|
||||
};
|
||||
}
|
||||
119
servers/brevo/src/apps/sms-dashboard.ts
Normal file
119
servers/brevo/src/apps/sms-dashboard.ts
Normal 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; }
|
||||
`,
|
||||
},
|
||||
};
|
||||
}
|
||||
94
servers/brevo/src/apps/template-gallery.ts
Normal file
94
servers/brevo/src/apps/template-gallery.ts
Normal 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; }
|
||||
`,
|
||||
},
|
||||
};
|
||||
}
|
||||
118
servers/brevo/src/apps/transactional-monitor.ts
Normal file
118
servers/brevo/src/apps/transactional-monitor.ts
Normal 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; }
|
||||
`,
|
||||
},
|
||||
};
|
||||
}
|
||||
149
servers/brevo/src/apps/webhook-manager.ts
Normal file
149
servers/brevo/src/apps/webhook-manager.ts
Normal 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; }
|
||||
`,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -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
23
servers/brevo/src/main.ts
Normal 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
205
servers/brevo/src/server.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
77
servers/brevo/src/tools/automations-tools.ts
Normal file
77
servers/brevo/src/tools/automations-tools.ts
Normal 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`);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
188
servers/brevo/src/tools/campaigns-tools.ts
Normal file
188
servers/brevo/src/tools/campaigns-tools.ts
Normal 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,
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
239
servers/brevo/src/tools/contacts-tools.ts
Normal file
239
servers/brevo/src/tools/contacts-tools.ts
Normal 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,
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
122
servers/brevo/src/tools/deals-tools.ts
Normal file
122
servers/brevo/src/tools/deals-tools.ts
Normal 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 || [],
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
105
servers/brevo/src/tools/lists-tools.ts
Normal file
105
servers/brevo/src/tools/lists-tools.ts
Normal 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,
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
109
servers/brevo/src/tools/senders-tools.ts
Normal file
109
servers/brevo/src/tools/senders-tools.ts
Normal 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,
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
113
servers/brevo/src/tools/sms-tools.ts
Normal file
113
servers/brevo/src/tools/sms-tools.ts
Normal 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`);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
124
servers/brevo/src/tools/templates-tools.ts
Normal file
124
servers/brevo/src/tools/templates-tools.ts
Normal 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,
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
139
servers/brevo/src/tools/transactional-tools.ts
Normal file
139
servers/brevo/src/tools/transactional-tools.ts
Normal 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);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
103
servers/brevo/src/tools/webhooks-tools.ts
Normal file
103
servers/brevo/src/tools/webhooks-tools.ts
Normal 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}`);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
92
servers/brevo/src/types/api-client.ts
Normal file
92
servers/brevo/src/types/api-client.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
272
servers/brevo/src/types/index.ts
Normal file
272
servers/brevo/src/types/index.ts
Normal 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;
|
||||
}
|
||||
@ -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"]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user