Go SDK

Go SDK

The official Go SDK for Sent LogoSent 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-go

To pin a specific version:

go get github.com/sentdm/sent-dm-go@v0.7.0

Quick 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"),
    },
    Channels: 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
}

Source & Issues

Getting Help

On this page