Documentation
¶
Overview ¶
Package honk is the official Go client for Honk (https://honk-honk.app), the inbox that turns events from apps, scripts, cron jobs and CI into calm, grouped push notifications.
c, err := honk.FromEnv() // HONK_URL, HONK_KEY
if err != nil { log.Fatal(err) }
c.Uhoh(ctx, "oven/kitchen", "The cake is burning", "Oven at 260 °C")
c.Phew(ctx, "oven/kitchen", "Cake saved", "Nobody needs to know.")
Uhoh opens an incident for its group key, Phew closes it, and Honk sends any other Message. Before 0.3.0 they were Problem, Recovery and Send; those names still work and send exactly the same requests.
Every call carries an Idempotency-Key (a UUIDv7 unless you pass WithIdempotencyKey) that is reused on every retry, so a lost response never creates a duplicate. Only network errors, 429 and 5xx are retried, with exponential backoff, full jitter and Retry-After, within a total deadline. Errors are *Error; match them with errors.Is(err, honk.ErrQuota) and friends, or errors.As for the details.
The ingestion key belongs on the server side only. Call the client from a goroutine or a queue so the critical path of your service never waits for a notification.
Example ¶
package main
import (
"context"
"log"
honk "github.com/honk-hq/go"
)
func main() {
c, err := honk.FromEnv() // HONK_URL, HONK_KEY
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
if _, err := c.Uhoh(ctx, "db/backup", "Backup failed", "pg_dump exited with 1"); err != nil {
log.Print(err)
}
}
Output:
Index ¶
- Constants
- Variables
- func EncodeMessage(m Message, defaults Defaults) ([]byte, error)
- func NewIdempotencyKey() string
- func Ptr[T any](v T) *T
- type Accepted
- type Action
- type Backoff
- type Category
- type Client
- func (c *Client) Beep(ctx context.Context, title, message string, opts ...Option) (*Accepted, error)
- func (c *Client) Blast(ctx context.Context, title, message string, opts ...Option) (*Accepted, error)
- func (c *Client) Critical(ctx context.Context, title, message string, opts ...Option) (*Accepted, error)
- func (c *Client) Error(ctx context.Context, title, message string, opts ...Option) (*Accepted, error)
- func (c *Client) Honk(ctx context.Context, m Message, opts ...Option) (*Accepted, error)
- func (c *Client) Info(ctx context.Context, title, message string, opts ...Option) (*Accepted, error)
- func (c *Client) Light(ctx context.Context, title, message string, opts ...Option) (*Accepted, error)
- func (c *Client) Long(ctx context.Context, title, message string, opts ...Option) (*Accepted, error)
- func (c *Client) Loud(ctx context.Context, title, message string, opts ...Option) (*Accepted, error)
- func (c *Client) Phew(ctx context.Context, groupKey, title, message string, opts ...Option) (*Accepted, error)
- func (c *Client) Problem(ctx context.Context, groupKey, title, message string, opts ...Option) (*Accepted, error)
- func (c *Client) Recovery(ctx context.Context, groupKey, title, message string, opts ...Option) (*Accepted, error)
- func (c *Client) Send(ctx context.Context, m Message, opts ...Option) (*Accepted, error)
- func (c *Client) Success(ctx context.Context, title, message string, opts ...Option) (*Accepted, error)
- func (c *Client) Uhoh(ctx context.Context, groupKey, title, message string, opts ...Option) (*Accepted, error)
- func (c *Client) Warning(ctx context.Context, title, message string, opts ...Option) (*Accepted, error)
- type Defaults
- type Error
- type EventType
- type FieldError
- type Kind
- type Message
- type Option
- func WithActions(actions ...Action) Option
- func WithCategory(cat Category) Option
- func WithChannel(ch string) Option
- func WithEnvironment(e string) Option
- func WithGroupKey(k string) Option
- func WithIdempotencyKey(key string) Option
- func WithImageURL(u string) Option
- func WithMetadata(md map[string]any) Option
- func WithOccurredAt(t time.Time) Option
- func WithPriority(p Priority) Option
- func WithSeverity(s Severity) Option
- func WithSource(s string) Option
- func WithSourceSequence(n int64) Option
- func WithTTLSeconds(s int) Option
- func WithURL(u string) Option
- type Options
- type Priority
- type Severity
Examples ¶
Constants ¶
const ( MaxBodyBytes = 16 << 10 MaxMessageBytes = 8192 MaxTitle = 160 MaxSource = 64 MaxEnvironment = 32 MaxChannel = 64 MaxGroupKey = 128 MaxURLBytes = 2048 MaxActions = 3 MaxActionTitle = 40 MaxMetadataKeys = 16 MaxMetadataString = 512 MinTTLSeconds = 60 MaxTTLSeconds = 86400 )
Limits from contracts/openapi.yaml (and the server's validator).
const NoRetries = -1
NoRetries disables retries when used as Options.Retries.
const Version = "0.4.0"
Version of this SDK, sent in the User-Agent header.
Variables ¶
var ( ErrValidation = errors.New("honk: invalid message") ErrAuth = errors.New("honk: authentication failed") ErrQuota = errors.New("honk: quota or rate limit exceeded") ErrConflict = errors.New("honk: idempotency conflict") ErrNetwork = errors.New("honk: network error") ErrTimeout = errors.New("honk: timeout") ErrServer = errors.New("honk: server error") )
Sentinels for errors.Is: errors.Is(err, honk.ErrQuota).
var SeverityAliases = map[string]Severity{ "light": SeverityInfo, "beep": SeveritySuccess, "loud": SeverityWarning, "long": SeverityError, "blast": SeverityCritical, }
SeverityAliases maps the horn names to the canonical severities.
Functions ¶
func EncodeMessage ¶
EncodeMessage validates m (with defaults applied) and returns the exact JSON body Honk would post. Useful for logging, dry runs and tests.
func NewIdempotencyKey ¶
func NewIdempotencyKey() string
NewIdempotencyKey returns a new UUIDv7 (RFC 9562): 48-bit Unix milliseconds, then random bits. Store it with your job if you want to retry the same event across process restarts.
Types ¶
type Accepted ¶
type Accepted struct {
// ID of the message (msg_…). The original ID when Duplicate is true.
ID string `json:"id"`
// Duplicate is true when this idempotency key was already accepted with the same payload
// in the last 24 hours.
Duplicate bool `json:"duplicate"`
// ReceivedAt is when the server accepted it (the first time, for a duplicate).
ReceivedAt time.Time `json:"received_at"`
}
Accepted is the 202 answer: the message is durably stored (not necessarily pushed yet).
type Action ¶
type Action struct {
// Title is 1–40 characters (trimmed), one line, shown as sent: "Reply", "Call Emily".
Title string `json:"title"`
// URL is https:// (no credentials), mailto: with one address (?subject=…&body=…
// percent-encoded, no other keys), tel: or sms: with a number (sms: also ?body=…), at most
// 2048 bytes without spaces. Other schemes are refused.
URL string `json:"url"`
}
Action is a button on a message. Honk never opens or fetches the URL; the app opens it when the user taps the button.
Example ¶
Buttons on a customer request: reply by email or call back, straight from the notification.
package main
import (
"context"
"log"
"net/url"
honk "github.com/honk-hq/go"
)
func main() {
c, _ := honk.FromEnv()
_, err := c.Honk(context.Background(), honk.Message{
Title: "New request: online shop quote",
Message: "Emily Carter (Acme) asked for a quote: online shop, 40 products",
Category: honk.CategoryCustomers,
GroupKey: "requests/4812",
Actions: []honk.Action{
{Title: "Reply", URL: "mailto:emily@example.com?subject=" + url.PathEscape("Your quote")},
{Title: "Call Emily", URL: "tel:+15550134"},
},
}, honk.WithIdempotencyKey("request-4812"))
if err != nil {
log.Printf("honk: %v", err)
}
}
Output:
type Category ¶
type Category string
Category from taxonomy v1.
const ( CategoryInfrastructure Category = "infrastructure" CategorySecurity Category = "security" CategoryBackups Category = "backups" CategoryDeployments Category = "deployments" CategoryPayments Category = "payments" CategoryCustomers Category = "customers" CategorySales Category = "sales" CategoryAutomation Category = "automation" CategoryPersonal Category = "personal" CategoryOther Category = "other" )
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client sends messages to one Honk project. It is safe for concurrent use; create one and reuse it so connections are kept alive.
func (*Client) Beep ¶
func (c *Client) Beep(ctx context.Context, title, message string, opts ...Option) (*Accepted, error)
Beep sends a beep-beep (severity success).
func (*Client) Blast ¶
func (c *Client) Blast(ctx context.Context, title, message string, opts ...Option) (*Accepted, error)
Blast sends a blast (severity critical; pushes at least as high priority).
func (*Client) Critical ¶
func (c *Client) Critical(ctx context.Context, title, message string, opts ...Option) (*Accepted, error)
Critical is a synonym of Blast.
func (*Client) Error ¶
func (c *Client) Error(ctx context.Context, title, message string, opts ...Option) (*Accepted, error)
Error is a synonym of Long.
func (*Client) Honk ¶
Honk sends one event and returns once the server has durably stored it (202), which does not mean a push was delivered. Network errors, 429 and 5xx are retried with the same Idempotency-Key until Retries or the deadline runs out. Errors are *Error.
c.Honk(ctx, honk.Message{Title: "Someone bought 3 rubber chickens", Message: "Order 1042, €2.97", Severity: honk.Beep})
Example ¶
package main
import (
"context"
"log"
honk "github.com/honk-hq/go"
)
func main() {
c, _ := honk.FromEnv()
_, err := c.Honk(context.Background(), honk.Message{
Title: "Someone bought 3 rubber chickens",
Message: "Order 1042, €2.97",
Severity: honk.Beep,
Category: honk.CategorySales,
}, honk.WithIdempotencyKey("order-1042"))
if err != nil {
log.Printf("honk: %v", err)
}
}
Output:
Example (CustomerRequest) ¶
A customer request: one group per request, pushed right away, linked to the admin page.
package main
import (
"context"
"log"
honk "github.com/honk-hq/go"
)
func main() {
c, _ := honk.FromEnv()
go func() { // never block the request path on a notification
_, err := c.Honk(context.Background(), honk.Message{
Title: "New request: online shop quote",
Message: "Ana Pop (Acme) asked for a quote: 40 products, delivery in November.",
Priority: honk.PriorityHigh,
Category: honk.CategoryCustomers,
Channel: "requests",
GroupKey: "requests/4812",
URL: "https://shop.example.com/admin/requests/4812",
Metadata: map[string]any{"request_id": "4812"},
}, honk.WithIdempotencyKey("request-4812"))
if err != nil {
log.Printf("honk: %v", err)
}
}()
}
Output:
func (*Client) Info ¶
func (c *Client) Info(ctx context.Context, title, message string, opts ...Option) (*Accepted, error)
Info is a synonym of Light.
func (*Client) Light ¶
func (c *Client) Light(ctx context.Context, title, message string, opts ...Option) (*Accepted, error)
Light sends a light honk (severity info). An empty title lets the server derive one.
func (*Client) Long ¶
func (c *Client) Long(ctx context.Context, title, message string, opts ...Option) (*Accepted, error)
Long sends a long honk (severity error; pushes at least as high priority).
func (*Client) Loud ¶
func (c *Client) Loud(ctx context.Context, title, message string, opts ...Option) (*Accepted, error)
Loud sends a loud honk (severity warning).
func (*Client) Phew ¶
func (c *Client) Phew(ctx context.Context, groupKey, title, message string, opts ...Option) (*Accepted, error)
Phew reports that groupKey works again: a recovery (closes its open incident). Severity defaults to Beep (success); WithSeverity overrides it.
c.Phew(ctx, "oven/kitchen", "Cake saved", "Nobody needs to know.")
func (*Client) Problem ¶
func (c *Client) Problem(ctx context.Context, groupKey, title, message string, opts ...Option) (*Accepted, error)
Problem is the old name of Uhoh: same arguments, same request.
func (*Client) Recovery ¶
func (c *Client) Recovery(ctx context.Context, groupKey, title, message string, opts ...Option) (*Accepted, error)
Recovery is the old name of Phew: same arguments, same request.
func (*Client) Success ¶
func (c *Client) Success(ctx context.Context, title, message string, opts ...Option) (*Accepted, error)
Success is a synonym of Beep.
func (*Client) Uhoh ¶
func (c *Client) Uhoh(ctx context.Context, groupKey, title, message string, opts ...Option) (*Accepted, error)
Uhoh reports that something broke: a problem for groupKey (opens or continues its incident). Severity defaults to Long (error); WithSeverity overrides it.
c.Uhoh(ctx, "oven/kitchen", "The cake is burning", "Oven at 260 °C")
Example ¶
An oven sensor: Uhoh opens an incident for the group, Phew closes it.
package main
import (
"context"
"log"
honk "github.com/honk-hq/go"
)
func main() {
c, _ := honk.FromEnv()
ctx := context.Background()
if _, err := c.Uhoh(ctx, "oven/kitchen", "The cake is burning", "Oven at 260 °C. The smoke alarm agrees."); err != nil {
log.Print(err)
}
// …a few tense minutes later
if _, err := c.Phew(ctx, "oven/kitchen", "Cake saved", "Nobody needs to know."); err != nil {
log.Print(err)
}
}
Output:
type Error ¶
type Error struct {
Kind Kind
// Status is the HTTP status (0 when no answer was received).
Status int
// Code is the API error code (invalid_key, quota_exceeded, …), or network_error/timeout.
Code string
Message string
// Fields lists invalid fields for KindValidation.
Fields []FieldError
// Local is true when the SDK rejected the message before sending anything.
Local bool
RequestID string
// IdempotencyKey that was used. Retry later with the same key to stay duplicate-free.
IdempotencyKey string
// Attempts is the number of HTTP attempts made.
Attempts int
// RetryAfter is the server's Retry-After, when present.
RetryAfter time.Duration
// Err is the underlying transport or context error, if any.
Err error
}
Error is returned by Honk and the helpers. Use errors.As to read the details, or errors.Is with the Err* sentinels.
Example ¶
package main
import (
"context"
"errors"
"log"
"time"
honk "github.com/honk-hq/go"
)
func main() {
c, _ := honk.FromEnv()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_, err := c.Long(ctx, "Payment failed", "Stripe declined order 1042", honk.WithGroupKey("payments/stripe"))
var he *honk.Error
switch {
case err == nil:
case errors.Is(err, honk.ErrValidation):
log.Printf("fix the message: %v", err) // he.Fields lists every invalid field
case errors.Is(err, honk.ErrQuota) && errors.As(err, &he):
log.Printf("over quota, retry in %s", he.RetryAfter)
case errors.As(err, &he) && he.Retryable():
log.Printf("Honk unreachable; retry later with key %s", he.IdempotencyKey)
default:
log.Print(err)
}
}
Output:
type EventType ¶
type EventType string
EventType: a problem opens an incident for its group, a recovery closes it. Both need a GroupKey.
type FieldError ¶
type FieldError struct {
// Field is the wire name: group_key, metadata.region, Idempotency-Key, body, …
Field string `json:"field"`
// Code: required, too_long, too_short, invalid_enum, invalid_format, out_of_range,
// not_allowed, invalid_utf8, requires_group_key.
Code string `json:"code"`
Message string `json:"message,omitempty"`
}
FieldError is one invalid field, from the server (error.fields[]) or local validation.
type Kind ¶
type Kind string
Kind classifies an *Error.
const ( // KindValidation: the message is invalid (rejected locally, or 400/413/415/422). Fix it. KindValidation Kind = "validation" // KindAuth: 401/403 — invalid or revoked key, priority_not_allowed (urgent without // allow_urgent), project_suspended, workspace_suspended. KindAuth Kind = "auth" // KindQuota: 429 quota_exceeded (daily messages_per_day, until UTC midnight) or // rate_limited, after retries. See RetryAfter. KindQuota Kind = "quota" // KindConflict: 409 idempotency_conflict — the key was already used with a different // payload in the last 24 hours. KindConflict Kind = "conflict" // KindNetwork: Honk could not be reached on any attempt before the deadline. KindNetwork Kind = "network" // KindTimeout: attempts timed out (or the context deadline passed). The event may or may // not have been stored; retrying with the same idempotency key is safe. KindTimeout Kind = "timeout" // KindCanceled: the context was canceled. KindCanceled Kind = "canceled" // KindServer: 5xx on every attempt before the deadline. KindServer Kind = "server" // KindHTTP: any other unexpected answer (404 wrong URL, a redirect, a malformed 202). KindHTTP Kind = "http" )
type Message ¶
type Message struct {
// Title is one line, at most 160 characters. Defaults to the first line of Message.
Title string
// Message is plain text, 1–8192 bytes of UTF-8. Line breaks and tabs are allowed.
Message string
// Severity: a horn name (Light, Beep, Loud, Long, Blast) or canonical value; any case.
// Sent canonical. Default light (info).
Severity Severity
Priority Priority
Category Category
// Source (≤ 64), Environment (≤ 32) and Channel (≤ 64) default to the client's Defaults.
Source string
Environment string
Channel string
// GroupKey (≤ 128) relates occurrences: messages with the same key (per environment,
// source and channel) form one group, so the first one pushes and repeats update it calmly.
// Use one key per customer request ("requests/<id>"), a shared key only for repeats of the
// same problem.
GroupKey string
EventType EventType
// OccurredAt is when it happened at the source (informational). Zero means unset.
OccurredAt time.Time
// URL is an https link shown as "Open link" (no credentials, ≤ 2048 bytes).
URL string
// ImageURL is an https image the server fetches after ingestion (no credentials or
// fragment, ≤ 2048 bytes).
ImageURL string
// Actions are up to 3 buttons, in display order (the first is the primary). Empty means
// none.
Actions []Action
// Metadata has at most 16 keys matching [A-Za-z0-9_.-]{1,64}; values are strings
// (≤ 512 characters), numbers or booleans.
Metadata map[string]any
// TTLSeconds is the push lifetime, 60–86400. Zero means the server default (3600).
TTLSeconds int
// SourceSequence is a monotonic counter per source stream (0 … 2^53-1) so a delayed
// recovery can never close a newer problem. Requires GroupKey. Use honk.Ptr(n).
SourceSequence *int64
}
Message is one event for POST /v1/messages. Only Message is required; zero values are omitted, so the server defaults apply (severity info, priority normal, source "api", environment "default", channel "general", event type event, TTL 3600 s).
type Option ¶
type Option func(*sendCall)
Option adjusts one Honk (or helper) call: the idempotency key, or message fields.
func WithActions ¶
WithActions appends buttons to the message (at most 3 in all, in display order).
func WithChannel ¶
WithChannel sets the channel (≤ 64 characters).
func WithEnvironment ¶
WithEnvironment sets the environment (≤ 32 characters).
func WithGroupKey ¶
WithGroupKey sets the group key, e.g. "requests/4812".
func WithIdempotencyKey ¶
WithIdempotencyKey sets a stable key for this event (1–128 printable ASCII characters), e.g. "request-4812". It is reused on every retry. Default: a new UUIDv7 per call.
func WithImageURL ¶
WithImageURL sets an https image the server fetches after ingestion.
func WithMetadata ¶
WithMetadata merges keys into the message metadata.
func WithOccurredAt ¶
WithOccurredAt sets when the event happened at the source.
func WithPriority ¶
WithPriority sets the declared priority (urgent needs a key with allow_urgent).
func WithSeverity ¶
WithSeverity sets the severity: a horn name (honk.Loud) or canonical value, any case (helpers like Uhoh default it; Light…Blast fix it).
func WithSourceSequence ¶
WithSourceSequence sets the monotonic sequence for problem/recovery ordering.
func WithTTLSeconds ¶
WithTTLSeconds sets the push lifetime (60–86400 seconds).
type Options ¶
type Options struct {
// URL is the base address of your Honk server, e.g. https://honk.example.com.
URL string
// Key is a project ingestion key (honk_…). Keep it server-side.
Key string
// Timeout of one HTTP attempt. Default 5s.
Timeout time.Duration
// Retries after the first attempt (network errors, 429 and 5xx only). 0 means the
// default (4); use NoRetries to disable.
Retries int
// Deadline is the total time budget of one Honk call, waits included. Default 30s. A
// shorter context deadline wins.
Deadline time.Duration
// Defaults fill Source/Environment/Channel when a message leaves them empty.
Defaults Defaults
// SkipValidation sends messages without local checks (the server always validates).
SkipValidation bool
// Backoff between retries: attempt n waits rand(0, min(Max, Base·2ⁿ)) (full jitter), or
// the server's Retry-After when longer. Default 500ms / 8s.
Backoff Backoff
// HTTPClient to use (proxies, instrumentation). Its CheckRedirect is overridden so
// redirects are reported instead of followed. Default: a keep-alive client.
HTTPClient *http.Client
// UserAgent is appended to the User-Agent header.
UserAgent string
}
Options configure a Client. URL and Key are required.
func OptionsFromEnv ¶
func OptionsFromEnv() Options
OptionsFromEnv reads HONK_URL, HONK_KEY and the optional HONK_SOURCE, HONK_ENVIRONMENT and HONK_CHANNEL defaults.
type Priority ¶
type Priority string
Priority declared by the source. Urgent needs an ingestion key with allow_urgent.
type Severity ¶
type Severity string
Severity of a message, lowest to highest. Error and critical raise the effective priority to at least high. Every severity has a horn name on the Honk scale (Light, Beep, Loud, Long, Blast); those constants are aliases of the canonical values.
const ( Light Severity = SeverityInfo // light honk Beep Severity = SeveritySuccess // beep-beep Loud Severity = SeverityWarning // loud honk Long Severity = SeverityError // long honk Blast Severity = SeverityCritical // blast )
The Honk scale: horn names, aliases of the canonical severities (honk.Loud == honk.SeverityWarning).
const ( SeverityInfo Severity = "info" SeveritySuccess Severity = "success" SeverityWarning Severity = "warning" SeverityError Severity = "error" SeverityCritical Severity = "critical" )
Canonical severities, as stored and returned by the server.
func ParseSeverity ¶
ParseSeverity returns the canonical severity for a canonical value or horn alias, case-insensitively: ParseSeverity("LOUD") == SeverityWarning.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
honk
command
Command honk sends one event to Honk from a shell script, cron job or CI step.
|
Command honk sends one event to Honk from a shell script, cron job or CI step. |
|
internal
|
|
|
cli
Package cli is the honk command line tool: it sends one event to Honk from a shell script, cron job or CI step.
|
Package cli is the honk command line tool: it sends one event to Honk from a shell script, cron job or CI step. |