Skip to main content

Overview

Bulk messaging in GoBlue allows you to efficiently send personalized messages to large groups of contacts while maintaining the personal touch that makes your communications effective. This guide covers strategies, tools, and best practices for scaling your messaging operations.
Always ensure compliance with messaging regulations (TCPA, GDPR, etc.) and obtain proper consent before sending bulk messages. See our Compliance Guide for details.

Understanding Bulk Messaging in GoBlue

How Bulk Messaging Works

Unlike traditional bulk SMS services, GoBlue maintains personalization through:

Individual Processing

Each message is processed individually with recipient-specific data

Template Variables

Dynamic content insertion using syntax

Form-Based Routing

Different message templates based on data source

iOS Integration

Messages sent through your iOS device maintain authenticity

Bulk vs. Individual Messages

  • Individual Messages
  • Bulk Messages
Best For:
  • New lead follow-ups
  • Customer service responses
  • Appointment confirmations
  • High-value prospects
Characteristics:
  • Immediate processing
  • Real-time personalization
  • Individual webhook triggers
  • High engagement rates

Setting Up Bulk Messaging

1. Contact Segmentation

Before sending bulk messages, organize your contacts into meaningful segments:
High Engagement: Recent interactions, quick responders Medium Engagement: Occasional interactions Low Engagement: Rare interactions, may need re-engagement
// Example segmentation by last activity
const segments = {
  high: contacts.filter(c => 
    c.lastResponseAt && 
    Date.now() - new Date(c.lastResponseAt) < 7 * 24 * 60 * 60 * 1000 // 7 days
  ),
  medium: contacts.filter(c => 
    c.lastContactedAt && 
    Date.now() - new Date(c.lastContactedAt) < 30 * 24 * 60 * 60 * 1000 // 30 days
  ),
  low: contacts.filter(c => 
    !c.lastResponseAt || 
    Date.now() - new Date(c.lastResponseAt) > 90 * 24 * 60 * 60 * 1000 // 90 days
  )
};
Industry: SaaS, E-commerce, Healthcare, etc. Company Size: Startup, SMB, Enterprise Location: Geographic regions, time zones
// Segment by industry
const industrySegments = contacts.reduce((acc, contact) => {
  const industry = contact.customFields?.industry || 'unknown';
  if (!acc[industry]) acc[industry] = [];
  acc[industry].push(contact);
  return acc;
}, {});
Purchase History: Previous customers, new prospects Form Source: Which forms they came from Campaign Response: How they’ve responded to previous campaigns
// Segment by form source
const sourceSegments = {
  website: contacts.filter(c => c.formName.includes('Website')),
  ads: contacts.filter(c => c.source?.includes('google-ads')),
  referral: contacts.filter(c => c.source === 'referral')
};

2. Campaign Planning

1

Define Campaign Goals

Primary Goals:
  • Lead nurturing
  • Product announcements
  • Event promotion
  • Customer retention
Success Metrics:
  • Response rate targets
  • Conversion goals
  • Engagement thresholds
2

Create Message Templates

Design templates for different segments:
// High-engagement template
Hi {{firstName}}! 🎉 

Since you've been so engaged with our {{serviceType}} discussions, I wanted to give you early access to our new {{productName}}.

Interested in a quick demo this week?

Best,
{{agentName}}
// Re-engagement template
Hi {{firstName}},

It's been a while since we last connected about {{serviceType}}. I hope your {{projectType}} project is going well!

We've made some exciting updates that might interest you. Worth a quick catch-up?

{{agentName}}
3

Schedule and Sequence

Plan your campaign timing:
  • Initial announcement
  • Follow-up sequences
  • Time zone considerations
  • Optimal sending times

Implementation Strategies

Strategy 1: Multiple Form Approach

Create separate forms for different segments, each with targeted templates:
  • High-Value Segment Form
  • General Audience Form
Form Name: VIP Customer Campaign Template:
Hi {{firstName}},

As one of our valued {{customerTier}} clients, I wanted to personally invite you to an exclusive preview of {{eventName}}.

Limited to just 25 attendees, this {{eventType}} will cover {{topicRelevant}} specifically for {{industryType}} leaders like yourself.

Date: {{eventDate}}
Time: {{eventTime}}
Location: {{eventLocation}}

RSVP: {{rsvpLink}}

Looking forward to seeing you there!

{{hostName}}

Strategy 2: External System Integration

Use your CRM or marketing automation platform to trigger GoBlue forms:
// Example: Segment-based campaign trigger
class BulkCampaignManager {
  async launchCampaign(campaignConfig) {
    const { segments, formMappings, schedule } = campaignConfig;
    
    for (const [segmentName, contacts] of Object.entries(segments)) {
      const formId = formMappings[segmentName];
      
      for (const contact of contacts) {
        // Add campaign-specific data
        const webhookData = {
          ...contact.customFields,
          firstName: contact.firstName,
          lastName: contact.lastName,
          phoneNumber: contact.phoneNumber,
          campaignName: campaignConfig.name,
          segmentName: segmentName,
          sendTime: this.calculateOptimalSendTime(contact, schedule)
        };
        
        // Schedule webhook to GoBlue form
        await this.scheduleWebhook(formId, webhookData, webhookData.sendTime);
      }
    }
  }
  
  calculateOptimalSendTime(contact, baseSchedule) {
    // Factor in timezone, engagement history, etc.
    const timezone = contact.customFields?.timezone || 'UTC';
    const optimalHour = contact.bestEngagementHour || 10; // 10 AM default
    
    return moment.tz(baseSchedule.date, timezone)
      .hour(optimalHour)
      .toISOString();
  }
}

Strategy 3: Zapier Bulk Workflows

Set up Zapier workflows that process CSV files or database changes:
1

Trigger Setup

Options:
  • Google Sheets new row
  • Airtable record updated
  • CSV file uploaded to Dropbox
  • CRM contact tag added
2

Data Processing

Zapier Filter: Only process contacts meeting criteria Formatter: Clean and structure data for GoBlue Delay: Add delays between messages to avoid rate limits
3

GoBlue Integration

Webhook Action: Send to appropriate GoBlue form Error Handling: Retry failed webhooks Logging: Track campaign progress

Message Queue Management

Understanding the Queue System

GoBlue processes messages through a priority queue system:

Urgent

Immediate processing

High

Next in queue

Normal

Standard processing

Low

Background processing

Bulk Message Processing

// Monitor and manage bulk message queues
class QueueManager {
  async monitorBulkCampaign(campaignId) {
    const stats = await this.getCampaignStats(campaignId);
    
    console.log('Campaign Status:', {
      total: stats.totalMessages,
      queued: stats.queuedMessages,
      sent: stats.sentMessages,
      failed: stats.failedMessages,
      deliveryRate: (stats.sentMessages / stats.totalMessages) * 100
    });
    
    // Handle any issues
    if (stats.failedMessages > stats.totalMessages * 0.05) { // 5% failure rate
      await this.investigateFailures(campaignId);
    }
    
    if (stats.queuedMessages > 0 && this.isStuck(stats)) {
      await this.restartQueueProcessing();
    }
  }
  
  async optimizeQueueProcessing() {
    // Prioritize high-engagement contacts
    const highEngagementMessages = await this.getMessages({
      tags: ['high-engagement'],
      status: 'queued'
    });
    
    // Boost priority for these messages
    await this.bulkUpdatePriority(
      highEngagementMessages.map(m => m.id),
      'high'
    );
  }
}

Delivery Optimization

Timing Strategies

Send messages at optimal local times for each recipient:
function calculateOptimalSendTime(contact, baseCampaignTime) {
  const contactTimezone = contact.customFields?.timezone || 
                        inferTimezone(contact.phoneNumber);
  
  const optimalHours = {
    'business': 10, // 10 AM for B2B
    'consumer': 7,  // 7 PM for B2C
    'retail': 2     // 2 PM for retail
  };
  
  const contactType = contact.customFields?.contactType || 'business';
  const optimalHour = optimalHours[contactType];
  
  return moment.tz(baseCampaignTime, contactTimezone)
    .hour(optimalHour)
    .minute(0)
    .second(0)
    .toISOString();
}
Use historical data to determine best sending times:
class EngagementAnalyzer {
  getBestSendTime(contact) {
    const responseHistory = contact.messageHistory?.filter(m => m.responseTime);
    
    if (!responseHistory?.length) {
      return this.getDefaultTime(contact);
    }
    
    // Analyze response patterns
    const hourResponses = responseHistory.reduce((acc, msg) => {
      const hour = new Date(msg.sentAt).getHours();
      acc[hour] = (acc[hour] || 0) + 1;
      return acc;
    }, {});
    
    // Find peak response hour
    const bestHour = Object.entries(hourResponses)
      .sort(([,a], [,b]) => b - a)[0][0];
    
    return parseInt(bestHour);
  }
}
Respect platform limits and recipient preferences:
const rateLimits = {
  perMinute: 30,    // iOS Shortcuts processing capacity
  perHour: 1000,    // GoBlue API limit
  perDay: 10000,    // Account daily limit
  perRecipient: 3   // Max messages per day per contact
};

class RateLimiter {
  async scheduleMessages(messages) {
    const batches = this.createBatches(messages, rateLimits.perMinute);
    
    for (let i = 0; i < batches.length; i++) {
      const batch = batches[i];
      const sendTime = new Date(Date.now() + (i * 60 * 1000)); // 1 minute intervals
      
      await this.scheduleBatch(batch, sendTime);
    }
  }
  
  validateRecipientLimits(messages) {
    const recipientCounts = messages.reduce((acc, msg) => {
      acc[msg.recipientPhone] = (acc[msg.recipientPhone] || 0) + 1;
      return acc;
    }, {});
    
    return messages.filter(msg => 
      recipientCounts[msg.recipientPhone] <= rateLimits.perRecipient
    );
  }
}

Advanced Personalization

Dynamic Content Generation

Create highly personalized bulk messages using advanced templating:
class AdvancedPersonalizer {
  generatePersonalizedContent(template, contact, campaignData) {
    let content = template;
    
    // Basic variable replacement
    content = this.replaceVariables(content, {
      ...contact.customFields,
      firstName: contact.firstName,
      lastName: contact.lastName
    });
    
    // Conditional content
    content = this.processConditionals(content, contact);
    
    // Dynamic recommendations
    content = this.addRecommendations(content, contact, campaignData);
    
    return content;
  }
  
  processConditionals(content, contact) {
    // Industry-specific content
    if (contact.customFields?.industry === 'healthcare') {
      content = content.replace(
        '{{industrySpecific}}',
        'Our HIPAA-compliant solution ensures patient data security'
      );
    }
    
    // Customer tier messaging
    if (contact.tags?.includes('enterprise')) {
      content = content.replace(
        '{{tierMessage}}',
        'As an enterprise client, you have access to our premium support team'
      );
    }
    
    return content;
  }
  
  addRecommendations(content, contact, campaignData) {
    const recommendations = this.generateRecommendations(contact, campaignData);
    
    return content.replace('{{recommendations}}', recommendations.join('\n• '));
  }
}

A/B Testing at Scale

Test different approaches across your bulk campaigns:
  • Subject Line Testing
  • Message Length Testing
  • CTA Testing
const testVariants = {
  direct: "{{firstName}}, here's what you need to know",
  question: "{{firstName}}, ready to {{actionVerb}}?",
  benefit: "{{firstName}}, save {{savingsAmount}} with this",
  urgent: "{{firstName}}, only {{timeLeft}} left!"
};

function assignTestVariant(contact, testName) {
  const hash = this.generateHash(contact.id + testName);
  const variants = Object.keys(testVariants);
  const variantIndex = hash % variants.length;
  
  return {
    variant: variants[variantIndex],
    template: testVariants[variants[variantIndex]]
  };
}

Campaign Analytics

Tracking Campaign Performance

class CampaignMonitor {
  async trackCampaign(campaignId) {
    const realTimeStats = await this.getRealTimeStats(campaignId);
    
    return {
      sent: realTimeStats.messagesProcessed,
      pending: realTimeStats.messagesQueued,
      failed: realTimeStats.messagesFailed,
      responses: realTimeStats.responsesReceived,
      deliveryRate: (realTimeStats.messagesProcessed / realTimeStats.totalMessages) * 100,
      responseRate: (realTimeStats.responsesReceived / realTimeStats.messagesProcessed) * 100,
      estimatedCompletion: this.calculateETA(realTimeStats)
    };
  }
}
async function analyzeSegmentPerformance(campaignId) {
  const segments = await this.getCampaignSegments(campaignId);
  const analysis = {};
  
  for (const [segmentName, contacts] of Object.entries(segments)) {
    const stats = await this.getSegmentStats(campaignId, segmentName);
    
    analysis[segmentName] = {
      totalContacts: contacts.length,
      deliveryRate: stats.deliveryRate,
      responseRate: stats.responseRate,
      avgResponseTime: stats.avgResponseTime,
      topResponses: stats.commonResponses,
      roi: this.calculateROI(stats, segmentName)
    };
  }
  
  return analysis;
}
function analyzeABTestResults(testId) {
  const results = this.getTestResults(testId);
  
  return {
    variants: results.variants.map(variant => ({
      name: variant.name,
      sampleSize: variant.messagesSent,
      responseRate: variant.responses / variant.messagesSent,
      conversionRate: variant.conversions / variant.responses,
      significance: this.calculateSignificance(variant, results.control)
    })),
    winner: this.determineWinner(results),
    recommendation: this.generateRecommendation(results)
  };
}

Key Performance Indicators

Delivery Metrics

  • Delivery Rate: Messages successfully sent
  • Queue Time: Average time in processing queue
  • Failure Rate: Messages that couldn’t be delivered
  • Retry Success: Failed messages recovered on retry

Engagement Metrics

  • Response Rate: Recipients who replied
  • Response Time: How quickly people respond
  • Conversion Rate: Responses that led to desired action
  • Unsubscribe Rate: Opt-out requests

Compliance and Best Practices

Requirements:
  • Express written consent for automated messages
  • Clear opt-out instructions (STOP keyword)
  • Sender identification in messages
  • Respect opt-out requests within required timeframe
Implementation:
function ensureTCPACompliance(message, contact) {
  // Check consent
  if (!contact.hasExpressConsent) {
    throw new Error('No express consent for automated messaging');
  }
  
  // Add required elements
  message.content += '\n\nReply STOP to opt out.';
  message.content = `From ${config.businessName}: ${message.content}`;
  
  return message;
}
Requirements:
  • Lawful basis for processing
  • Data minimization
  • Right to deletion
  • Data portability
Implementation:
class GDPRCompliance {
  validateProcessing(contact, campaignType) {
    const lawfulBasis = this.determineLawfulBasis(contact, campaignType);
    
    if (!lawfulBasis) {
      throw new Error('No lawful basis for processing this contact');
    }
    
    // Log processing activity
    this.logProcessingActivity(contact.id, campaignType, lawfulBasis);
  }
  
  handleDeletionRequest(contactId) {
    // Remove from all systems
    return this.deleteContactData(contactId);
  }
}

Messaging Best Practices

Content Guidelines

  • Keep messages concise and valuable
  • Include clear sender identification
  • Provide easy opt-out options
  • Avoid spam trigger words

Frequency Management

  • Respect recipient preferences
  • Monitor engagement drop-off
  • Implement fatigue detection
  • Use progressive delays

Segmentation

  • Target relevant audiences
  • Personalize based on behavior
  • Test different segments
  • Maintain clean data

Performance

  • Monitor delivery rates
  • Track engagement metrics
  • Optimize send times
  • A/B test approaches

Troubleshooting Common Issues

High Failure Rates

Problem: Messages failing due to invalid phone formatsSolution:
function validatePhoneNumbers(contacts) {
  const phoneValidator = new PhoneValidator();
  
  return contacts.filter(contact => {
    const isValid = phoneValidator.validate(contact.phoneNumber);
    
    if (!isValid) {
      this.logInvalidPhone(contact);
    }
    
    return isValid;
  });
}
Problem: Messages being rate-limited or delayedSolution:
class SmartRateLimiter {
  async adjustRate(currentFailureRate) {
    if (currentFailureRate > 0.1) { // 10% failure rate
      this.currentRate = Math.max(this.currentRate * 0.8, this.minRate);
    } else if (currentFailureRate < 0.02) { // 2% failure rate
      this.currentRate = Math.min(this.currentRate * 1.2, this.maxRate);
    }
    
    await this.updateProcessingRate(this.currentRate);
  }
}

Low Engagement

Problem: Poor response rates due to bad timingSolution: Implement engagement-based scheduling
function optimizeSendTimes(campaign) {
  const historicalData = this.getHistoricalEngagement();
  
  campaign.messages.forEach(message => {
    const contact = message.contact;
    const optimalTime = this.predictOptimalTime(contact, historicalData);
    message.scheduledFor = optimalTime;
  });
}
Problem: Generic messages not resonatingSolution: Enhance personalization
function enhancePersonalization(message, contact) {
  // Add behavioral data
  const behaviorData = this.getBehaviorData(contact);
  
  // Industry-specific content
  const industryContent = this.getIndustryContent(contact.industry);
  
  // Recent interaction context
  const recentContext = this.getRecentInteractions(contact);
  
  return this.applyAdvancedPersonalization(message, {
    behavior: behaviorData,
    industry: industryContent,
    context: recentContext
  });
}

Next Steps

Start with small test campaigns before scaling up. This allows you to optimize your templates, timing, and segmentation before committing to large-scale campaigns.