Home
/
Blog
/
How to Send iMessages from Go (Golang)
March 29, 2026
8 min read
Nikita Jerschow

How to Send iMessages from Go (Golang)

Go developers can send iMessages using the Sendblue REST API with the standard library — no third-party HTTP packages needed. This tutorial covers building an HTTP client, sending messages, setting up a webhook server, handling media, and sending messages concurrently with goroutines.

Prerequisites

You need:

  • Go 1.21+
  • Sendblue API credentialssign up free to get your API key and secret

Go's standard library includes everything you need — net/http for HTTP requests, encoding/json for JSON marshaling, and os for environment variables. No external dependencies required.

Set your environment variables:

export SENDBLUE_API_KEY="your-api-key" export SENDBLUE_API_SECRET="your-api-secret"

HTTP Client Setup

Create a reusable Sendblue client struct with typed request/response objects:

package sendblue import ( "bytes" "encoding/json" "fmt" "net/http" "os" "time" ) const baseURL = "https://api.sendblue.co" type Client struct { apiKey string apiSecret string http *http.Client } func NewClient() *Client { return &Client{ apiKey: os.Getenv("SENDBLUE_API_KEY"), apiSecret: os.Getenv("SENDBLUE_API_SECRET"), http: &http.Client{ Timeout: 30 * time.Second, }, } } type SendMessageRequest struct { Number string `json:"number"` Content string `json:"content"` SendStyle string `json:"send_style,omitempty"` MediaURL string `json:"media_url,omitempty"` } type SendMessageResponse struct { Status string `json:"status"` MessageID string `json:"message_id"` } type EvaluateResponse struct { IsIMessage bool `json:"is_imessage"` Number string `json:"number"` }

Send Your First iMessage

Implement the send method and use it in a main function:

func (c *Client) SendMessage(req SendMessageRequest) (*SendMessageResponse, error) { body, err := json.Marshal(req) if err != nil { return nil, fmt.Errorf("marshal request: %w", err) } httpReq, err := http.NewRequest( "POST", baseURL+"/api/send-message", bytes.NewReader(body), ) if err != nil { return nil, fmt.Errorf("create request: %w", err) } httpReq.Header.Set("Content-Type", "application/json") httpReq.Header.Set("sb-api-key-id", c.apiKey) httpReq.Header.Set("sb-api-secret-key", c.apiSecret) resp, err := c.http.Do(httpReq) if err != nil { return nil, fmt.Errorf("send request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("API returned status %d", resp.StatusCode) } var result SendMessageResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, fmt.Errorf("decode response: %w", err) } return &result, nil } // Usage in main.go: func main() { client := sendblue.NewClient() result, err := client.SendMessage(sendblue.SendMessageRequest{ Number: "+15551234567", Content: "Hello from Go!", SendStyle: "celebration", }) if err != nil { log.Fatal(err) } fmt.Printf("Message sent: %s (ID: %s)\n", result.Status, result.MessageID) }

The send_style field is optional — it adds iMessage effects like celebration, fireworks, and confetti. Sendblue automatically falls back to RCS or SMS if the recipient doesn't have iMessage.

Webhook Server

Set up an HTTP server to receive incoming messages from Sendblue webhooks:

package main import ( "encoding/json" "fmt" "log" "net/http" ) type IncomingMessage struct { FromNumber string `json:"from_number"` Content string `json:"content"` MediaURL string `json:"media_url"` Service string `json:"service"` } func main() { client := sendblue.NewClient() http.HandleFunc("/webhooks/receive", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var msg IncomingMessage if err := json.NewDecoder(r.Body).Decode(&msg); err != nil { http.Error(w, "Invalid JSON", http.StatusBadRequest) return } log.Printf("[%s] %s: %s", msg.Service, msg.FromNumber, msg.Content) if msg.MediaURL != "" { log.Printf(" Media: %s", msg.MediaURL) } // Auto-reply go func() { _, err := client.SendMessage(sendblue.SendMessageRequest{ Number: msg.FromNumber, Content: "Thanks for your message! We'll respond shortly.", }) if err != nil { log.Printf("Error sending reply: %v", err) } }() w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{"status": "ok"}) }) fmt.Println("Webhook server running on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) }

Configure your webhook URL in the Sendblue dashboard. Use ngrok for local development: ngrok http 8080.

Send Media

Attach images, videos, PDFs, or contact cards to any message:

// Send an image with a message client.SendMessage(sendblue.SendMessageRequest{ Number: "+15551234567", Content: "Here's your invoice:", MediaURL: "https://example.com/invoice.pdf", }) // Send a contact card (vCard) — unique to Sendblue client.SendMessage(sendblue.SendMessageRequest{ Number: "+15551234567", MediaURL: "https://example.com/sales-rep.vcf", })

Contact cards are especially powerful for sales teams. When the recipient saves the vCard, your business name appears on all future messages.

Concurrent Message Sending with Goroutines

Go's concurrency model makes it easy to send many messages at once. Use goroutines with a WaitGroup to send to multiple recipients concurrently:

func sendBulk(client *sendblue.Client, recipients []string, content string) { var wg sync.WaitGroup sem := make(chan struct{}, 10) // limit to 10 concurrent requests for _, number := range recipients { wg.Add(1) sem <- struct{}{} // acquire semaphore go func(num string) { defer wg.Done() defer func() { <-sem }() // release semaphore result, err := client.SendMessage(sendblue.SendMessageRequest{ Number: num, Content: content, }) if err != nil { log.Printf("Error sending to %s: %v", num, err) return } log.Printf("Sent to %s: %s", num, result.Status) }(number) } wg.Wait() } // Usage: recipients := []string{"+15551234567", "+15559876543", "+15555551212"} sendBulk(client, recipients, "Important update from our team!")

The semaphore pattern limits concurrency to 10 simultaneous requests, preventing rate limiting while maximizing throughput.

Error Handling

Implement proper error handling with retry logic:

func (c *Client) SendMessageWithRetry( req SendMessageRequest, maxRetries int, ) (*SendMessageResponse, error) { var lastErr error for attempt := 0; attempt <= maxRetries; attempt++ { if attempt > 0 { delay := time.Duration(1<<uint(attempt-1)) * time.Second time.Sleep(delay) log.Printf("Retry %d/%d after %v", attempt, maxRetries, delay) } result, err := c.SendMessage(req) if err == nil { return result, nil } lastErr = err log.Printf("Attempt %d failed: %v", attempt+1, err) } return nil, fmt.Errorf("all %d attempts failed: %w", maxRetries+1, lastErr) }

This uses exponential backoff (1s, 2s, 4s) for transient failures — a best practice for reliable message delivery in production.

Next Steps

You're now sending iMessages from Go. Continue with:

Get your free API keys and start sending iMessages from Go today.

Ready to send your first iMessage?

Get API access in minutes. Free sandbox, no credit card required.

Get API Access