Documentation
ΒΆ
Overview ΒΆ
Package calisero is the official Go library for the Calisero transactional SMS API (https://calisero.ro).
Send SMS messages, verify phone numbers with one-time codes, manage opt-outs for GDPR compliance, receive delivery reports and monitor your account. The library has no dependency outside the standard library.
Getting Started ΒΆ
Create a client with your API key, from the dashboard's API Keys section, then send a message:
client, err := calisero.NewClient(os.Getenv("CALISERO_API_KEY"))
if err != nil {
log.Fatal(err)
}
response, err := client.Messages.Create(ctx, &calisero.CreateMessageRequest{
Recipient: "+40742123456",
Body: "Hello from Calisero!",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Data.ID, response.Data.Status)
A Client is safe for concurrent use by multiple goroutines: create it once and share it. Every call takes a context, which cancels it.
Services ΒΆ
The client groups the API's operations by resource:
- Client.Messages sends messages, now or later, lists them, reads their delivery status and cancels the scheduled ones.
- Client.Verifications sends one-time codes (OTP, 2FA) and checks them.
- Client.OptOuts manages the phone numbers that asked to receive no message.
- Client.Accounts reads the account: credit, status and daily sending limit.
A single resource comes as a Response, a list as a Page. Both carry a ResponseMeta, read from the answer's headers: the request's trace ID, the request rate limit and, for a created message or verification, what is left of the account's daily sending limit.
Errors ΒΆ
Every error the API answers with is an *APIError, wrapped in the type that matches its status: *UnauthorizedError (401), *ForbiddenError (403), *NotFoundError (404), *ValidationError (422), *RateLimitedError (429), *DailyLimitExceededError (429, the daily sending limit), *ServerError (5xx). A request that got no answer returns a *TransportError. Test for them with errors.As:
var validationError *calisero.ValidationError
if errors.As(err, &validationError) {
fmt.Println(validationError.ValidationErrors["recipient"])
}
The client never retries a request: the API takes no idempotency key, so a message retried after a timeout or a server error may already have been sent.
Delivery Status Webhooks ΒΆ
When a message has a callback URL, Calisero posts its delivery status updates to it. ParseDeliveryWebhook reads and validates a callback's body, and DeliveryWebhookHandler answers the callbacks for net/http:
http.Handle("POST /webhooks/calisero", &calisero.DeliveryWebhookHandler{
Token: os.Getenv("CALISERO_WEBHOOK_TOKEN"),
OnMessage: func(r *http.Request, message *calisero.DeliveryWebhookMessage) error {
log.Println(message.MessageID, message.Status)
return nil
},
})
Index ΒΆ
- Constants
- Variables
- type APIError
- type Account
- type AccountService
- type AccountStatus
- type AuthProvider
- type AuthProviderFunc
- type Client
- type CreateMessageRequest
- type CreateOptOutRequest
- type CreateVerificationRequest
- type DailyLimitExceededError
- type DeliveryStatus
- type DeliveryWebhookHandler
- type DeliveryWebhookMessage
- type ForbiddenError
- type HTTPDoer
- type InvalidWebhookPayloadError
- type ListOptions
- type Message
- type MessageService
- func (s *MessageService) Create(ctx context.Context, request *CreateMessageRequest) (*Response[Message], error)
- func (s *MessageService) Delete(ctx context.Context, messageID string) error
- func (s *MessageService) Get(ctx context.Context, messageID string) (*Response[Message], error)
- func (s *MessageService) List(ctx context.Context, options *ListOptions) (*Page[Message], error)
- type MessageStatus
- type NotFoundError
- type OptOut
- type OptOutService
- func (s *OptOutService) Create(ctx context.Context, request *CreateOptOutRequest) (*Response[OptOut], error)
- func (s *OptOutService) Delete(ctx context.Context, optOutID string) error
- func (s *OptOutService) Get(ctx context.Context, optOutID string) (*Response[OptOut], error)
- func (s *OptOutService) List(ctx context.Context, options *ListOptions) (*Page[OptOut], error)
- func (s *OptOutService) Update(ctx context.Context, optOutID string, request *UpdateOptOutRequest) (*Response[OptOut], error)
- type Option
- type Page
- type PaginationLinks
- type PaginationMeta
- type RateLimitedError
- type Response
- type ResponseMeta
- type ServerError
- type ShortenedLink
- type TransportError
- type UnauthorizedError
- type UpdateOptOutRequest
- type ValidationError
- type Verification
- type VerificationCheckRequest
- type VerificationListOptions
- type VerificationService
- func (s *VerificationService) Create(ctx context.Context, request *CreateVerificationRequest) (*Response[Verification], error)
- func (s *VerificationService) Get(ctx context.Context, verificationID string) (*Response[Verification], error)
- func (s *VerificationService) List(ctx context.Context, options *VerificationListOptions) (*Page[Verification], error)
- func (s *VerificationService) Validate(ctx context.Context, request *VerificationCheckRequest) (*Response[Verification], error)
- type VerificationStatus
Examples ΒΆ
Constants ΒΆ
const DailyLimitExceededCode = "daily_limit_exceeded"
DailyLimitExceededCode is the code of the error body that tells a refusal of the account's daily sending limit from a refusal of the request rate limit.
const DefaultBaseURL = "https://rest.calisero.ro/api/v1"
DefaultBaseURL is the production API.
const DefaultTimeout = 30 * time.Second
DefaultTimeout is how long the default HTTP client waits for each answer, headers and body included.
const DefaultWebhookMaxBodySize = 64 << 10
DefaultWebhookMaxBodySize is the largest callback body a DeliveryWebhookHandler accepts when its MaxBodySize is not set: 64 KiB.
const UserAgent = "Calisero-SMS-Go/" + Version
UserAgent is the product token that opens the User-Agent header of every request: the library and its version. A request whose User-Agent starts with "Calisero-SMS-Go/" comes from this library.
const Version = "1.0.1"
Version is the library's version, sent in the User-Agent header. A test keeps it in step with the latest version of CHANGELOG.md.
Variables ΒΆ
var ErrInvalidWebhookToken = errors.New("calisero: invalid webhook token: the request URL lacks the expected token")
ErrInvalidWebhookToken is reported to DeliveryWebhookHandler.OnError for a callback whose URL lacks the handler's token.
Functions ΒΆ
This section is empty.
Types ΒΆ
type APIError ΒΆ
type APIError struct {
// Message is the API's own description of the error, e.g. "Resource not
// found!"; "HTTP error 404" when the answer carried none.
Message string
// StatusCode is the HTTP status of the answer; 0 when the request got no answer.
StatusCode int
// TraceID is the request's trace ID, from the X-Trace-Id header or the error
// body's trace_id: quote it to Calisero support, or look the request up in the
// dashboard under Developers β Debug. Empty when the answer carried none.
TraceID string
// Details is the decoded JSON error body; nil when the answer had none.
Details map[string]any
// Err is the error that caused this one: the network error of a
// *TransportError, the decoding error of an answer that could not be read.
Err error
}
APIError is an error the Calisero API answered with, or the error of a request that got no answer (a *TransportError). Every error a call returns once its request was sent is one, wrapped in the type that matches the answer's status: test for them with errors.As, the specific types first.
Example ΒΆ
package main
import (
"context"
"errors"
"log"
"os"
calisero "github.com/calisero/calisero-go"
)
func main() {
client, err := calisero.NewClient(os.Getenv("CALISERO_API_KEY"))
if err != nil {
log.Fatal(err)
}
_, err = client.Messages.Get(context.Background(), "9e2574e8-3615-4090-9b5a-0fc812079da8")
var apiError *calisero.APIError
if errors.As(err, &apiError) {
// Quote the trace ID to support, or look it up under Developers β Debug
log.Printf("Calisero request %s failed with %d: %s", apiError.TraceID, apiError.StatusCode, apiError.Message)
}
}
Output:
func (*APIError) Error ΒΆ
Error describes the error with its status and trace ID, e.g. "calisero: Resource not found! (status 404, trace ID 9b80eef1-49d4-4502-85a8-febb68cc11a7)".
type Account ΒΆ
type Account struct {
ID string `json:"id"`
Code string `json:"code"`
Name string `json:"name"`
Description string `json:"description"`
FiscalCode string `json:"fiscal_code"`
RegistryNumber string `json:"registry_number"`
IBAN string `json:"iban"`
City string `json:"city"`
State string `json:"state"`
Country string `json:"country"`
Address string `json:"address"`
PostalCode *int `json:"postal_code"`
Email string `json:"email"`
Phone string `json:"phone"`
ContactPerson string `json:"contact_person"`
// Credit is the account's credit balance.
Credit float64 `json:"credit"`
Status AccountStatus `json:"status"`
// Sandbox reports whether this is a sandbox account: its messages are test
// messages, marked delivered at once, never sent to a phone nor billed.
Sandbox bool `json:"sandbox"`
// DailyLimit is how many messages the account can send in a day, midnight to
// midnight, Romania time; nil when no daily limit applies.
DailyLimit *int `json:"daily_limit"`
// DailyRemaining is how many messages the account can still send today; nil
// when no daily limit applies.
DailyRemaining *int `json:"daily_remaining"`
// SentToday is the number of real messages the account created today, Romania
// time: OTP codes included, test messages left out, one per message whatever
// its parts.
SentToday int `json:"sent_today"`
CreatedAt time.Time `json:"created_at"`
}
Account is a Calisero account. Its optional details are empty, or nil, when the account has none.
func (*Account) UnmarshalJSON ΒΆ
UnmarshalJSON reads an account as the API sends it, the way the client does: a required field missing or malformed is an error naming it.
type AccountService ΒΆ
type AccountService struct {
// contains filtered or unexported fields
}
AccountService reads the account: credit, status and daily sending limit. Reach it as Client.Accounts.
type AccountStatus ΒΆ
type AccountStatus string
AccountStatus is the status of an account; other values may come with future API versions.
const ( AccountStatusActive AccountStatus = "active" AccountStatusPending AccountStatus = "pending" AccountStatusDisabled AccountStatus = "disabled" )
The statuses of an account.
type AuthProvider ΒΆ
AuthProvider provides the bearer token the API authenticates requests with: the API key.
Token is called before every request, with the request's context, so it may read the key from a secret manager and rotate it without a restart. An error it returns fails the request before anything is sent.
type AuthProviderFunc ΒΆ
AuthProviderFunc adapts a function to an AuthProvider.
type Client ΒΆ
type Client struct {
// Messages sends and manages SMS messages.
Messages *MessageService
// OptOuts manages the phone numbers that opted out of receiving messages.
OptOuts *OptOutService
// Accounts reads the account: credit, status and daily sending limit.
Accounts *AccountService
// Verifications verifies phone numbers with one-time codes.
Verifications *VerificationService
// contains filtered or unexported fields
}
Client is the Calisero SMS API client. Create it with NewClient, once, and share it: a Client is safe for concurrent use by multiple goroutines.
It never retries a request: the API takes no idempotency key, so a message retried after a timeout or a server error could be sent twice.
func NewClient ΒΆ
NewClient creates a client that authenticates with apiKey, a key of the dashboard's API Keys section, or with the provider of WithAuthProvider.
It returns an error when the API key is empty and no provider is given, or when an option is invalid: a misconfigured environment fails at startup rather than on the first message.
Example ΒΆ
package main
import (
"context"
"fmt"
"log"
"os"
calisero "github.com/calisero/calisero-go"
)
func main() {
client, err := calisero.NewClient(os.Getenv("CALISERO_API_KEY"))
if err != nil {
log.Fatal(err)
}
response, err := client.Messages.Create(context.Background(), &calisero.CreateMessageRequest{
Recipient: "+40742123456",
Body: "Hello from Calisero!",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Data.ID, response.Data.Status)
}
Output:
Example (Options) ΒΆ
package main
import (
"fmt"
"log"
"os"
"time"
calisero "github.com/calisero/calisero-go"
)
func main() {
client, err := calisero.NewClient(os.Getenv("CALISERO_API_KEY"),
calisero.WithTimeout(10*time.Second), // 30 seconds by default
calisero.WithAppInfo("MyShop", "2.1.0"), // Named in the User-Agent header
calisero.WithBaseURL(calisero.DefaultBaseURL), // The default
)
if err != nil {
log.Fatal(err)
}
fmt.Println(client.UserAgent()) // Calisero-SMS-Go/1.0.0 (Go 1.27.1; linux amd64) MyShop/2.1.0
}
Output:
type CreateMessageRequest ΒΆ
type CreateMessageRequest struct {
// Recipient is the recipient's mobile number in international E.164 format,
// e.g. +40742123456. Required.
Recipient string
// Body is the text sent, at most 1600 characters. Required.
Body string
// VisibleBody is what the API shows instead of Body in its answers, its lists
// and the dashboard, e.g. the text with a code masked: "Your code is ******".
VisibleBody string
// Validity is the message's validity period, in hours.
Validity int
// ScheduleAt is when to send the message, converted to the Romania time the
// API reads; the zero time sends it at once, as does a time already past
// today. From today to 5 years ahead.
ScheduleAt time.Time
// CallbackURL is where Calisero posts the message's delivery status updates.
CallbackURL string
// Sender is one of the account's approved sender IDs, exactly as approved; the
// default sender when empty.
Sender string
// ShortenURLs replaces the http:// and https:// links of the body with short
// ones, whose clicks are counted. The body is sent as written otherwise.
ShortenURLs bool
}
CreateMessageRequest is a message to send. The optional fields left at their zero value are not sent.
type CreateOptOutRequest ΒΆ
type CreateOptOutRequest struct {
// Phone is the phone number in international E.164 format. Required.
Phone string
// Reason is why the number opted out, at most 600 characters.
Reason string
}
CreateOptOutRequest is a phone number to opt out of receiving messages.
type CreateVerificationRequest ΒΆ
type CreateVerificationRequest struct {
// Phone is the phone number to verify, in international E.164 format. Required.
Phone string
// Brand is the brand named in the default message, at most 120 characters:
// required unless a Template is given.
Brand string
// Template is a message of your own, at most 600 characters, that must hold
// {code}.
Template string
// ExpiresIn is how many minutes the code stays valid, 1 to 10: 5 when 0.
ExpiresIn int
}
CreateVerificationRequest is a verification to start: Calisero generates a code and sends it by SMS. The optional fields left at their zero value are not sent.
type DailyLimitExceededError ΒΆ
type DailyLimitExceededError struct {
RateLimitedError
// DailyLimit is how many messages the account can send in a day.
DailyLimit int
// DailyRemaining is how many messages the account can still send today: 0 once
// the limit is reached.
DailyRemaining int
// ResetsAt is the next midnight, Romania time, when the account can send
// again, e.g. 2026-10-01T00:00:00+03:00; the zero time when the answer did not
// say.
ResetsAt time.Time
}
DailyLimitExceededError is returned when the account reached its daily sending limit (429 with the code daily_limit_exceeded): nothing was sent and nothing was billed.
The limit resets at midnight, Romania time (ResetsAt); test messages never count towards it. It wraps a *RateLimitedError, so code that tests for that type keeps catching this refusal too.
Example ΒΆ
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
calisero "github.com/calisero/calisero-go"
)
func main() {
client, err := calisero.NewClient(os.Getenv("CALISERO_API_KEY"))
if err != nil {
log.Fatal(err)
}
_, err = client.Messages.Create(context.Background(), &calisero.CreateMessageRequest{Recipient: "+40742123456", Body: "Hello!"})
// A *DailyLimitExceededError is also a *RateLimitedError: test for it first
var dailyLimit *calisero.DailyLimitExceededError
var rateLimit *calisero.RateLimitedError
switch {
case errors.As(err, &dailyLimit):
fmt.Println("Nothing sent; the daily limit resets at", dailyLimit.ResetsAt)
case errors.As(err, &rateLimit):
fmt.Println("Too many requests; retry in", rateLimit.RetryAfter)
case err != nil:
log.Fatal(err)
}
}
Output:
func (*DailyLimitExceededError) Unwrap ΒΆ
func (e *DailyLimitExceededError) Unwrap() error
Unwrap returns the embedded *RateLimitedError, so errors.As finds it.
type DeliveryStatus ΒΆ
type DeliveryStatus string
DeliveryStatus is a status a delivery status callback reports; other values may come with future API versions.
const ( DeliveryStatusSent DeliveryStatus = "sent" DeliveryStatusDelivered DeliveryStatus = "delivered" DeliveryStatusUndelivered DeliveryStatus = "undelivered" )
The statuses a delivery status callback reports.
type DeliveryWebhookHandler ΒΆ
type DeliveryWebhookHandler struct {
// OnMessage is called with every delivery status update, before the answer,
// which Calisero waits 2 seconds for: record the status or queue a job, then
// return. An error it returns is answered with 500. Required.
OnMessage func(r *http.Request, message *DeliveryWebhookMessage) error
// Token is a secret the callback URL carries as its token query parameter,
// e.g. https://yourapp.com/webhooks/calisero?token=β¦: a request without it is
// answered with 401, the comparison taking constant time. Calisero does not
// sign its callbacks, so this is how to accept only those of the URLs you gave
// it. No token is checked when it is empty.
Token string
// OnError is called when a request is refused or OnMessage fails, e.g. to log it.
OnError func(r *http.Request, err error)
// MaxBodySize is the largest body accepted, in bytes: DefaultWebhookMaxBodySize
// when it is not positive.
MaxBodySize int64
}
DeliveryWebhookHandler answers the delivery status callbacks Calisero posts to a message's callback URL: an http.Handler for net/http and the routers built on it. It answers:
- 200 {"received":true} once OnMessage handled the update;
- 400 for a payload that is not a delivery status update, 413 for one larger than MaxBodySize;
- 401 when Token is set and the request URL lacks it;
- 405 for anything but POST;
- 500 when OnMessage returns an error.
Calisero retries only a callback that got no answer within 2 seconds, at most 5 attempts in total, never one answered with an error.
Example ΒΆ
package main
import (
"log"
"net/http"
"os"
"time"
calisero "github.com/calisero/calisero-go"
)
func main() {
http.Handle("POST /webhooks/calisero", &calisero.DeliveryWebhookHandler{
// Accept only the callback URLs that carry ?token=<this secret>
Token: os.Getenv("CALISERO_WEBHOOK_TOKEN"),
// Keep it short (update a record, queue a job): the answer waits for it
OnMessage: func(_ *http.Request, message *calisero.DeliveryWebhookMessage) error {
log.Println(message.MessageID, message.Status, message.Price)
return nil
},
OnError: func(_ *http.Request, err error) {
log.Println(err)
},
})
server := &http.Server{Addr: ":8080", ReadHeaderTimeout: 5 * time.Second}
log.Fatal(server.ListenAndServe())
}
Output:
func (*DeliveryWebhookHandler) ServeHTTP ΒΆ
func (h *DeliveryWebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP answers a delivery status callback.
type DeliveryWebhookMessage ΒΆ
type DeliveryWebhookMessage struct {
MessageID string `json:"messageId"`
// Sender is the sender ID the message went out under; empty for the default sender.
Sender string `json:"sender"`
// Recipient is the recipient in E.164 format.
Recipient string `json:"recipient"`
// Status is the delivery status: sent, delivered or undelivered.
Status DeliveryStatus `json:"status"`
// ScheduleAt is when the message was scheduled to go out, if it was scheduled.
ScheduleAt *time.Time `json:"scheduleAt"`
// SentAt is when the message was handed to the network (statuses sent and
// delivered).
SentAt *time.Time `json:"sentAt"`
// DeliveredAt is when the handset confirmed delivery (status delivered only).
DeliveredAt *time.Time `json:"deliveredAt"`
// Price is the price charged for the message's parts.
Price float64 `json:"price"`
// RemainingBalance is the account's balance after billing, if available.
RemainingBalance *float64 `json:"remainingBalance"`
// DailyLimit is the account's daily sending limit when the callback was sent;
// nil when no daily limit applies.
DailyLimit *int `json:"dailyLimit"`
// DailyRemaining is how many messages the account could still send today when
// the callback was sent; nil when no daily limit applies.
DailyRemaining *int `json:"dailyRemaining"`
// SentToday is the number of real messages the account created today, Romania
// time, when the callback was sent: OTP codes included, test messages left
// out, one per message whatever its parts. Nil in payloads that predate the
// daily limit.
SentToday *int `json:"sentToday"`
}
DeliveryWebhookMessage is the payload Calisero posts to a message's callback URL when its status changes: sent, delivered or undelivered.
It reaches your endpoint from outside your application, so it is validated: ParseDeliveryWebhook and UnmarshalJSON refuse a malformed payload with an *InvalidWebhookPayloadError.
func ParseDeliveryWebhook ΒΆ
func ParseDeliveryWebhook(body []byte) (*DeliveryWebhookMessage, error)
ParseDeliveryWebhook reads the raw body of a delivery status callback.
It returns an *InvalidWebhookPayloadError when the body is not a JSON object or a required field is missing or malformed, which includes the [] Calisero sends for a message cancelled in the meantime.
Example ΒΆ
package main
import (
"fmt"
"time"
calisero "github.com/calisero/calisero-go"
)
func main() {
body := []byte(`{"messageId":"019961db-14a7-7348-963f-5a7a789a969f","recipient":"+40742***350","status":"delivered",
"price":0.0378,"deliveredAt":"2025-09-19T12:02:53.000000Z","dailyLimit":1000,"dailyRemaining":588}`)
message, err := calisero.ParseDeliveryWebhook(body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(message.MessageID, message.Status)
fmt.Println(message.DeliveredAt.Format(time.RFC3339))
fmt.Printf("%.4f\n", message.Price)
fmt.Println(*message.DailyRemaining, "of", *message.DailyLimit, "messages left today")
// What Calisero sends for a message cancelled in the meantime
_, err = calisero.ParseDeliveryWebhook([]byte(`[]`))
fmt.Println(err)
}
Output: 019961db-14a7-7348-963f-5a7a789a969f delivered 2025-09-19T12:02:53Z 0.0378 588 of 1000 messages left today calisero: invalid delivery webhook payload: expected an object, got an array
func (*DeliveryWebhookMessage) UnmarshalJSON ΒΆ
func (m *DeliveryWebhookMessage) UnmarshalJSON(data []byte) error
UnmarshalJSON reads a delivery status callback the way ParseDeliveryWebhook does, e.g. when a web framework binds the request's body: a malformed payload, the JSON null included, is an *InvalidWebhookPayloadError.
type ForbiddenError ΒΆ
type ForbiddenError struct {
APIError
}
ForbiddenError is returned when access is forbidden (403), e.g. deleting a message already sent, or reading another account's resource.
func (*ForbiddenError) Unwrap ΒΆ
func (e *ForbiddenError) Unwrap() error
Unwrap returns the embedded *APIError, so errors.As finds it.
type HTTPDoer ΒΆ
HTTPDoer sends an HTTP request and returns the answer, whatever its status: *http.Client implements it.
It returns an error only when the request got no answer (a network error, a timeout, a cancelled context): the client turns that error into a *TransportError.
type InvalidWebhookPayloadError ΒΆ
type InvalidWebhookPayloadError struct {
// Message says what is wrong with the payload, e.g. `invalid delivery webhook
// payload: "messageId" must be a non-empty string, got nothing`.
Message string
// Err is the error that caused this one, e.g. the JSON syntax error.
Err error
}
InvalidWebhookPayloadError is returned when a delivery status callback is not the payload Calisero sends: not JSON, not an object, too large, or a required field missing or malformed.
func (*InvalidWebhookPayloadError) Error ΒΆ
func (e *InvalidWebhookPayloadError) Error() string
Error describes what is wrong with the payload.
func (*InvalidWebhookPayloadError) Unwrap ΒΆ
func (e *InvalidWebhookPayloadError) Unwrap() error
Unwrap returns the error that caused this one, if any.
type ListOptions ΒΆ
type ListOptions struct {
// Page is the page to read, from 1, each holding 50 items; 0 reads the first one.
Page int
}
ListOptions are the options of the calls that list resources.
type Message ΒΆ
type Message struct {
ID string `json:"id"`
// Recipient is the recipient's phone number.
Recipient string `json:"recipient"`
// Body is the text sent, or the VisibleBody it was sent with.
Body string `json:"body"`
// Parts is how many SMS parts the body takes.
Parts int `json:"parts"`
CreatedAt time.Time `json:"created_at"`
// ScheduledAt is when the message is or was due to be sent.
ScheduledAt *time.Time `json:"scheduled_at"`
// SentAt is when the message was handed to the network; nil until it is.
SentAt *time.Time `json:"sent_at"`
// DeliveredAt is when the handset confirmed delivery; nil until it does.
DeliveredAt *time.Time `json:"delivered_at"`
// CallbackURL is where Calisero posts the message's delivery status updates;
// empty when it has none.
CallbackURL string `json:"callback_url"`
Status MessageStatus `json:"status"`
// Sender is the sender ID; empty for the default sender.
Sender string `json:"sender"`
// ShortenedURLs are the links of the body Calisero shortened (asked with
// ShortenURLs), with their click statistics; empty when none were.
ShortenedURLs []ShortenedLink `json:"shortened_urls"`
}
Message is an SMS message.
func (*Message) UnmarshalJSON ΒΆ
UnmarshalJSON reads a message as the API sends it, the way the client does: a required field missing or malformed is an error naming it.
type MessageService ΒΆ
type MessageService struct {
// contains filtered or unexported fields
}
MessageService sends and manages SMS messages: reach it as Client.Messages.
func (*MessageService) Create ΒΆ
func (s *MessageService) Create(ctx context.Context, request *CreateMessageRequest) (*Response[Message], error)
Create sends a message, now or at its ScheduleAt.
Its ResponseMeta holds what is left of the account's daily sending limit after this message. Once the limit is reached it returns a *DailyLimitExceededError; a refused recipient (invalid, or opted out) a *ValidationError.
Example ΒΆ
package main
import (
"context"
"fmt"
"log"
"os"
"time"
calisero "github.com/calisero/calisero-go"
)
func main() {
client, err := calisero.NewClient(os.Getenv("CALISERO_API_KEY"))
if err != nil {
log.Fatal(err)
}
response, err := client.Messages.Create(context.Background(), &calisero.CreateMessageRequest{
Recipient: "+40742123456",
Body: "Your code is 123456. Manage alerts: https://yoursite.com/alerts",
VisibleBody: "Your code is ******. Manage alerts: https://yoursite.com/alerts", // Shown in answers and the dashboard
Validity: 24, // Hours
ScheduleAt: time.Now().Add(time.Hour), // Converted to Romania time
CallbackURL: "https://yoursite.com/webhooks/calisero", // Delivery reports
Sender: "Calisero", // One of your approved sender IDs
ShortenURLs: true, // Shorten the links of the body
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Data.ID, response.Data.Status)
fmt.Println(response.ResponseMeta.TraceID)
// What is left of the account's daily sending limit (nil: no daily limit)
if remaining := response.ResponseMeta.DailyRemaining; remaining != nil {
fmt.Println(*remaining, "messages left today")
}
}
Output:
func (*MessageService) Delete ΒΆ
func (s *MessageService) Delete(ctx context.Context, messageID string) error
Delete cancels a scheduled message. A message already queued, sent or delivered cannot be: Delete then returns a *ForbiddenError.
func (*MessageService) List ΒΆ
func (s *MessageService) List(ctx context.Context, options *ListOptions) (*Page[Message], error)
List lists the messages, newest first, 50 a page; options may be nil.
Example ΒΆ
package main
import (
"context"
"fmt"
"log"
"os"
calisero "github.com/calisero/calisero-go"
)
func main() {
client, err := calisero.NewClient(os.Getenv("CALISERO_API_KEY"))
if err != nil {
log.Fatal(err)
}
// Every page, newest first, 50 messages each
for page := 1; ; page++ {
messages, err := client.Messages.List(context.Background(), &calisero.ListOptions{Page: page})
if err != nil {
log.Fatal(err)
}
for _, message := range messages.Data {
fmt.Println(message.ID, message.Status)
}
if !messages.HasNextPage() {
break
}
}
}
Output:
type MessageStatus ΒΆ
type MessageStatus string
MessageStatus is the status of a message; other values may come with future API versions.
const ( MessageStatusScheduled MessageStatus = "scheduled" MessageStatusPending MessageStatus = "pending" MessageStatusQueued MessageStatus = "queued" MessageStatusSent MessageStatus = "sent" MessageStatusDelivered MessageStatus = "delivered" MessageStatusUndelivered MessageStatus = "undelivered" MessageStatusFailed MessageStatus = "failed" MessageStatusCanceled MessageStatus = "canceled" )
The statuses of a message. Only a scheduled message can be cancelled.
type NotFoundError ΒΆ
type NotFoundError struct {
APIError
}
NotFoundError is returned when a resource is not found (404).
func (*NotFoundError) Unwrap ΒΆ
func (e *NotFoundError) Unwrap() error
Unwrap returns the embedded *APIError, so errors.As finds it.
type OptOut ΒΆ
type OptOut struct {
ID string `json:"id"`
Phone string `json:"phone"`
// Reason is why the number opted out; empty when none was given.
Reason string `json:"reason"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
OptOut is a phone number that opted out of receiving messages.
func (*OptOut) UnmarshalJSON ΒΆ
UnmarshalJSON reads an opt-out as the API sends it, the way the client does: a required field missing or malformed is an error naming it.
type OptOutService ΒΆ
type OptOutService struct {
// contains filtered or unexported fields
}
OptOutService manages the phone numbers that opted out of receiving messages: reach it as Client.OptOuts. The API refuses to send to an opted-out number.
func (*OptOutService) Create ΒΆ
func (s *OptOutService) Create(ctx context.Context, request *CreateOptOutRequest) (*Response[OptOut], error)
Create opts a phone number out.
func (*OptOutService) Delete ΒΆ
func (s *OptOutService) Delete(ctx context.Context, optOutID string) error
Delete deletes an opt-out: the number can receive messages again.
func (*OptOutService) List ΒΆ
func (s *OptOutService) List(ctx context.Context, options *ListOptions) (*Page[OptOut], error)
List lists the opt-outs, 50 a page; options may be nil.
func (*OptOutService) Update ΒΆ
func (s *OptOutService) Update(ctx context.Context, optOutID string, request *UpdateOptOutRequest) (*Response[OptOut], error)
Update updates an opt-out.
type Option ΒΆ
type Option func(*settings) error
Option configures a Client: pass them to NewClient.
func WithAppInfo ΒΆ
WithAppInfo names your application in the User-Agent header, after the library: WithAppInfo("MyShop", "2.1.0") appends "MyShop/2.1.0", so the requests of each of your applications can be told apart. Characters a header does not allow (spaces, diacriticsβ¦) become "-"; the version may be empty.
func WithAuthProvider ΒΆ
func WithAuthProvider(provider AuthProvider) Option
WithAuthProvider authenticates the requests with the token of provider instead of an API key, e.g. to read the key from a secret manager and rotate it without a restart. NewClient then ignores its API key, which may be empty.
Example ΒΆ
package main
import (
"context"
"log"
"os"
calisero "github.com/calisero/calisero-go"
)
func main() {
// Called before every request: the key can rotate without a restart
provider := calisero.AuthProviderFunc(func(_ context.Context) (string, error) {
return os.Getenv("CALISERO_API_KEY"), nil
})
client, err := calisero.NewClient("", calisero.WithAuthProvider(provider))
if err != nil {
log.Fatal(err)
}
_ = client
}
Output:
func WithBaseURL ΒΆ
WithBaseURL sends the requests to another base URL than DefaultBaseURL, e.g. a local server in tests. It must be an absolute http or https URL, without a query string.
func WithHTTPClient ΒΆ
WithHTTPClient sends the requests through httpClient instead of the default HTTP client: an *http.Client with a proxy, TLS settings or instrumentation of your own, or a stub in tests.
It is used as configured, its timeout and its redirect policy included, whereas the default client waits DefaultTimeout and follows no redirect.
func WithTimeout ΒΆ
WithTimeout sets how long the default HTTP client waits for each answer, headers and body included: DefaultTimeout otherwise. It is ignored with WithHTTPClient, whose client keeps its own timeout.
type Page ΒΆ
type Page[T any] struct { // Data are the items of the page, at most 50; empty on an empty page. Data []T // Links are the URLs of the neighbouring pages. Links PaginationLinks // Meta is where the page stands in the whole list. Meta PaginationMeta // ResponseMeta is what the answer's headers report: the trace ID and the // request rate limit. ResponseMeta ResponseMeta }
Page is a page of a list as the API answers it: its items, the links to the other pages, where it stands in the whole list, and what the answer's headers report.
func (*Page[T]) HasNextPage ΒΆ
HasNextPage reports whether a next page exists.
type PaginationLinks ΒΆ
type PaginationLinks struct {
First string `json:"first"`
// Last is always empty: the API does not count the pages.
Last string `json:"last"`
Prev string `json:"prev"`
// Next is empty on the last page.
Next string `json:"next"`
}
PaginationLinks are the URLs of the pages around a page; empty when there is none.
type PaginationMeta ΒΆ
type PaginationMeta struct {
CurrentPage int `json:"current_page"`
// From is the position of the page's first item in the whole list; nil on an
// empty page.
From *int `json:"from"`
Path string `json:"path"`
PerPage int `json:"per_page"`
// To is the position of the page's last item in the whole list; nil on an
// empty page.
To *int `json:"to"`
}
PaginationMeta is where a page stands in the whole list.
type RateLimitedError ΒΆ
type RateLimitedError struct {
APIError
// RetryAfter is how long to wait before sending again (the Retry-After
// header); 0 when the answer did not say it in seconds.
RetryAfter time.Duration
// RateLimitLimit is how many requests the API user may make a minute (the
// X-RateLimit-Limit header); 0 when the answer did not say.
RateLimitLimit int
// RateLimitRemaining is how many requests the API user may still make this
// minute (the X-RateLimit-Remaining header): 0 when the request rate limit
// refused the request.
RateLimitRemaining int
// RateLimitReset is when requests are accepted again (the X-RateLimit-Reset
// header, sent only when the request rate limit refused the request); the zero
// time when the answer did not say.
RateLimitReset time.Time
}
RateLimitedError is returned when the request rate limit is exceeded (429): each API user may make 240 requests a minute. A refusal of the account's daily sending limit is a *DailyLimitExceededError, which wraps this type, so test for that one first.
func (*RateLimitedError) Unwrap ΒΆ
func (e *RateLimitedError) Unwrap() error
Unwrap returns the embedded *APIError, so errors.As finds it.
type Response ΒΆ
type Response[T any] struct { // Data is the resource. Data T // ResponseMeta is what the answer's headers report: the trace ID, the request // rate limit and, for a created message or verification, what is left of the // account's daily sending limit. ResponseMeta ResponseMeta }
Response is a single resource as the API answers it: the resource (Data) and what the answer's headers report (ResponseMeta).
type ResponseMeta ΒΆ
type ResponseMeta struct {
// TraceID is the request's trace ID (X-Trace-Id): quote it to Calisero
// support, or look the request up in the dashboard under Developers β Debug.
// Empty when the answer did not carry it.
TraceID string
// RateLimitLimit is how many requests the API user may make a minute
// (X-RateLimit-Limit).
RateLimitLimit *int
// RateLimitRemaining is how many requests the API user may still make this
// minute (X-RateLimit-Remaining).
RateLimitRemaining *int
// DailyLimit is how many messages the account can send in a day
// (X-Daily-Limit); nil when no daily limit applies or the answer is not to a
// created message or verification.
DailyLimit *int
// DailyRemaining is how many messages the account can still send today, after
// this one (X-Daily-Remaining); nil when no daily limit applies.
DailyRemaining *int
}
ResponseMeta is what the API reports in the headers of an answer: the request's trace ID, the request rate limit and, for a created message or verification, the account's daily sending limit. A count is nil when the answer did not carry it.
type ServerError ΒΆ
type ServerError struct {
APIError
}
ServerError is returned for server errors (500, 502, 503, 504).
func (*ServerError) Unwrap ΒΆ
func (e *ServerError) Unwrap() error
Unwrap returns the embedded *APIError, so errors.As finds it.
type ShortenedLink ΒΆ
type ShortenedLink struct {
ID string `json:"id"`
// OriginalLink is the URL as it was written in the message body.
OriginalLink string `json:"original_link"`
// ShortenedLink is the short URL that replaced it in the SMS sent.
ShortenedLink string `json:"shortened_link"`
// ClickCount is how many times the short URL was opened.
ClickCount int `json:"click_count"`
// LastClick is when the short URL was last opened; nil if it never was.
LastClick *time.Time `json:"last_click"`
CreatedAt time.Time `json:"created_at"`
}
ShortenedLink is a link of a message body shortened by Calisero, with its click statistics.
func (*ShortenedLink) UnmarshalJSON ΒΆ
func (l *ShortenedLink) UnmarshalJSON(data []byte) error
UnmarshalJSON reads a shortened link as the API sends it, the way the client does: a required field missing or malformed is an error naming it.
type TransportError ΒΆ
type TransportError struct {
APIError
}
TransportError is returned when the request got no answer: a network error, a timeout or a cancelled context. Its StatusCode is 0 and the original error is its Err: errors.Is(err, context.DeadlineExceeded) tells a timeout of the context.
func (*TransportError) Unwrap ΒΆ
func (e *TransportError) Unwrap() error
Unwrap returns the embedded *APIError, so errors.As and errors.Is find it and the original error.
type UnauthorizedError ΒΆ
type UnauthorizedError struct {
}
UnauthorizedError is returned when authentication fails (401): the API key is missing, invalid or revoked, or the request comes from an IP address the key does not allow.
func (*UnauthorizedError) Unwrap ΒΆ
func (e *UnauthorizedError) Unwrap() error
Unwrap returns the embedded *APIError, so errors.As finds it.
type UpdateOptOutRequest ΒΆ
type UpdateOptOutRequest struct {
// Phone is the phone number in international E.164 format. Required.
Phone string
// Reason is why the number opted out, at most 600 characters.
Reason string
}
UpdateOptOutRequest is the new data of an opt-out.
type ValidationError ΒΆ
type ValidationError struct {
APIError
// ValidationErrors are the field errors, field name β messages, e.g.
// {"recipient": ["The recipient field is required."]}; empty when the answer
// carried none.
ValidationErrors map[string][]string
}
ValidationError is returned for validation errors (422): the request's data was refused, e.g. an invalid or opted-out recipient, or a wrong verification code.
func (*ValidationError) Unwrap ΒΆ
func (e *ValidationError) Unwrap() error
Unwrap returns the embedded *APIError, so errors.As finds it.
type Verification ΒΆ
type Verification struct {
ID string `json:"id"`
Phone string `json:"phone"`
// Brand is the brand named in the default message; empty when a template was used.
Brand string `json:"brand"`
Status VerificationStatus `json:"status"`
// Template is the custom message, holding {code}; empty for the default message.
Template string `json:"template"`
CreatedAt time.Time `json:"created_at"`
ExpiresAt time.Time `json:"expires_at"`
// VerifiedAt is when the code was validated; nil until it is.
VerifiedAt *time.Time `json:"verified_at"`
// Attempts is how many times a code was checked against this verification.
Attempts int `json:"attempts"`
Expired bool `json:"expired"`
}
Verification is a phone verification: a one-time code sent by SMS, to be validated.
func (*Verification) UnmarshalJSON ΒΆ
func (v *Verification) UnmarshalJSON(data []byte) error
UnmarshalJSON reads a verification as the API sends it, the way the client does: a required field missing or malformed is an error naming it.
type VerificationCheckRequest ΒΆ
type VerificationCheckRequest struct {
// Phone is the phone number the code was sent to, in international E.164
// format. Required.
Phone string
// Code is the 6-character code received, in any case. Required.
Code string
}
VerificationCheckRequest is a code to check against the latest verification of a phone number.
type VerificationListOptions ΒΆ
type VerificationListOptions struct {
// Page is the page to read, from 1, each holding 50 items; 0 reads the first one.
Page int
// Status keeps only the verifications with this status; all of them when empty.
Status VerificationStatus
}
VerificationListOptions are the options of VerificationService.List.
type VerificationService ΒΆ
type VerificationService struct {
// contains filtered or unexported fields
}
VerificationService verifies phone numbers with one-time codes (OTP, 2FA): reach it as Client.Verifications.
func (*VerificationService) Create ΒΆ
func (s *VerificationService) Create(ctx context.Context, request *CreateVerificationRequest) (*Response[Verification], error)
Create starts a verification: Calisero generates a code and sends it by SMS.
The code's SMS counts towards the account's daily sending limit: its ResponseMeta holds what is left of it, and once it is reached Create returns a *DailyLimitExceededError.
func (*VerificationService) Get ΒΆ
func (s *VerificationService) Get(ctx context.Context, verificationID string) (*Response[Verification], error)
Get retrieves a verification by its ID.
func (*VerificationService) List ΒΆ
func (s *VerificationService) List(ctx context.Context, options *VerificationListOptions) (*Page[Verification], error)
List lists the verifications, newest first, 50 a page; options may be nil.
func (*VerificationService) Validate ΒΆ
func (s *VerificationService) Validate(ctx context.Context, request *VerificationCheckRequest) (*Response[Verification], error)
Validate checks a code against the latest verification of a phone number.
A wrong, expired or exhausted code (5 attempts) returns a *ValidationError whose ValidationErrors["code"] says which; a phone number without a verification a *NotFoundError. Checking a verified code again succeeds.
Example ΒΆ
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
calisero "github.com/calisero/calisero-go"
)
func main() {
client, err := calisero.NewClient(os.Getenv("CALISERO_API_KEY"))
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
// 1. Send a code (6 characters, valid for 5 minutes by default)
_, err = client.Verifications.Create(ctx, &calisero.CreateVerificationRequest{Phone: "+40742123456", Brand: "YourApp"})
if err != nil {
log.Fatal(err)
}
// 2. Check the code the user typed
userInput := "ABC123"
response, err := client.Verifications.Validate(ctx, &calisero.VerificationCheckRequest{Phone: "+40742123456", Code: userInput})
var validationError *calisero.ValidationError
switch {
case errors.As(err, &validationError):
// A wrong or expired code, or too many attempts (5)
fmt.Println(validationError.ValidationErrors["code"])
case err != nil:
log.Fatal(err)
default:
fmt.Println("Verified at", response.Data.VerifiedAt)
}
}
Output:
type VerificationStatus ΒΆ
type VerificationStatus string
VerificationStatus is the status of a verification; other values may come with future API versions.
const ( VerificationStatusUnverified VerificationStatus = "unverified" VerificationStatusVerified VerificationStatus = "verified" )
The statuses of a verification.
Source Files
ΒΆ
Directories
ΒΆ
| Path | Synopsis |
|---|---|
|
examples
|
|
|
account/check_balance
command
Check the credit and the daily sending limit before a campaign.
|
Check the credit and the daily sending limit before a campaign. |
|
account/get_account
command
Retrieve the account: details, credit, status and daily sending limit.
|
Retrieve the account: details, credit, status and daily sending limit. |
|
error_handling_complete
command
Every error the library returns, and how to handle each.
|
Every error the library returns, and how to handle each. |
|
messages/delete_sms
command
Cancel a scheduled message: schedule one for tomorrow, then delete it.
|
Cancel a scheduled message: schedule one for tomorrow, then delete it. |
|
messages/get_sms
command
Retrieve a message and its delivery status.
|
Retrieve a message and its delivery status. |
|
messages/list_sms
command
List the messages, newest first, 50 a page.
|
List the messages, newest first, 50 a page. |
|
messages/send_advanced_sms
command
Send an SMS with every option: a body shown masked, a validity, a scheduled time, a delivery status callback, a sender ID and URL shortening.
|
Send an SMS with every option: a body shown masked, a validity, a scheduled time, a delivery status callback, a sender ID and URL shortening. |
|
messages/send_bulk_sms
command
Send the same message to several recipients, one request each, stopping once the account's daily sending limit is used up.
|
Send the same message to several recipients, one request each, stopping once the account's daily sending limit is used up. |
|
messages/send_simple_sms
command
Send a simple SMS message.
|
Send a simple SMS message. |
|
optouts/create_optout
command
Opt a phone number out: the API then refuses to send it messages.
|
Opt a phone number out: the API then refuses to send it messages. |
|
optouts/delete_optout
command
Delete an opt-out: the number can receive messages again.
|
Delete an opt-out: the number can receive messages again. |
|
optouts/get_optout
command
Retrieve an opt-out by its ID.
|
Retrieve an opt-out by its ID. |
|
optouts/list_optouts
command
List the opt-outs, 50 a page.
|
List the opt-outs, 50 a page. |
|
optouts/update_optout
command
Update the reason of an opt-out.
|
Update the reason of an opt-out. |
|
verifications/create_verification
command
Start a verification: Calisero generates a 6-character code and sends it by SMS.
|
Start a verification: Calisero generates a 6-character code and sends it by SMS. |
|
verifications/get_verification
command
Retrieve a verification by its ID.
|
Retrieve a verification by its ID. |
|
verifications/list_verifications
command
List the verifications, newest first, optionally only the verified or the unverified ones.
|
List the verifications, newest first, optionally only the verified or the unverified ones. |
|
verifications/validate_verification
command
Check the code a user received against the latest verification of their phone.
|
Check the code a user received against the latest verification of their phone. |
|
webhooks/delivery_webhook
command
Receive delivery status callbacks.
|
Receive delivery status callbacks. |