notifypost

package
v1.147.0 Latest Latest
Warning

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

Go to latest
Published: Oct 17, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package notifypost is the transport half of notification channels: one Sender per chat or webhook kind, turning a document into a post on its upstream.

It is separate from notifychannel, which persists the operator's channel records, because the two share nothing: a record is read and written by the admin surface, a post is made by the send worker, and neither needs the other's machinery. What they share is the Channel value, which lives in the domain both depend on.

No sender holds a credential. The three kinds deliver through an Upstream the api gateway materializes for the connection the channel names, so a bot token lives exactly where every other upstream credential lives.

Index

Constants

View Source
const MattermostMaxTextBytes = 16000

MattermostMaxTextBytes is the most a mattermost channel sends in one message. A Mattermost post is capped at 16,383 characters by the server; the platform cuts below that and keeps the link, so the server never refuses a post for its length.

View Source
const (
	// SlackMaxMarkdownBytes is the most a slack channel sends in its
	// markdown block. The platform cuts below it and keeps the link.
	SlackMaxMarkdownBytes = 12000
)

Slack's bounds. The markdown block's cap is cumulative across a payload's markdown blocks; the header's is its plain text.

View Source
const WebhookMaxTextBytes = 12000

WebhookMaxTextBytes is the most a webhook channel sends in one message. An incoming webhook is consumed by whichever server the URL belongs to, most often Slack or Mattermost, so the cap is the lower of the two.

Variables

View Source
var ErrTerminal = errors.New("notifychannel: upstream refused the message")

ErrTerminal marks a send failure that retrying cannot fix: the upstream refused the message itself rather than failing to receive it. A channel posting to a chat channel that was deleted, or through a bot that was uninstalled, fails this way on every attempt, so burning five of them delays nothing and fills the history with the same line five times.

The worker matches it with errors.Is and fails the row at once. Everything else -- a timeout, a refused connection, a 5xx, a rate limit -- retries on the existing backoff.

Functions

func Bulletin added in v1.143.0

Bulletin composes a daily channel's batch into the one document it posts: a line per document with its title, its first paragraph and its link, so a day's findings read as one summary rather than as a burst of messages.

Attachments are not carried. A bulletin is a summary, and each item's link is where its files are; carrying five files from each of a day's documents would make the bulletin the burst it exists to replace.

Types

type DeliveryIDCarrier added in v1.138.3

type DeliveryIDCarrier interface {
	DeliveryIDHeader() string
}

DeliveryIDCarrier is an Upstream that names a header a delivery's id is sent in: an hmac connection's hmac_id_header, which its signature covers. *upstreamcall.Upstream implements it.

type Envelope added in v1.138.3

type Envelope struct {
	// ID is the queue row's id, the same on every retry of that row, so a
	// receiver deduplicates on it.
	ID string `json:"id"`
	// Type is what produced the notification, one of the
	// notification.Document* types; a receiver routes on it.
	Type       string    `json:"type"`
	OccurredAt time.Time `json:"occurred_at"`
	Deployment string    `json:"deployment,omitempty"`
	Channel    string    `json:"channel"`
	Title      string    `json:"title"`
	// Body is the markdown body as sent, whole: a system reads fields, and a
	// chat client's display cap does not apply to it.
	Body   string                       `json:"body,omitempty"`
	Link   string                       `json:"link,omitempty"`
	Source *notification.DocumentSource `json:"source,omitempty"`
	// Attachments name the files the notification carries, each with its
	// link; a webhook carries no file content.
	Attachments []notification.Attachment `json:"attachments,omitempty"`
	// Data is the sender's structured payload, verbatim.
	Data json.RawMessage `json:"data,omitempty"`
}

Envelope is the body a JSON webhook channel posts for one notification.

type MattermostSender

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

MattermostSender posts to a Mattermost channel through the v4 REST API.

The bot token is the connection's credential, applied by the connection's authenticator as a bearer token, so this sender never sees it.

func NewMattermostSender

func NewMattermostSender(upstream UpstreamFunc) *MattermostSender

NewMattermostSender builds the mattermost transport over upstream.

func (*MattermostSender) Send

Send uploads the delivery's files to the channel, then posts the document with them.

Mattermost answers an application-level refusal with the matching HTTP status rather than with a 200 and a flag, so the shared status classification is the whole verdict and there is no envelope to read.

An upload that fails fails the send, retried or terminal by the same classification as the post: a message that names files it does not carry would read as the report having been sent when it was not. A retry uploads again; an upload is not a post, so a file Mattermost holds from an attempt that went no further is shown to nobody.

type RetryAfterError added in v1.138.3

type RetryAfterError struct {
	After time.Duration
	Err   error
}

RetryAfterError is a send the upstream asked to have retried after a delay: a 429 or a 503 carrying Retry-After. The worker waits that long, within its own longest backoff, rather than its own schedule.

func (*RetryAfterError) Error added in v1.138.3

func (r *RetryAfterError) Error() string

Error is the upstream's answer.

func (*RetryAfterError) Unwrap added in v1.138.3

func (r *RetryAfterError) Unwrap() error

Unwrap returns the upstream's answer.

type Sender

type Sender interface {
	// Send posts d to ch, returning ErrTerminal for a refusal that
	// retrying cannot fix.
	Send(ctx context.Context, ch notification.Channel, d notification.Delivery) error
}

Sender delivers one document to one channel of its kind.

type Senders

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

Senders dispatches a document to the sender for its channel's kind.

The email kind is absent: an email channel fans out at enqueue to one ordinary queue row per recipient, which the existing renderer and SMTP sender deliver. There is nothing for a channel transport to do with it, and a second mail path would be a second place for the deployment's mail settings to be read.

func NewSenders

func NewSenders(upstream UpstreamFunc, deployment string) *Senders

NewSenders builds the dispatcher for the HTTP kinds over upstream.

deployment names this deployment in a JSON webhook envelope, so a receiver fed by several can tell them apart.

func (*Senders) For

func (s *Senders) For(kind string) (Sender, bool)

For returns the sender for a kind, and whether this dispatcher delivers it.

func (*Senders) Send

Send delivers d through the sender for ch's kind.

type SlackSender added in v1.143.0

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

SlackSender posts to a Slack channel through the Web API with the bot token the connection holds as its bearer credential.

Slack answers an application-level refusal with HTTP 200 and ok: false, so the shared status classification is not the whole verdict: every answer's envelope is read, and its error code decides whether a retry could help.

func NewSlackSender added in v1.143.0

func NewSlackSender(upstream UpstreamFunc) *SlackSender

NewSlackSender builds the slack transport over upstream.

func (*SlackSender) Send added in v1.143.0

Send uploads the delivery's files, posts the document, then shares the files into the post's thread.

The upload's first two steps come before the post: a file Slack has received but nobody has shared is seen by nobody, so a failure there costs a retry nothing. The share comes after, so the files sit with the message they belong to. A share that fails after the post is terminal and says the message was posted: retrying would post the message a second time.

type Upstream

type Upstream interface {
	// BaseURL is the connection's upstream root.
	BaseURL() string
	// Do applies the connection's authentication and static headers and
	// sends the request.
	Do(req *http.Request) (*http.Response, error)
}

Upstream is the authorized transport a channel delivers through: the connection's base URL and its ability to make an authorized call.

It is an interface here, satisfied by *apigateway.Upstream, so this package depends on the idea of an authorized upstream rather than on the api gateway. That keeps the senders testable against an httptest server and keeps the credential on the gateway's side of the seam.

type UpstreamFunc

type UpstreamFunc func(connection string) (Upstream, error)

UpstreamFunc resolves a connection name to its authorized transport. The platform wires it to the api gateway; a test wires it to an httptest server.

type WebhookSender

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

WebhookSender posts to a webhook in the channel's format: one {"text": ...} body for a chat client's incoming webhook (the default), or one JSON envelope for a system receiving events (#1997).

The whole address is the connection's base_url, including the secret path segment an incoming webhook URL carries, so the kind sends to the base itself and names no path. That secret is the connection's to hold: a webhook URL is a bearer credential written as a URL, which is why the kind names a connection like the two token kinds rather than storing a URL of its own. A connection can hold the segment as path_secret, encrypted, rather than in its base_url.

A JSON delivery is signed when the connection's auth_mode is hmac: the channel holds no secret and no signing settings of its own.

func NewWebhookSender

func NewWebhookSender(upstream UpstreamFunc, deployment string) *WebhookSender

NewWebhookSender builds the webhook transport over upstream.

func (*WebhookSender) Send

Send posts the delivery to the webhook.

Jump to

Keyboard shortcuts

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