Skip to main content
Glama
bcharleson

Instantly MCP Server

update_account

Modify email account settings including daily sending limits and warmup configuration for improved email deliverability and campaign management.

Instructions

Update a sending account

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
daily_limitNoNew daily sending limit
emailYesEmail address of the account to update
warmup_enabledNoEnable/disable warmup

Implementation Reference

  • The core handler implementation for the 'update_account' tool. Builds a partial update object from input arguments (first_name, last_name, warmup config, daily_limit, sending_gap, tracking domain settings, etc.) and performs a PATCH request to the Instantly API endpoint `/accounts/{email}`.
    case 'update_account': {
      console.error('[Instantly MCP] 🔧 Executing update_account...');
    
      if (!args?.email) {
        throw new McpError(ErrorCode.InvalidParams, 'email is required');
      }
    
      // Build account update data with all provided parameters (excluding email from body)
      const updateData: any = {};
    
      // Add all optional parameters if provided
      if (args.first_name !== undefined) updateData.first_name = args.first_name;
      if (args.last_name !== undefined) updateData.last_name = args.last_name;
      if (args.warmup !== undefined) updateData.warmup = args.warmup;
      if (args.daily_limit !== undefined) updateData.daily_limit = args.daily_limit;
      if (args.tracking_domain_name !== undefined) updateData.tracking_domain_name = args.tracking_domain_name;
      if (args.tracking_domain_status !== undefined) updateData.tracking_domain_status = args.tracking_domain_status;
      if (args.enable_slow_ramp !== undefined) updateData.enable_slow_ramp = args.enable_slow_ramp;
      if (args.inbox_placement_test_limit !== undefined) updateData.inbox_placement_test_limit = args.inbox_placement_test_limit;
      if (args.sending_gap !== undefined) updateData.sending_gap = args.sending_gap;
      if (args.skip_cname_check !== undefined) updateData.skip_cname_check = args.skip_cname_check;
      if (args.remove_tracking_domain !== undefined) updateData.remove_tracking_domain = args.remove_tracking_domain;
    
      console.error(`[Instantly MCP] 📤 Updating account with data: ${JSON.stringify(updateData, null, 2)}`);
    
      const result = await makeInstantlyRequest(`/accounts/${encodeURIComponent(args.email)}`, {
        method: 'PATCH',
        body: updateData
      }, apiKey);
    
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              success: true,
              account: result,
              message: 'Account updated successfully'
            }, null, 2),
          },
        ],
      };
    }
  • Tool registration/definition including name, description, annotations, and detailed inputSchema used by MCP server.
    {
      name: 'update_account',
      title: 'Update Account',
      description: 'Update account settings (partial). Supports name, warmup, daily_limit, sending_gap, tracking_domain.',
      annotations: { destructiveHint: false },
      inputSchema: {
        type: 'object',
        properties: {
          email: { type: 'string', description: 'Account to update' },
          first_name: { type: 'string' },
          last_name: { type: 'string' },
          warmup: {
            type: 'object',
            properties: {
              limit: { type: 'number' },
              advanced: {
                type: 'object',
                properties: {
                  warm_ctd: { type: 'boolean' },
                  open_rate: { type: 'number' },
                  important_rate: { type: 'number' },
                  read_emulation: { type: 'boolean' },
                  spam_save_rate: { type: 'number' },
                  weekday_only: { type: 'boolean' }
                }
              },
              warmup_custom_ftag: { type: 'string' },
              increment: { type: 'string' },
              reply_rate: { type: 'number' }
            }
          },
          daily_limit: { type: 'number' },
          sending_gap: { type: 'number', description: 'Minutes between emails (0-1440)' },
          enable_slow_ramp: { type: 'boolean' },
          tracking_domain_name: { type: 'string' },
          tracking_domain_status: { type: 'string' },
          skip_cname_check: { type: 'boolean' },
          remove_tracking_domain: { type: 'boolean' },
          inbox_placement_test_limit: { type: 'number' }
        },
        required: ['email']
      }
    },
  • Zod schema (UpdateAccountSchema) and validator function (validateUpdateAccountData) for input validation of update_account tool arguments.
    export const UpdateAccountSchema = z.object({
      email: EmailSchema,
      first_name: z.string().optional(),
      last_name: z.string().optional(),
      warmup: z.object({
        limit: z.number().optional(),
        advanced: z.object({
          warm_ctd: z.boolean().optional(),
          open_rate: z.number().optional(),
          important_rate: z.number().optional(),
          read_emulation: z.boolean().optional(),
          spam_save_rate: z.number().optional(),
          weekday_only: z.boolean().optional()
        }).optional(),
        warmup_custom_ftag: z.string().optional(),
        increment: z.string().optional(),
        reply_rate: z.number().optional()
      }).optional(),
      daily_limit: z.number().int().min(1).max(1000).nullable().optional(),
      tracking_domain_name: z.string().nullable().optional(),
      tracking_domain_status: z.string().nullable().optional(),
      enable_slow_ramp: z.boolean().nullable().optional(),
      inbox_placement_test_limit: z.number().nullable().optional(),
      sending_gap: z.number().min(0).max(1440).optional(),
      skip_cname_check: z.boolean().optional(),
      remove_tracking_domain: z.boolean().optional()
    });
    
    /**
     * Lead creation validation schema - Updated to match Instantly.ai API v2 specification
     */
    export const CreateLeadSchema = z.object({
      // Core lead information
      campaign: z.string().optional(),
      email: z.string().email().optional(),
      first_name: z.string().optional(),
      last_name: z.string().optional(),
      company_name: z.string().optional(),
      phone: z.string().optional(),
      website: z.string().url().optional(),
      personalization: z.string().optional(),
    
      // Advanced parameters
      lt_interest_status: z.number().int().min(-3).max(4).optional(),
      pl_value_lead: z.string().optional(),
      list_id: z.string().optional(),
      assigned_to: z.string().optional(),
    
      // Skip conditions
      skip_if_in_workspace: z.boolean().optional(),
      skip_if_in_campaign: z.boolean().optional(),
      skip_if_in_list: z.boolean().optional(),
    
      // Verification and blocklist
      blocklist_id: z.string().optional(),
      verify_leads_for_lead_finder: z.boolean().optional(),
      verify_leads_on_import: z.boolean().optional(),
    
      // Custom variables
      custom_variables: z.record(z.string(), z.any()).optional()
    });
    
    /**
     * Update lead validation schema - Updated to match Instantly.ai API v2 specification
     */
    export const UpdateLeadSchema = z.object({
      lead_id: z.string().min(1, { message: 'Lead ID cannot be empty' }),
      personalization: z.string().optional(),
      website: z.string().url().optional(),
      last_name: z.string().optional(),
      first_name: z.string().optional(),
      company_name: z.string().optional(),
      phone: z.string().optional(),
      lt_interest_status: z.number().int().min(-3).max(4).optional(),
      pl_value_lead: z.string().optional(),
      assigned_to: z.string().optional(),
      custom_variables: z.record(z.string(), z.any()).optional()
    });
    
    /**
     * List leads validation schema
     */
    export const ListLeadsSchema = z.object({
      campaign_id: z.string().optional(),
      list_id: z.string().optional(),
      status: z.string().optional(),
      limit: z.number().int().min(1).max(100).optional(),
      starting_after: z.string().optional()
    });
    
    /**
     * Create lead list validation schema - Updated to match Instantly.ai API v2 specification
     */
    export const CreateLeadListSchema = z.object({
      name: z.string().min(1, { message: 'Lead list name cannot be empty' }),
      has_enrichment_task: z.boolean().optional(),
      owned_by: z.string().optional()
    });
    
    /**
     * List lead lists validation schema
     */
    export const ListLeadListsSchema = z.object({
      limit: z.number().int().min(1).max(100).optional(),
      starting_after: z.string().optional()
    });
    
    /**
     * Reply to email validation schema
     */
    export const ReplyToEmailSchema = z.object({
      reply_to_uuid: z.string()
        .min(1, { message: 'Reply to UUID cannot be empty' })
        .refine(
          (val) => val !== 'test-uuid',
          'reply_to_uuid must be a valid email ID. Use list_emails or get_email tools first to obtain a valid email UUID.'
        ),
      body: z.object({
        html: z.string().optional(),
        text: z.string().optional()
      }).refine(
        (val) => val.html || val.text,
        'Body must contain either html or text content'
      )
    });
    
    /**
     * List emails validation schema
     */
    export const ListEmailsSchema = z.object({
      campaign_id: z.string().optional(),
      account_id: z.string().optional(),
      limit: z.number().int().min(1).max(100).optional(),
      starting_after: z.string().optional()
    });
    
    /**
     * Get email validation schema
     */
    export const GetEmailSchema = z.object({
      email_id: z.string().min(1, { message: 'Email ID cannot be empty' })
    });
    
    /**
     * Validate campaign accounts validation schema
     */
    export const ValidateCampaignAccountsSchema = z.object({
      email_list: z.array(EmailSchema).optional()
    });
    
    /**
     * Get account details validation schema
     */
    export const GetAccountDetailsSchema = z.object({
      email: EmailSchema
    });
    
    // ============================================================================
    // VALIDATION HELPER FUNCTIONS
    // ============================================================================
    
    /**
     * Generic validation function that converts Zod errors to McpError
     */
    export function validateWithSchema<T>(
      schema: z.ZodSchema<T>,
      data: unknown,
      toolName: string
    ): T {
      try {
        return schema.parse(data);
      } catch (error) {
        if (error instanceof z.ZodError) {
          const errorMessages = error.issues.map((err: any) => {
            const path = err.path.length > 0 ? `${err.path.join('.')}: ` : '';
            return `${path}${err.message}`;
          });
          
          throw new McpError(
            ErrorCode.InvalidParams,
            `${toolName} validation failed: ${errorMessages.join('; ')}. Please check your input parameters and try again.`
          );
        }
        
        // Re-throw non-Zod errors
        throw error;
      }
    }
    
    /**
     * Validate campaign creation data with enhanced error messages and contextual guidance
     */
    export function validateCampaignData(args: unknown): z.infer<typeof CreateCampaignSchema> {
      try {
        return validateWithSchema(CreateCampaignSchema, args, 'create_campaign');
      } catch (error: any) {
        // Enhance error messages with contextual guidance
        if (error.message) {
          let enhancedMessage = error.message;
    
          // Add specific guidance for common issues
          if (error.message.includes('email_list') || error.message.includes('Placeholder')) {
            enhancedMessage += '\n\n💡 CRITICAL GUIDANCE: \n1️⃣ Call list_accounts first to see your available sender email addresses\n2️⃣ Only use verified, warmed-up accounts that show status=1 and warmup_status=1\n3️⃣ NEVER use placeholder emails like test@example.com, user@example.com, etc.\n4️⃣ To create ONE campaign with MULTIPLE emails, provide ALL emails in a SINGLE email_list array\n5️⃣ Example: email_list: ["real1@domain.com", "real2@domain.com"] creates ONE campaign with 2 senders';
          }
    
          if (error.message.includes('subject') || error.message.includes('Subject')) {
            enhancedMessage += '\n\n💡 GUIDANCE: Good subject lines are personal and specific. Examples:\n• "{{firstName}}, quick question about {{companyName}}" (48 chars)\n• "Helping {{companyName}} with [specific problem]" (adjust to <50)\n• "{{firstName}}, saw your recent [achievement/news]" (adjust to <50)\n\n⚠️ CRITICAL: Subject line MUST be under 50 characters. If your subject is too long, you MUST shorten it before retrying.';
          }
    
          if (error.message.includes('body') || error.message.includes('Body')) {
            enhancedMessage += '\n\n💡 GUIDANCE: Email body formatting tips:\n• Use \\n for line breaks - they will be automatically converted to <br /> tags for HTML email rendering\n• Double line breaks (\\n\\n) create new paragraphs\n• Personalize with {{firstName}}, {{lastName}}, {{companyName}}\n• Keep it conversational and specific\n• Example: "Hi {{firstName}},\\n\\nI noticed {{companyName}} recently [specific observation].\\n\\nBest regards,\\nYour Name"';
          }
    
          if (error.message.includes('daily_limit')) {
            enhancedMessage += '\n\n💡 GUIDANCE: Daily email limits for cold outreach:\n• Maximum 30 emails per day per account for compliance\n• Higher limits may trigger spam filters\n• Start with lower limits (10-20) for new accounts\n• Gradually increase as account reputation improves';
          }
    
          if (error.message.includes('track_opens') || error.message.includes('track_clicks')) {
            enhancedMessage += '\n\n💡 GUIDANCE: Email tracking considerations:\n• Tracking is disabled by default for better deliverability\n• Many email clients now block tracking pixels\n• Tracking can trigger spam filters and reduce trust\n• Enable only if analytics are absolutely necessary';
          }
    
          if (error.message.includes('name') && error.message.includes('Campaign')) {
            enhancedMessage += '\n\n💡 GUIDANCE: Use descriptive campaign names that help you identify the purpose. Examples:\n• "Q4 Product Launch - Tech Companies"\n• "Holiday Sales Outreach - Existing Customers"\n• "Partnership Inquiry - SaaS Companies"';
          }
    
          error.message = enhancedMessage;
        }
    
        throw error;
      }
    }
    
    /**
     * Validate campaign prerequisite check data with flexible validation
     */
    export function validateCampaignPrerequisiteData(args: unknown): z.infer<typeof CreateCampaignPrerequisiteSchema> {
      return validateWithSchema(CreateCampaignPrerequisiteSchema, args, 'create_campaign_prerequisite');
    }
    
    /**
     * Validate get campaign analytics parameters
     */
    export function validateGetCampaignAnalyticsData(args: unknown): z.infer<typeof GetCampaignAnalyticsSchema> {
      return validateWithSchema(GetCampaignAnalyticsSchema, args, 'get_campaign_analytics');
    }
    
    /**
     * Validate list accounts parameters
     */
    export function validateListAccountsData(args: unknown): z.infer<typeof ListAccountsSchema> {
      return validateWithSchema(ListAccountsSchema, args, 'list_accounts');
    }
    
    /**
     * Validate list campaigns parameters
     */
    export function validateListCampaignsData(args: unknown): z.infer<typeof ListCampaignsSchema> {
      return validateWithSchema(ListCampaignsSchema, args, 'list_campaigns');
    }
    
    /**
     * Validate warmup analytics parameters
     */
    export function validateWarmupAnalyticsData(args: unknown): z.infer<typeof GetWarmupAnalyticsSchema> {
      return validateWithSchema(GetWarmupAnalyticsSchema, args, 'get_warmup_analytics');
    }
    
    /**
     * Validate email verification parameters
     */
    export function validateEmailVerificationData(args: unknown): z.infer<typeof VerifyEmailSchema> {
      return validateWithSchema(VerifyEmailSchema, args, 'verify_email');
    }
    
    /**
     * Validate get campaign parameters
     */
    export function validateGetCampaignData(args: unknown): z.infer<typeof GetCampaignSchema> {
      return validateWithSchema(GetCampaignSchema, args, 'get_campaign');
    }
    
    /**
     * Validate update campaign parameters
     */
    export function validateUpdateCampaignData(args: unknown): z.infer<typeof UpdateCampaignSchema> {
      return validateWithSchema(UpdateCampaignSchema, args, 'update_campaign');
    }
    
    
    
    /**
     * Validate create account parameters
     */
    export function validateCreateAccountData(args: unknown): z.infer<typeof CreateAccountSchema> {
      return validateWithSchema(CreateAccountSchema, args, 'create_account');
    }
    
    /**
     * Validate update account parameters
     */
    export function validateUpdateAccountData(args: unknown): z.infer<typeof UpdateAccountSchema> {
      return validateWithSchema(UpdateAccountSchema, args, 'update_account');
  • Imports and registers accountTools (including update_account) into the main TOOLS_DEFINITION array used by MCP server.
    import { accountTools } from './account-tools.js';
    import { campaignTools } from './campaign-tools.js';
    import { leadTools } from './lead-tools.js';
    import { emailTools } from './email-tools.js';
    import { analyticsTools } from './analytics-tools.js';
    
    // Category mapping for lazy loading
    const CATEGORY_MAP: Record<string, any[]> = {
      accounts: accountTools,
  • Maps the tool name 'update_account' to its validator function in the validation registry.
    'update_account': validateUpdateAccountData,
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden for behavioral disclosure. While 'Update' implies a mutation operation, the description doesn't specify permission requirements, whether changes are reversible, rate limits, or what happens when updating fails. For a mutation tool with zero annotation coverage, this is a significant gap in behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise at just three words ('Update a sending account'), with zero wasted language. It's front-loaded with the core action and resource, making it immediately understandable despite its brevity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutation tool with no annotations and no output schema, the description is insufficiently complete. It doesn't explain what 'sending account' means in this context, what values are returned, error conditions, or how this tool relates to other account management tools like 'get_account_details' and 'list_accounts'.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema description coverage is 100%, with all three parameters well-documented in the schema itself. The description adds no additional parameter information beyond what's already in the structured schema. According to scoring rules, when schema_description_coverage is high (>80%), the baseline is 3 even with no param info in the description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Update') and resource ('a sending account'), providing a specific verb+resource combination. However, it doesn't differentiate this tool from sibling tools like 'update_campaign' or 'update_lead' - it simply describes what it does without comparative context.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. There's no mention of prerequisites, when this should be used instead of other update tools, or what context would make this appropriate. The agent must infer usage from the tool name alone.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

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/bcharleson/Instantly-MCP'

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