hellio

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 5, 2026 License: MIT Imports: 11 Imported by: 0

README

Hellio Messaging - Official Go SDK

tests Go Reference License

Go client for the Hellio Messaging API v1: SMS, OTP (SMS / email / voice), Voice broadcasts, Number Lookup (HLR), Email Verification, and Webhooks. Standard library only, no third-party dependencies.

Install

go get github.com/HellioSolutions/hellio-go

Configure

Generate a token in your dashboard: Settings -> API -> Generate API token. Pass it to NewClient, or set environment variables and let the client read them:

HELLIO_BASE_URL=https://api.helliomessaging.com/v1
HELLIO_API_TOKEN=your-token-here
HELLIO_DEFAULT_SENDER=HellioSMS
import "github.com/HellioSolutions/hellio-go"

// Explicit token plus options.
client := hellio.NewClient("your-token-here",
    hellio.WithDefaultSender("HellioSMS"),
    hellio.WithTimeout(15*time.Second),
)

// Or read HELLIO_API_TOKEN / HELLIO_BASE_URL / HELLIO_DEFAULT_SENDER from the env.
client := hellio.NewClient("")

Available options: WithBaseURL, WithTimeout, WithDefaultSender, WithHTTPClient.

Every method takes a context.Context first and returns map[string]any (payloads live under the data key), except Verify which returns a bool.

Usage

ctx := context.Background()

// Account
client.Balance(ctx)          // map[data:map[balance:195.0000 available:194.65 ...]]
client.Pricing(ctx, "GH")    // optional ISO-2 country filter, "" for all

// SMS (recipients: single numbers or comma-separated strings, flattened to a list)
client.SMS(ctx, []string{"233241234567"}, "Hello!", "", "")             // default sender, no gateway
client.SMS(ctx, []string{"233241234567", "233201234567"}, "Hi all", "HellioSMS", "")
client.SMS(ctx, hellio.Recipients("233241234567,233201234567"), "Hi", "HellioSMS", "")
client.Message(ctx, 1024)    // delivery status
client.Campaign(ctx, 1024)   // campaign summary

// OTP - sender (Sender ID) is REQUIRED for sms/voice and must be approved on your
// account. length (4-10 digits) and expiry (minutes) are optional; pass 0 to omit.
client.OTP(ctx, "233241234567", "HellioSMS", "sms", "", 0, 0, "")   // SMS
client.OTP(ctx, "233241234567", "HellioSMS", "voice", "", 0, 0, "") // Voice (TTS reads the code)
client.OTP(ctx, "233241234567", "HellioSMS", "sms", "login", 6, 10, "") // custom length / expiry
client.OTP(ctx, "user@example.com", "", "email", "", 0, 0, "")     // Email (no sender)

client.Verify(ctx, "233241234567", "123456", "sms")                // bool convenience
client.VerifyOTP(ctx, "user@example.com", "123456", "email")       // full response

// Voice broadcast - text (we TTS it) or a hosted audio URL
client.Voice(ctx, []string{"233241234567"}, "HELLIO", "Your code is 1 2 3 4", "", "")
client.Voice(ctx, []string{"233241234567"}, "HELLIO", "", "https://cdn.example.com/promo.mp3", "")
client.VoiceStatus(ctx, 42)

// Number lookup (HLR) - async; poll results
client.Lookup(ctx, []string{"233241234567"})
client.Lookups(ctx)
client.LookupResult(ctx, 5)

// Email verification
client.VerifyEmail(ctx, []string{"user@gmail.com", "bad@nodomain.invalid"})

// Webhooks (receive delivery reports)
client.CreateWebhook(ctx, "https://your-app.com/hooks/hellio", []string{"message.delivered", "message.failed"})
client.Webhooks(ctx)
client.DeleteWebhook(ctx, 1)
Reading a response

Responses are plain maps. Reach into data with a type assertion:

res, err := client.Balance(ctx)
if err != nil {
    log.Fatal(err)
}
data := res["data"].(map[string]any)
fmt.Println(data["balance"])
Recipient normalization

SMS, Voice, Lookup, and VerifyEmail accept a []string. Each entry may be a single value or a comma-separated string; entries are trimmed and flattened. The hellio.Recipients(...) helper does the same and is handy for building lists from mixed input.

Error handling

Non-2xx responses return a typed *hellio.Error carrying StatusCode, Message, Body, and a Kind. Use errors.As to read the fields or errors.Is against a sentinel:

Sentinel Kind Status
hellio.ErrInvalidApiToken KindInvalidApiToken 401
hellio.ErrInsufficientBalance KindInsufficientBalance 402
hellio.ErrValidation KindValidation 422
hellio.ErrRateLimit KindRateLimit 429
(base error) KindGeneric other
res, err := client.SMS(ctx, []string{"233241234567"}, "Hi", "HellioSMS", "")
if err != nil {
    switch {
    case errors.Is(err, hellio.ErrInsufficientBalance):
        // top up
    case errors.Is(err, hellio.ErrRateLimit):
        // back off and retry
    default:
        var e *hellio.Error
        if errors.As(err, &e) {
            fmt.Println(e.StatusCode, e.Message, e.Errors()) // Errors() holds 422 details
        }
    }
}

Verify is the exception: a 422 (wrong code) is reported as (false, nil) rather than an error.

Rate limit: 120 requests/minute per token.

Testing

go vet ./...
go test ./...

Tests use net/http/httptest to mock the API; inject a client with hellio.WithHTTPClient(...) to point at your own server.

License

MIT. See LICENSE.

Documentation

Overview

Package hellio is the official Go client for the Hellio Messaging API v1: SMS, OTP (SMS / voice / WhatsApp / email), voice broadcasts, number lookup (HLR), email verification, and webhooks.

Create a client with a Bearer token and call one method per endpoint. Every call returns the decoded JSON response as a map[string]any (payloads live under the "data" key); non-2xx responses return a typed *Error.

Index

Constants

View Source
const (
	// DefaultBaseURL is the production API root.
	DefaultBaseURL = "https://api.helliomessaging.com/v1"
	// DefaultTimeout is the per-request timeout when none is configured.
	DefaultTimeout = 30 * time.Second
)

Variables

View Source
var (
	ErrInvalidApiToken     = &Error{Kind: KindInvalidApiToken}
	ErrInsufficientBalance = &Error{Kind: KindInsufficientBalance}
	ErrValidation          = &Error{Kind: KindValidation}
	ErrRateLimit           = &Error{Kind: KindRateLimit}
	ErrServiceUnavailable  = &Error{Kind: KindServiceUnavailable}
)

Sentinel errors for use with errors.Is. Each matches any Error of the same Kind.

Functions

func Recipients

func Recipients(values ...string) []string

Recipients flattens single numbers and comma-separated strings into a trimmed, non-empty list. hellio.Recipients("233...", "233...,244...") is valid.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client talks to the Hellio Messaging API. Create one with NewClient and reuse it; it is safe for concurrent use.

func NewClient

func NewClient(token string, opts ...Option) *Client

NewClient builds a client. The token falls back to HELLIO_API_TOKEN, the base URL to HELLIO_BASE_URL, and the default sender to HELLIO_DEFAULT_SENDER when those are left empty.

func (*Client) Balance

func (c *Client) Balance(ctx context.Context) (map[string]any, error)

Balance returns the account balance. GET balance.

func (*Client) Campaign

func (c *Client) Campaign(ctx context.Context, id int) (map[string]any, error)

Campaign returns a campaign summary. GET campaigns/{id}.

func (*Client) CreateWebhook

func (c *Client) CreateWebhook(ctx context.Context, webhookURL string, events []string) (map[string]any, error)

CreateWebhook registers a webhook URL for the given events (empty events is omitted from the request). POST webhooks.

func (*Client) DeleteWebhook

func (c *Client) DeleteWebhook(ctx context.Context, id int) (map[string]any, error)

DeleteWebhook removes a webhook. DELETE webhooks/{id}.

func (*Client) Lookup

func (c *Client) Lookup(ctx context.Context, numbers []string) (map[string]any, error)

Lookup queues an HLR lookup for the given numbers (async; poll the result with LookupResult). POST lookup.

func (*Client) LookupResult

func (c *Client) LookupResult(ctx context.Context, id int) (map[string]any, error)

LookupResult returns a single lookup result. GET lookup/{id}.

func (*Client) Lookups

func (c *Client) Lookups(ctx context.Context) (map[string]any, error)

Lookups lists past lookups. GET lookups.

func (*Client) Message

func (c *Client) Message(ctx context.Context, id int) (map[string]any, error)

Message returns the delivery status of a single message. GET messages/{id}.

func (*Client) OTP

func (c *Client) OTP(ctx context.Context, to, sender, channel, purpose string, length, expiry int, gateway string) (map[string]any, error)

OTP sends a one-time passcode. "to" is a phone number (sms/voice/whatsapp) or an email (email). sender (a Sender ID) is required for sms/voice and ignored for whatsapp and email. channel defaults to "sms" when empty (sms/voice/whatsapp/email). Pass length or expiry as 0 to omit them, and purpose or gateway as "" to omit them. POST otp/send.

func (*Client) Pricing

func (c *Client) Pricing(ctx context.Context, country string) (map[string]any, error)

Pricing returns per-network SMS pricing. Pass an ISO-2 country code to narrow the result, or "" for all. GET pricing.

func (*Client) SMS

func (c *Client) SMS(ctx context.Context, recipients []string, message, sender, gateway string) (map[string]any, error)

SMS sends a text message. recipients may be single numbers or comma-separated strings; they are flattened into a list. Pass sender "" to use the configured default sender, and gateway "" to omit it. POST sms/send.

func (*Client) Verify

func (c *Client) Verify(ctx context.Context, to, code, channel string) (bool, error)

Verify is a convenience wrapper that returns true when the code is valid. A 422 validation error is treated as "not verified" and returns (false, nil); other errors are returned as-is.

func (*Client) VerifyEmail

func (c *Client) VerifyEmail(ctx context.Context, emails []string) (map[string]any, error)

VerifyEmail validates a list of email addresses. POST email/verify.

func (*Client) VerifyOTP

func (c *Client) VerifyOTP(ctx context.Context, to, code, channel string) (map[string]any, error)

VerifyOTP checks a passcode and returns the full response. channel defaults to "sms" when empty. POST otp/verify.

func (*Client) Voice

func (c *Client) Voice(ctx context.Context, recipients []string, callerID, text, audioURL, name string) (map[string]any, error)

Voice starts a voice broadcast. Provide text (read out with TTS) or audioURL (a hosted audio file); pass "" for the one you do not use. name is an optional label. POST voice/send.

func (*Client) VoiceStatus

func (c *Client) VoiceStatus(ctx context.Context, id int) (map[string]any, error)

VoiceStatus returns the status of a voice broadcast. GET voice/{id}.

func (*Client) Webhooks

func (c *Client) Webhooks(ctx context.Context) (map[string]any, error)

Webhooks lists configured webhooks. GET webhooks.

type Error

type Error struct {
	Kind       Kind
	StatusCode int
	Message    string
	// Body is the decoded JSON response, if any.
	Body map[string]any
}

Error is returned for every non-2xx response. It carries the HTTP status code, a human message (from the body "message" field or a default), the parsed body, and a Kind for convenient switching. Use errors.As to reach these fields:

var e *hellio.Error
if errors.As(err, &e) { fmt.Println(e.StatusCode, e.Message) }

Or errors.Is against a sentinel:

if errors.Is(err, hellio.ErrRateLimit) { time.Sleep(time.Second) }

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface.

func (*Error) Errors

func (e *Error) Errors() map[string]any

Errors returns the validation details a 422 response carries under "errors", or nil when the body has no such field.

func (*Error) Is

func (e *Error) Is(target error) bool

Is lets errors.Is match by Kind, so callers can compare against the sentinels.

type Kind

type Kind int

Kind classifies a Hellio API error by its HTTP status. Callers can switch on it or use errors.Is against the exported sentinels below.

const (
	// KindGeneric is any non-2xx response that is not one of the specific kinds.
	KindGeneric Kind = iota
	// KindInvalidApiToken maps to HTTP 401.
	KindInvalidApiToken
	// KindInsufficientBalance maps to HTTP 402.
	KindInsufficientBalance
	// KindValidation maps to HTTP 422.
	KindValidation
	// KindRateLimit maps to HTTP 429.
	KindRateLimit
	// KindServiceUnavailable maps to HTTP 503 (a service switched off by an admin,
	// or the API paused). Transient: retry later.
	KindServiceUnavailable
)

type Option

type Option func(*Client)

Option configures a Client. Pass options to NewClient.

func WithBaseURL

func WithBaseURL(baseURL string) Option

WithBaseURL overrides the API root (default DefaultBaseURL).

func WithDefaultSender

func WithDefaultSender(sender string) Option

WithDefaultSender sets the Sender ID used by SMS when no sender is given.

func WithHTTPClient

func WithHTTPClient(h *http.Client) Option

WithHTTPClient injects a custom *http.Client. Handy for tests and for tuning transport, proxies, or timeouts.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the per-request timeout on the default HTTP client. It has no effect if you also pass WithHTTPClient; set the timeout on that client instead.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL