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 credentials — sign 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:
- Full API documentation — Group messaging, typing indicators, reactions, and all endpoints
- Webhook integration guide — Handle all 7 webhook types
- Send iMessages from Linux — Deploy your Go service on any Linux server
- Build an AI agent — Connect an LLM to iMessage
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.