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 ¶
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.
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 ¶
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 ¶
This section is empty.
Types ¶
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 ¶
func (s *MattermostSender) Send(ctx context.Context, ch notification.Channel, doc notification.Document) error
Send posts the document to the channel's target.
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.
type Sender ¶
type Sender interface {
// Send posts doc to ch, returning ErrTerminal for a refusal that
// retrying cannot fix.
Send(ctx context.Context, ch notification.Channel, doc notification.Document) 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) *Senders
NewSenders builds the dispatcher for the HTTP kinds over upstream.
func (*Senders) Send ¶
func (s *Senders) Send(ctx context.Context, ch notification.Channel, doc notification.Document) error
Send delivers doc through the sender for ch's kind.
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 ¶
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 one {"text": ...} body to an incoming-webhook URL, the shape Slack and Mattermost both accept and the shape most other receivers of a "post this line somewhere" webhook understand.
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.
func NewWebhookSender ¶
func NewWebhookSender(upstream UpstreamFunc) *WebhookSender
NewWebhookSender builds the webhook transport over upstream.
func (*WebhookSender) Send ¶
func (s *WebhookSender) Send(ctx context.Context, ch notification.Channel, doc notification.Document) error
Send posts the document to the webhook.