MCP 2026-07-28 candidate server for Resend email management
The server demonstrates solid tool definition quality with clear naming conventions, comprehensive descriptions, and properly structured input schemas. All 8 tools follow verb_noun patterns (upsert_contacts, remove_contacts, find_contacts, segments, send, campaigns, subscriptions, templates). Descriptions are detailed and informative (ranging 150-400+ chars), exceeding the 10-1024 char baseline. Input schemas are well-defined with types and descriptions for all parameters. However, output schemas are undocumented in the provided source, only return types are mentioned in descriptions, not formally in JSON Schema. Error handling guidance is mentioned in descriptions but not formalized. Tool compositions are logical and single-responsibility.
View campaign history, check status, or cancel scheduled campaigns. Inputs: - action: 'list' | 'status' | 'cancel' (required) - campaign_id?: string — Required for status/cancel - limit?: number — For list action (default 20, max 100) - cursor?: string — Pagination cursor Actions: - list: Returns recent campaigns/broadcasts - status: Get detailed status of specific campaign - cancel: Cancel a scheduled (not yet sent) campaign Returns: - list: { items: [{id, name?, subject, segment_name, status, sent_at?, recipients_count?}], has_more } - status: { id, status, sent_count?, delivered_count?, opened_count?, failed_count? } - cancel: { id, cancelled: boolean } Status values: draft, scheduled, sending, sent, cancelled
Search and filter contacts in the mailing list. Inputs: - segment?: string — Filter by segment name - email?: string — Find specific contact by exact email - unsubscribed?: boolean — Filter by subscription status - limit?: number — Max results (default 50, max 100) - cursor?: string — Pagination cursor from previous response Returns: { items: [{id, email, first_name?, last_name?, unsubscribed, created_at, properties?}], has_more, cursor? } Next steps: Use returned emails with 'upsert_contacts' to update or 'segments' to modify membership.
Delete contacts from the mailing list by email. Bulk-capable. Inputs: - emails: string[] — Array of email addresses to remove Behavior: - Removes contacts from all segments - Permanent deletion Returns: { results: [{email, ok, error?}], summary: {deleted, failed} }
Output schemas not formally documented. Descriptions mention return structures (e.g., '{ results: [{email, ok, id?, error?}], summary }') but these are prose descriptions, not JSON Schema definitions. LLMs cannot reliably parse prose return structures, formal schema definitions are required for downstream tool chaining and field extraction.
segments tool uses multi-action pattern (action enum: 'list|create|delete|add_contacts|remove_contacts') with conditional parameters. While documented in description, this violates single-responsibility principle, LLMs must reason about which parameters apply to which action. Consider splitting into segments_list, segments_create, segments_delete, segments_add, segments_remove.
Inferred effective spec: 2026-07-28+.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | C | 69 | 2026-07-28+ | v2 |
| 2026-03-09 | D | 55 | - | v1 |
Create, list, delete segments and manage their membership. Inputs: - action: 'list' | 'create' | 'delete' | 'add_contacts' | 'remove_contacts' (required) - name?: string — Segment name (required for create/delete/add_contacts/remove_contacts) - contacts?: string[] — Array of emails (for add_contacts/remove_contacts) Actions: - list: Returns all segments with contact counts - create: Creates new segment with given name - delete: Deletes segment (contacts remain in system) - add_contacts: Adds contacts to segment by email - remove_contacts: Removes contacts from segment Returns varies by action: - list: { items: [{id, name, contact_count, created_at}] } - create/delete: { id, name, ok } - add_contacts/remove_contacts: { results: [{email, ok}], summary } Next steps: Use segment names with 'send' for broadcasts.
Send email to individuals or broadcast to a segment. Inputs (choose one target): - to?: string | string[] — Recipient email(s) for individual send (max 50) - segment?: string — Segment name for broadcast Content (choose one): - body?: string — Email content (\n\n between paragraphs, \n between list items) - template?: string — Template alias/ID (must be published) - variables?: Record<string, string|number> — Template variables Common fields: - subject?: string — Email subject (required unless template provides it) - name?: string — Broadcast name for dashboard (defaults to subject) - from_name?: string — Sender display name - reply_to?: string — Reply-to address - schedule_for?: string — ISO date or natural language ("in 30 minutes"). Broadcasts default to "in 5 minutes" minimum. Behavior: - Individual send (to): Sends immediately or scheduled - Broadcast (segment): Creates and sends campaign to all contacts in segment - Templates: Use {{{VAR}}} placeholders, pass values in variables Returns: - Individual: { id, to, subject, status } - Broadcast: { campaign_id, segment, recipients_count, status } Next steps: Use 'campaigns' to track delivery status.
Manage contact opt-in/opt-out preferences for email topics. Inputs: - emails: string | string[] — Contact email(s) to update - action: 'subscribe' | 'unsubscribe' | 'unsubscribe_all' (required) - topic?: string — Topic name (required for subscribe/unsubscribe, not for unsubscribe_all) Behavior: - subscribe: Opts contact into receiving emails for topic - unsubscribe: Opts contact out of specific topic - unsubscribe_all: Sets global unsubscribe (no broadcasts) Returns: { results: [{email, ok, error?}], summary: {success, failed} } Next steps: Verify with 'find_contacts' to see updated subscription status.
List available email templates and their required variables. Inputs: - limit?: number — Max results (default 20, max 100) - cursor?: string — Pagination cursor Returns: { items: [{id, alias?, name, subject?, from?, variables?: [{key, type, fallback?}], created_at}], has_more } Notes: - Only published templates can be used with 'send' - Use 'alias' or 'id' as the template parameter in 'send' - Pass variable values in 'send' variables parameter Next steps: Use template alias with 'send' tool.
Add or update contacts in the mailing list. Bulk-capable. Inputs: - contacts: Array<{email (required), first_name?, last_name?, properties?: Record<string, string|number>}> - segments?: string[] — Segment names to add all contacts to - unsubscribed?: boolean — Set subscription status for all contacts Behavior: - Creates new contacts if email doesn't exist - Updates existing contacts if email matches - Automatically adds to specified segments Returns: { results: [{email, ok, id?, error?}], summary: {created, updated, failed} } Next steps: Use 'find_contacts' to verify, or 'segments' to manage membership.
send tool parameter 'to' accepts both string and array but input schema shows type: ['string', 'array']. This union type requires LLM disambiguation, either require array always (convert single string to ['email']) or split into send_to_individual and send_broadcast.
Pagination consistency: find_contacts and templates support cursor-based pagination with limit parameters, but limit is numeric (1-100) without enforcement at schema level. campaigns also supports pagination. No documented max_results fields in responses, making it unclear if truncation occurred.
Error handling is mentioned descriptively ('Returns { results: [{email, ok, error?}]...}') but no formalized error response schema is documented. LLMs need structured error categories (retryable vs user-fixable) and actionable next steps, not just the presence of an 'error' field.
send tool supports both 'body' (raw text) and 'template' (alias/ID) but description does not clearly state these are mutually exclusive or define precedence. If both are provided, which wins? This dependency should be explicit in parameter descriptions.