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
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
Define Campaign Goals
Primary Goals:
Lead nurturing
Product announcements
Event promotion
Customer retention
Success Metrics:
Response rate targets
Conversion goals
Engagement thresholds
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}}
Schedule and Sequence
Plan your campaign timing:
Initial announcement
Follow-up sequences
Time zone considerations
Optimal sending times
Implementation Strategies
Create separate forms for different segments, each with targeted templates:
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:
Trigger Setup
Options:
Google Sheets new row
Airtable record updated
CSV file uploaded to Dropbox
CRM contact tag added
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
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
Normal Standard 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
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 )
};
}
}
Segment Performance 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 )
};
}
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
Legal Compliance
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\n Reply 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 schedulingfunction 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 personalizationfunction 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.