Go SDK
The official Go SDK for Sent provides a lightweight, high-performance client with minimal dependencies. Built for microservices, CLI tools, and high-throughput applications with full context support.
Requirements
This library requires Go 1.22 or later.
Installation
go get github.com/sentdm/sent-dm-goTo pin a specific version:
go get github.com/sentdm/sent-dm-go@v0.17.0Quick Start
Initialize the client
package main
import (
"context"
"os"
"github.com/sentdm/sent-dm-go"
"github.com/sentdm/sent-dm-go/option"
)
func main() {
client := sentdm.NewClient(
option.WithAPIKey(os.Getenv("SENT_DM_API_KEY")), // defaults to os.LookupEnv("SENT_DM_API_KEY")
)
// Use the client...
}Send your first message
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/sentdm/sent-dm-go"
"github.com/sentdm/sent-dm-go/option"
)
func main() {
client := sentdm.NewClient(
option.WithAPIKey(os.Getenv("SENT_DM_API_KEY")),
)
ctx := context.Background()
response, err := client.Messages.Send(ctx, sentdm.MessageSendParams{
To: []string{"+1234567890"},
Template: sentdm.MessageSendParamsTemplate{
ID: sentdm.String("7ba7b820-9dad-11d1-80b4-00c04fd430c8"),
Name: sentdm.String("welcome"),
Parameters: map[string]string{
"name": "John Doe",
"order_id": "12345",
},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Message sent: %s\n", response.Data.Messages[0].ID)
fmt.Printf("Status: %s\n", response.Data.Messages[0].Status)
}Authentication
The client can be configured using environment variables or explicitly using functional options:
import (
"github.com/sentdm/sent-dm-go"
"github.com/sentdm/sent-dm-go/option"
)
// Using environment variables (SENT_DM_API_KEY)
client := sentdm.NewClient()
// Or explicit configuration
client := sentdm.NewClient(
option.WithAPIKey("your_api_key"),
)Request fields
The sentdm library uses the omitzero semantics from Go 1.24+ encoding/json release for request fields.
Required primitive fields feature the tag json:"...,required". These fields are always serialized, even their zero values.
Optional primitive types are wrapped in a param.Opt[T]. These fields can be set with the provided constructors, sentdm.String(), sentdm.Int(), etc.
params := sentdm.MessageSendParams{
To: []string{"+1234567890"}, // required property
Template: sentdm.MessageSendParamsTemplate{
ID: sentdm.String("template-id"),
Name: sentdm.String("welcome"),
},
Channel: sentdm.StringSlice([]string{"whatsapp"}), // optional property
}To send null instead of a param.Opt[T], use param.Null[T](). To check if a field is omitted, use param.IsOmitted().
Send Messages
Send a message
response, err := client.Messages.Send(ctx, sentdm.MessageSendParams{
To: []string{"+1234567890"},
Template: sentdm.MessageSendParamsTemplate{
ID: sentdm.String("7ba7b820-9dad-11d1-80b4-00c04fd430c8"),
Name: sentdm.String("welcome"),
Parameters: map[string]string{
"name": "John Doe",
"order_id": "12345",
},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Sent: %s\n", response.Data.Messages[0].ID)
fmt.Printf("Status: %s\n", response.Data.Messages[0].Status)Test mode
Use TestMode to validate requests without sending real messages:
response, err := client.Messages.Send(ctx, sentdm.MessageSendParams{
To: []string{"+1234567890"},
Template: sentdm.MessageSendParamsTemplate{
ID: sentdm.String("7ba7b820-9dad-11d1-80b4-00c04fd430c8"),
Name: sentdm.String("welcome"),
},
TestMode: sentdm.Bool(true), // Validates but doesn't send
})
if err != nil {
log.Fatal(err)
}
// Response will have test data
fmt.Printf("Validation passed: %s\n", response.Data.Messages[0].ID)Error handling
When the API returns a non-success status code, we return an error of type *sentdm.Error:
response, err := client.Messages.Send(ctx, params)
if err != nil {
var apiErr *sentdm.Error
if errors.As(err, &apiErr) {
fmt.Printf("API error: %s\n", apiErr.Message)
fmt.Printf("Status: %d\n", apiErr.StatusCode)
} else {
fmt.Printf("Other error: %v\n", err)
}
}Pagination
For paginated list endpoints, you can use .ListAutoPaging() methods to iterate through items across all pages:
// List all contacts
iter := client.Contacts.ListAutoPaging(ctx, sentdm.ContactListParams{})
for iter.Next() {
contact := iter.Current()
fmt.Printf("%s - %s\n", contact.PhoneNumber, contact.AvailableChannels)
}
if err := iter.Err(); err != nil {
log.Fatal(err)
}Or use simple .List() methods to fetch a single page:
page, err := client.Contacts.List(ctx, sentdm.ContactListParams{
Limit: sentdm.Int(100),
})
if err != nil {
log.Fatal(err)
}
for _, contact := range page.Data {
fmt.Printf("%s\n", contact.PhoneNumber)
}
// Get next page
if page.HasMore {
nextPage, err := page.GetNextPage()
// ...
}Contacts
Create and manage contacts:
// Create a contact
response, err := client.Contacts.Create(ctx, sentdm.ContactCreateParams{
PhoneNumber: "+1234567890",
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Contact ID: %s\n", response.Data.ID)
// List contacts
page, err := client.Contacts.List(ctx, sentdm.ContactListParams{
Limit: sentdm.Int(100),
})
// Get a contact
contact, err := client.Contacts.Get(ctx, "contact-uuid")
// Update a contact
response, err = client.Contacts.Update(ctx, "contact-uuid", sentdm.ContactUpdateParams{
PhoneNumber: sentdm.String("+1987654321"),
})
// Delete a contact
err = client.Contacts.Delete(ctx, "contact-uuid")Templates
List and retrieve templates:
// List templates
templates, err := client.Templates.List(ctx, sentdm.TemplateListParams{})
if err != nil {
log.Fatal(err)
}
for _, template := range templates.Data {
fmt.Printf("%s (%s): %s\n", template.Name, template.Status, template.ID)
}
// Get a template
template, err := client.Templates.Get(ctx, "template-uuid")
fmt.Printf("Name: %s\n", template.Name)
fmt.Printf("Status: %s\n", template.Status)RequestOptions
This library uses the functional options pattern. Functions defined in the option package return a RequestOption, which is a closure that mutates a RequestConfig. These options can be supplied to the client or at individual requests:
client := sentdm.NewClient(
// Adds a header to every request made by the client
option.WithHeader("X-Some-Header", "custom_header_info"),
)
// Override per-request
response, err := client.Messages.Send(ctx, params,
option.WithHeader("X-Some-Header", "some_other_value"),
option.WithJSONSet("custom.field", map[string]string{"my": "object"}),
)The request option option.WithDebugLog(nil) may be helpful while debugging.
See the full list of request options.
Gin Framework
package main
import (
"context"
"net/http"
"os"
"time"
"github.com/gin-gonic/gin"
"github.com/sentdm/sent-dm-go"
"github.com/sentdm/sent-dm-go/option"
)
func main() {
client := sentdm.NewClient(
option.WithAPIKey(os.Getenv("SENT_DM_API_KEY")),
)
r := gin.Default()
r.POST("/send", func(c *gin.Context) {
var req struct {
To []string `json:"to"`
Template sentdm.MessageSendParamsTemplate `json:"template"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
response, err := client.Messages.Send(ctx, sentdm.MessageSendParams{
To: req.To,
Template: req.Template,
})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "sent",
"message_id": response.Data.Messages[0].ID,
})
})
r.Run(":8080")
}Echo Framework
package main
import (
"context"
"net/http"
"os"
"time"
"github.com/labstack/echo/v4"
"github.com/sentdm/sent-dm-go"
"github.com/sentdm/sent-dm-go/option"
)
func main() {
client := sentdm.NewClient(
option.WithAPIKey(os.Getenv("SENT_DM_API_KEY")),
)
e := echo.New()
e.POST("/send", func(c echo.Context) error {
req := new(sentdm.MessageSendParams)
if err := c.Bind(req); err != nil {
return err
}
ctx, cancel := context.WithTimeout(c.Request().Context(), 30*time.Second)
defer cancel()
response, err := client.Messages.Send(ctx, *req)
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{
"error": err.Error(),
})
}
return c.JSON(http.StatusOK, map[string]string{
"status": "sent",
"message_id": response.Data.Messages[0].ID,
})
})
e.Logger.Fatal(e.Start(":8080"))
}Response objects
All fields in response structs are ordinary value types. Response structs also include a special JSON field containing metadata about each property.
response, err := client.Templates.Get(ctx, "template-uuid")
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Name) // Access the field directly
// Check if field was present in response
if response.JSON.Name.Valid() {
fmt.Println("Name was present")
}
// Access raw JSON
fmt.Println(response.JSON.Name.Raw())Concurrent Sending
Leverage Go's concurrency for high-throughput:
func sendBulkMessages(
client *sentdm.Client,
phoneNumbers []string,
templateID string,
) error {
var wg sync.WaitGroup
errChan := make(chan error, len(phoneNumbers))
// Semaphore to limit concurrency
sem := make(chan struct{}, 10)
for _, phone := range phoneNumbers {
wg.Add(1)
go func(p string) {
defer wg.Done()
sem <- struct{}{}
defer func() { <-sem }()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
response, err := client.Messages.Send(ctx, sentdm.MessageSendParams{
To: []string{p},
Template: sentdm.MessageSendParamsTemplate{
ID: sentdm.String(templateID),
},
})
if err != nil {
errChan <- fmt.Errorf("failed to send to %s: %w", p, err)
}
}(phone)
}
wg.Wait()
close(errChan)
var errs []error
for err := range errChan {
errs = append(errs, err)
}
if len(errs) > 0 {
return fmt.Errorf("failed to send %d messages", len(errs))
}
return nil
}Webhooks
Recommended pattern: Webhooks are the primary way to track message delivery — don't poll the API. Save the message ID when you send, then update your database as webhook events arrive.
Sent delivers signed POST requests to your endpoint for every status change. Two event types exist:
messages— Message status changes (SENT,DELIVERED,READ,FAILED, …)templates— WhatsApp template approval/rejection
The signing secret (from the Sent Dashboard) has a whsec_ prefix. Strip it and base64-decode the remainder to obtain the raw HMAC key. The signed content is {X-Webhook-ID}.{X-Webhook-Timestamp}.{rawBody} and the signature format is v1,{base64(hmac)}.
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"io"
"math"
"net/http"
"os"
"strconv"
"strings"
"time"
)
func webhookHandler(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
webhookID := r.Header.Get("X-Webhook-ID")
timestamp := r.Header.Get("X-Webhook-Timestamp")
signature := r.Header.Get("X-Webhook-Signature")
// 1. Verify: signed content = "{webhookId}.{timestamp}.{rawBody}"
raw := os.Getenv("SENT_WEBHOOK_SECRET") // "whsec_abc123..."
keyStr := strings.TrimPrefix(raw, "whsec_")
keyBytes, _ := base64.StdEncoding.DecodeString(keyStr)
signed := webhookID + "." + timestamp + "." + string(body)
mac := hmac.New(sha256.New, keyBytes)
mac.Write([]byte(signed))
expected := "v1," + base64.StdEncoding.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(signature), []byte(expected)) {
http.Error(w, `{"error":"invalid signature"}`, http.StatusUnauthorized)
return
}
// 2. Optional: reject replayed events older than 5 minutes
ts, _ := strconv.ParseInt(timestamp, 10, 64)
if math.Abs(float64(time.Now().Unix()-ts)) > 300 {
http.Error(w, `{"error":"timestamp too old"}`, http.StatusUnauthorized)
return
}
var event struct {
Field string `json:"field"`
Payload map[string]any `json:"payload"`
}
json.Unmarshal(body, &event)
// 3. Handle events — update message status in your own database
if event.Field == "messages" {
messageID := event.Payload["message_id"]
status := event.Payload["message_status"]
// db.UpdateMessageStatus(ctx, messageID.(string), status.(string))
_ = messageID; _ = status
}
// 4. Always return 200 quickly
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"received":true}`))
}See the Webhooks reference for the full payload schema and all status values.
Source & Issues
- Version: 0.17.0
- GitHub: sentdm/sent-dm-go
- GoDoc: pkg.go.dev/github.com/sentdm/sent-dm-go
- Issues: Report a bug
Getting Help
- Documentation: API Reference
- Troubleshooting: Common Issues
- Support: Email support@sent.dm with your request ID