SendGrid MCP Server
by Garoth
- src
- services
import { Client } from '@sendgrid/client';
import sgMail from '@sendgrid/mail';
import { SendGridContact, SendGridList, SendGridTemplate, SendGridStats, SendGridSingleSend } from '../types/index.js';
export class SendGridService {
private client: Client;
constructor(apiKey: string) {
this.client = new Client();
this.client.setApiKey(apiKey);
sgMail.setApiKey(apiKey);
}
// Email Sending
async sendEmail(params: {
to: string;
from: string;
subject: string;
text: string;
html?: string;
template_id?: string;
dynamic_template_data?: Record<string, any>;
}) {
return await sgMail.send(params);
}
// Contact Management
async deleteContactsByEmails(emails: string[]): Promise<void> {
// First get the contact IDs for the emails
const [searchResponse] = await this.client.request({
method: 'POST',
url: '/v3/marketing/contacts/search',
body: {
query: `email IN (${emails.map(email => `'${email}'`).join(',')})`
}
});
const contacts = (searchResponse.body as { result: SendGridContact[] }).result || [];
const contactIds = contacts.map(contact => contact.id).filter(id => id) as string[];
if (contactIds.length > 0) {
// Then delete the contacts by their IDs
await this.client.request({
method: 'DELETE',
url: '/v3/marketing/contacts',
qs: {
ids: contactIds.join(',')
}
});
}
}
async listAllContacts(): Promise<SendGridContact[]> {
const [response] = await this.client.request({
method: 'POST',
url: '/v3/marketing/contacts/search',
body: {
query: "email IS NOT NULL" // Get all contacts that have an email
}
});
return (response.body as { result: SendGridContact[] }).result || [];
}
async addContact(contact: SendGridContact) {
const [response] = await this.client.request({
method: 'PUT',
url: '/v3/marketing/contacts',
body: {
contacts: [contact]
}
});
return response;
}
async getContactsByList(listId: string): Promise<SendGridContact[]> {
const [response] = await this.client.request({
method: 'POST',
url: '/v3/marketing/contacts/search',
body: {
query: `CONTAINS(list_ids, '${listId}')`
}
});
return (response.body as { result: SendGridContact[] }).result || [];
}
async getList(listId: string): Promise<SendGridList> {
const [response] = await this.client.request({
method: 'GET',
url: `/v3/marketing/lists/${listId}`
});
return response.body as SendGridList;
}
async listContactLists(): Promise<SendGridList[]> {
const [response] = await this.client.request({
method: 'GET',
url: '/v3/marketing/lists'
});
return (response.body as { result: SendGridList[] }).result;
}
async deleteList(listId: string): Promise<void> {
await this.client.request({
method: 'DELETE',
url: `/v3/marketing/lists/${listId}`
});
}
async createList(name: string): Promise<SendGridList> {
const [response] = await this.client.request({
method: 'POST',
url: '/v3/marketing/lists',
body: { name }
});
return response.body as SendGridList;
}
async addContactsToList(listId: string, contactEmails: string[]) {
const [response] = await this.client.request({
method: 'PUT',
url: '/v3/marketing/contacts',
body: {
list_ids: [listId],
contacts: contactEmails.map(email => ({ email }))
}
});
return response;
}
async removeContactsFromList(listId: string, contactEmails: string[]) {
// First get the contact IDs for the emails
const [searchResponse] = await this.client.request({
method: 'POST',
url: '/v3/marketing/contacts/search',
body: {
query: `email IN (${contactEmails.map(email => `'${email}'`).join(',')}) AND CONTAINS(list_ids, '${listId}')`
}
});
const contacts = (searchResponse.body as { result: SendGridContact[] }).result || [];
const contactIds = contacts.map(contact => contact.id).filter(id => id) as string[];
if (contactIds.length > 0) {
// Remove the contacts from the list
await this.client.request({
method: 'DELETE',
url: `/v3/marketing/lists/${listId}/contacts`,
qs: {
contact_ids: contactIds.join(',')
}
});
}
}
// Template Management
async createTemplate(params: {
name: string;
html_content: string;
plain_content: string;
subject: string;
}): Promise<SendGridTemplate> {
const [response] = await this.client.request({
method: 'POST',
url: '/v3/templates',
body: {
name: params.name,
generation: 'dynamic'
}
});
const templateId = (response.body as { id: string }).id;
// Create the first version of the template
const [versionResponse] = await this.client.request({
method: 'POST',
url: `/v3/templates/${templateId}/versions`,
body: {
template_id: templateId,
name: `${params.name} v1`,
subject: params.subject,
html_content: params.html_content,
plain_content: params.plain_content,
active: 1
}
});
return {
id: templateId,
name: params.name,
generation: 'dynamic',
updated_at: new Date().toISOString(),
versions: [{
id: (versionResponse.body as { id: string }).id,
template_id: templateId,
active: 1,
name: `${params.name} v1`,
html_content: params.html_content,
plain_content: params.plain_content,
subject: params.subject
}]
};
}
async listTemplates(): Promise<SendGridTemplate[]> {
const [response] = await this.client.request({
method: 'GET',
url: '/v3/templates',
qs: {
generations: 'dynamic'
}
});
return ((response.body as { templates: SendGridTemplate[] }).templates || []);
}
async getTemplate(templateId: string): Promise<SendGridTemplate> {
const [response] = await this.client.request({
method: 'GET',
url: `/v3/templates/${templateId}`
});
return response.body as SendGridTemplate;
}
async deleteTemplate(templateId: string): Promise<void> {
await this.client.request({
method: 'DELETE',
url: `/v3/templates/${templateId}`
});
}
// Email Validation
async validateEmail(email: string) {
const [response] = await this.client.request({
method: 'POST',
url: '/v3/validations/email',
body: { email }
});
return response.body;
}
// Statistics
async getStats(params: {
start_date: string;
end_date?: string;
aggregated_by?: 'day' | 'week' | 'month';
}): Promise<SendGridStats> {
const [response] = await this.client.request({
method: 'GET',
url: '/v3/stats',
qs: params
});
return response.body as SendGridStats;
}
// Single Sends (New Marketing Campaigns API)
async createSingleSend(params: {
name: string;
send_to: { list_ids: string[] };
email_config: {
subject: string;
html_content: string;
plain_content: string;
sender_id: number;
suppression_group_id?: number;
custom_unsubscribe_url?: string;
};
}): Promise<{ id: string }> {
const [response] = await this.client.request({
method: 'POST',
url: '/v3/marketing/singlesends',
body: params
});
return response.body as { id: string };
}
async scheduleSingleSend(singleSendId: string, sendAt: 'now' | string) {
const [response] = await this.client.request({
method: 'PUT',
url: `/v3/marketing/singlesends/${singleSendId}/schedule`,
body: {
send_at: sendAt
}
});
return response.body;
}
async getSingleSend(singleSendId: string): Promise<SendGridSingleSend> {
const [response] = await this.client.request({
method: 'GET',
url: `/v3/marketing/singlesends/${singleSendId}`
});
return response.body as SendGridSingleSend;
}
async listSingleSends(): Promise<SendGridSingleSend[]> {
const [response] = await this.client.request({
method: 'GET',
url: '/v3/marketing/singlesends'
});
return (response.body as { result: SendGridSingleSend[] }).result || [];
}
// Suppression Groups
async getSuppressionGroups() {
const [response] = await this.client.request({
method: 'GET',
url: '/v3/asm/groups'
});
return response.body;
}
// Verified Senders
async getVerifiedSenders() {
const [response] = await this.client.request({
method: 'GET',
url: '/v3/verified_senders'
});
return response.body;
}
}