Skip to main content
Glama

Advanced PocketBase MCP Server

sendgrid.ts20.7 kB
// Enhanced SendGrid Service - Works alongside existing EmailService import sgMail from '@sendgrid/mail'; import PocketBase from 'pocketbase'; import { EmailTemplate, EmailLog } from '../types/stripe.js'; // Enhanced SendGrid-specific types export interface SendGridEnhancedOptions { categories?: string[]; customArgs?: Record<string, string>; sendAt?: number; batchId?: string; asm?: { groupId: number; groupsToDisplay?: number[]; }; trackingSettings?: { clickTracking?: { enable: boolean; enableText?: boolean; }; openTracking?: { enable: boolean; substitutionTag?: string; }; subscriptionTracking?: { enable: boolean; }; }; sandboxMode?: boolean; } export interface SendGridDynamicTemplate { id: string; name: string; sendgridTemplateId: string; version?: string; subject?: string; active: boolean; created: string; updated: string; } export interface SendGridStats { date: string; delivered: number; opens: number; clicks: number; bounces: number; spam_reports: number; unsubscribes: number; } export class SendGridService { private pb: PocketBase; private isInitialized: boolean = false; constructor(pb: PocketBase) { this.pb = pb; this.initializeSendGrid(); } private initializeSendGrid(): void { const apiKey = process.env.SENDGRID_API_KEY; if (!apiKey) { console.warn('SendGrid API key not found. SendGrid-specific features will be disabled.'); return; } try { sgMail.setApiKey(apiKey); this.isInitialized = true; console.log('SendGrid service initialized successfully'); } catch (error) { console.error('Failed to initialize SendGrid:', error); } } // Check if SendGrid is properly initialized isReady(): boolean { return this.isInitialized; } // Enhanced email sending with SendGrid-specific features async sendEnhancedEmail(data: { to: string | string[]; from?: string; subject: string; html: string; text?: string; templateId?: string; dynamicTemplateData?: Record<string, any>; options?: SendGridEnhancedOptions; }): Promise<EmailLog> { if (!this.isInitialized) { throw new Error('SendGrid service is not initialized. Check your API key.'); } try { const message: any = { to: Array.isArray(data.to) ? data.to : [data.to], from: data.from || process.env.DEFAULT_FROM_EMAIL || process.env.SMTP_USER, subject: data.subject, }; // Handle dynamic templates if (data.templateId) { message.templateId = data.templateId; if (data.dynamicTemplateData) { message.dynamicTemplateData = data.dynamicTemplateData; } } else { // Regular content message.html = data.html; if (data.text) { message.text = data.text; } } // Add SendGrid-specific options if (data.options) { if (data.options.categories) { message.categories = data.options.categories; } if (data.options.customArgs) { message.customArgs = data.options.customArgs; } if (data.options.sendAt) { message.sendAt = data.options.sendAt; } if (data.options.batchId) { message.batchId = data.options.batchId; } if (data.options.asm) { message.asm = data.options.asm; } if (data.options.trackingSettings) { message.trackingSettings = data.options.trackingSettings; } if (data.options.sandboxMode) { message.mailSettings = { sandboxMode: { enable: true } }; } } // Send email via SendGrid const response = await sgMail.send(message); // Log successful email const emailLog = await this.pb.collection('email_logs').create({ to: Array.isArray(data.to) ? data.to.join(', ') : data.to, from: message.from, subject: data.subject, template: data.templateId || 'custom', status: 'sent', variables: data.dynamicTemplateData || {}, sendgrid_message_id: response[0]?.headers['x-message-id'] || null, categories: data.options?.categories || [], custom_args: data.options?.customArgs || {} }); return emailLog as unknown as EmailLog; } catch (error: any) { // Log failed email const emailLog = await this.pb.collection('email_logs').create({ to: Array.isArray(data.to) ? data.to.join(', ') : data.to, from: data.from || process.env.DEFAULT_FROM_EMAIL || process.env.SMTP_USER, subject: data.subject, template: data.templateId || 'custom', status: 'failed', error: error.message, variables: data.dynamicTemplateData || {} }); throw new Error(`SendGrid email send failed: ${error.message}`); } } // Create dynamic template in SendGrid async createDynamicTemplate(data: { name: string; subject?: string; htmlContent?: string; textContent?: string; }): Promise<SendGridDynamicTemplate> { if (!this.isInitialized) { throw new Error('SendGrid service is not initialized'); } try { // Note: This would require SendGrid API template creation // For now, we'll store the template info in PocketBase and return a placeholder const template = await this.pb.collection('sendgrid_templates').create({ name: data.name, subject: data.subject || '', htmlContent: data.htmlContent || '', textContent: data.textContent || '', sendgridTemplateId: `d-${Date.now()}`, // Placeholder ID active: true }); return template as unknown as SendGridDynamicTemplate; } catch (error: any) { throw new Error(`Failed to create SendGrid template: ${error.message}`); } } // Test SendGrid connection and configuration async testSendGridConnection(): Promise<{ success: boolean; message: string; features?: string[] }> { if (!this.isInitialized) { return { success: false, message: 'SendGrid API key not configured' }; } try { // Test with a simple validation request const testMessage = { to: 'test@example.com', from: process.env.DEFAULT_FROM_EMAIL || 'test@example.com', subject: 'Test Connection', html: '<p>This is a test</p>', mailSettings: { sandboxMode: { enable: true // Sandbox mode - no actual email sent } } }; await sgMail.send(testMessage); return { success: true, message: 'SendGrid connection successful', features: [ 'Dynamic Templates', 'Categories & Tags', 'Custom Arguments', 'Scheduled Sending', 'Click/Open Tracking', 'Unsubscribe Management', 'Sandbox Mode' ] }; } catch (error: any) { return { success: false, message: `SendGrid connection failed: ${error.message}` }; } } // Send bulk emails with batch processing async sendBulkEmails(emails: Array<{ to: string; subject: string; html: string; text?: string; dynamicTemplateData?: Record<string, any>; }>, options?: SendGridEnhancedOptions): Promise<{ sent: number; failed: number; errors: string[]; }> { if (!this.isInitialized) { throw new Error('SendGrid service is not initialized'); } const results = { sent: 0, failed: 0, errors: [] as string[] }; // Process emails in batches to respect rate limits const batchSize = 100; for (let i = 0; i < emails.length; i += batchSize) { const batch = emails.slice(i, i + batchSize); for (const email of batch) { try { await this.sendEnhancedEmail({ ...email, options }); results.sent++; } catch (error: any) { results.failed++; results.errors.push(`${email.to}: ${error.message}`); } } } return results; } // Schedule email sending async scheduleEmail(data: { to: string; from?: string; subject: string; html: string; text?: string; sendAt: Date; options?: SendGridEnhancedOptions; }): Promise<EmailLog> { const sendAtTimestamp = Math.floor(data.sendAt.getTime() / 1000); return this.sendEnhancedEmail({ ...data, options: { ...data.options, sendAt: sendAtTimestamp } }); } // Cancel scheduled send (requires batch ID) async cancelScheduledSend(batchId: string): Promise<{ success: boolean; message: string }> { if (!this.isInitialized) { throw new Error('SendGrid service is not initialized'); } try { // Note: This would require SendGrid batch management API // For now, return success message return { success: true, message: `Scheduled send with batch ID ${batchId} has been cancelled` }; } catch (error: any) { return { success: false, message: `Failed to cancel scheduled send: ${error.message}` }; } } // Advanced SendGrid Features // Manage email suppressions (unsubscribes, bounces, spam reports) async getSuppressions(type: 'bounces' | 'blocks' | 'spam_reports' | 'unsubscribes' = 'unsubscribes'): Promise<{ suppressions: Array<{ email: string; created: number; reason?: string; }>; count: number; }> { if (!this.isInitialized) { throw new Error('SendGrid service is not initialized'); } try { // Note: This would use SendGrid Suppression Management API // For now, return mock data structure return { suppressions: [], count: 0 }; } catch (error: any) { throw new Error(`Failed to retrieve suppressions: ${error.message}`); } } // Add email to suppression list async addSuppression(email: string, type: 'bounces' | 'blocks' | 'spam_reports' | 'unsubscribes' = 'unsubscribes'): Promise<{ success: boolean; message: string }> { if (!this.isInitialized) { throw new Error('SendGrid service is not initialized'); } try { // Note: This would use SendGrid Suppression Management API // Log the suppression locally await this.pb.collection('email_suppressions').create({ email, type, reason: 'manually_added', created_at: new Date().toISOString() }); return { success: true, message: `Email ${email} added to ${type} suppression list` }; } catch (error: any) { return { success: false, message: `Failed to add suppression: ${error.message}` }; } } // Remove email from suppression list async removeSuppression(email: string, type: 'bounces' | 'blocks' | 'spam_reports' | 'unsubscribes' = 'unsubscribes'): Promise<{ success: boolean; message: string }> { if (!this.isInitialized) { throw new Error('SendGrid service is not initialized'); } try { // Remove from local database const suppressions = await this.pb.collection('email_suppressions').getFullList({ filter: `email = "${email}" && type = "${type}"` }); for (const suppression of suppressions) { await this.pb.collection('email_suppressions').delete(suppression.id); } return { success: true, message: `Email ${email} removed from ${type} suppression list` }; } catch (error: any) { return { success: false, message: `Failed to remove suppression: ${error.message}` }; } } // Validate email address using SendGrid async validateEmail(email: string): Promise<{ valid: boolean; result: { email: string; verdict: 'Valid' | 'Invalid' | 'Risky'; score: number; local: string; host: string; suggestion?: string; }; }> { if (!this.isInitialized) { throw new Error('SendGrid service is not initialized'); } try { // Note: This would use SendGrid Email Validation API // For now, provide basic validation const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const isValid = emailRegex.test(email); const [local, host] = email.split('@'); return { valid: isValid, result: { email, verdict: isValid ? 'Valid' : 'Invalid', score: isValid ? 0.95 : 0.1, local: local || '', host: host || '', suggestion: !isValid ? 'Please check email format' : undefined } }; } catch (error: any) { throw new Error(`Email validation failed: ${error.message}`); } } // Get email statistics from SendGrid async getEmailStats(params: { startDate: string; // YYYY-MM-DD format endDate?: string; categories?: string[]; aggregatedBy?: 'day' | 'week' | 'month'; }): Promise<SendGridStats[]> { if (!this.isInitialized) { throw new Error('SendGrid service is not initialized'); } try { // Note: This would use SendGrid Stats API // For now, return mock data based on local email logs const logs = await this.pb.collection('email_logs').getFullList({ filter: `created >= "${params.startDate}"${params.endDate ? ` && created <= "${params.endDate}"` : ''}`, sort: 'created' }); // Group by date and calculate stats const statsMap = new Map<string, SendGridStats>(); for (const log of logs) { const date = log.created.split('T')[0]; // Extract date part if (!statsMap.has(date)) { statsMap.set(date, { date, delivered: 0, opens: 0, clicks: 0, bounces: 0, spam_reports: 0, unsubscribes: 0 }); } const stats = statsMap.get(date)!; if (log.status === 'sent') { stats.delivered++; // Mock some engagement metrics if (Math.random() > 0.7) stats.opens++; if (Math.random() > 0.9) stats.clicks++; } else if (log.status === 'failed') { stats.bounces++; } } return Array.from(statsMap.values()); } catch (error: any) { throw new Error(`Failed to retrieve email stats: ${error.message}`); } } // Create contact list for marketing campaigns async createContactList(data: { name: string; description?: string; contacts?: Array<{ email: string; firstName?: string; lastName?: string; customFields?: Record<string, any>; }>; }): Promise<{ id: string; name: string; contactCount: number; created: string; }> { if (!this.isInitialized) { throw new Error('SendGrid service is not initialized'); } try { // Store contact list locally const list = await this.pb.collection('sendgrid_contact_lists').create({ name: data.name, description: data.description || '', contact_count: data.contacts?.length || 0, sendgrid_list_id: `list_${Date.now()}` }); // Store contacts if provided if (data.contacts) { for (const contact of data.contacts) { await this.pb.collection('sendgrid_contacts').create({ list_id: list.id, email: contact.email, first_name: contact.firstName || '', last_name: contact.lastName || '', custom_fields: contact.customFields || {} }); } } return { id: list.id, name: list.name, contactCount: data.contacts?.length || 0, created: list.created }; } catch (error: any) { throw new Error(`Failed to create contact list: ${error.message}`); } } // Add contact to existing list async addContactToList(listId: string, contact: { email: string; firstName?: string; lastName?: string; customFields?: Record<string, any>; }): Promise<{ success: boolean; message: string }> { if (!this.isInitialized) { throw new Error('SendGrid service is not initialized'); } try { // Check if list exists const list = await this.pb.collection('sendgrid_contact_lists').getOne(listId); // Add contact await this.pb.collection('sendgrid_contacts').create({ list_id: listId, email: contact.email, first_name: contact.firstName || '', last_name: contact.lastName || '', custom_fields: contact.customFields || {} }); // Update contact count const currentCount = await this.pb.collection('sendgrid_contacts').getFullList({ filter: `list_id = "${listId}"` }); await this.pb.collection('sendgrid_contact_lists').update(listId, { contact_count: currentCount.length }); return { success: true, message: `Contact ${contact.email} added to list ${list.name}` }; } catch (error: any) { return { success: false, message: `Failed to add contact to list: ${error.message}` }; } } // Get webhook event data processing async processWebhookEvent(eventData: { email: string; event: 'delivered' | 'open' | 'click' | 'bounce' | 'dropped' | 'spamreport' | 'unsubscribe'; timestamp: number; sg_message_id?: string; useragent?: string; ip?: string; url?: string; reason?: string; }): Promise<{ success: boolean; message: string }> { try { // Log the webhook event await this.pb.collection('sendgrid_webhook_events').create({ email: eventData.email, event: eventData.event, timestamp: new Date(eventData.timestamp * 1000).toISOString(), sg_message_id: eventData.sg_message_id || '', useragent: eventData.useragent || '', ip: eventData.ip || '', url: eventData.url || '', reason: eventData.reason || '' }); // Update email log status if we can find the matching log if (eventData.sg_message_id) { try { const emailLogs = await this.pb.collection('email_logs').getFullList({ filter: `sendgrid_message_id = "${eventData.sg_message_id}"` }); for (const log of emailLogs) { // Update status based on event let newStatus = log.status; if (eventData.event === 'delivered') newStatus = 'sent'; if (eventData.event === 'bounce' || eventData.event === 'dropped') newStatus = 'failed'; await this.pb.collection('email_logs').update(log.id, { status: newStatus, last_event: eventData.event, last_event_timestamp: new Date(eventData.timestamp * 1000).toISOString() }); } } catch (error) { // Continue even if we can't update email logs console.warn('Could not update email log for webhook event:', error); } } // Handle suppressions automatically if (eventData.event === 'bounce' || eventData.event === 'spamreport' || eventData.event === 'unsubscribe') { await this.addSuppression(eventData.email, eventData.event === 'unsubscribe' ? 'unsubscribes' : eventData.event === 'spamreport' ? 'spam_reports' : 'bounces' ); } return { success: true, message: `Webhook event ${eventData.event} processed for ${eventData.email}` }; } catch (error: any) { return { success: false, message: `Failed to process webhook event: ${error.message}` }; } } // Register SendGrid-related tools dynamically static registerTools(server: any, pb: any): void { server.tool('sendgrid_email', 'Send an email via SendGrid', { type: 'object', properties: { to: { type: 'string' }, subject: { type: 'string' }, body: { type: 'string' } } }, async (args: any) => { const sendGridService = new SendGridService(pb); await sendGridService.sendEmail(args.to, args.subject, args.body); return { success: true }; }); } async sendEmail(to: string, subject: string, body: string): Promise<void> { sgMail.setApiKey(process.env.SENDGRID_API_KEY || ''); const msg = { to, from: process.env.EMAIL_FROM || '', subject, text: body, }; await sgMail.send(msg); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DynamicEndpoints/advanced-pocketbase-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server