Documentation
¶
Overview ¶
Package sendlimit enforces an Integration's Send rate limit (ADR 0023): two token buckets, per-second and rolling 24 hours, whose state is one send_limiters row per Integration in Postgres (no Redis). A reservation takes one token from both buckets or from none, in a single UPDATE through the scoped client, so concurrent workers and instances cannot overspend; a denial reports how long until capacity returns.
Buckets are stored as a fill fraction of capacity (1 is full). Capacity and refill derive from the limits passed in on every call, so a user changing a limit takes effect at once, and a bucket with no limit is simply kept full.
Index ¶
- func RefreshQuota(ctx context.Context, s *ent.Scoped, cipher *secrets.Cipher, ...) error
- func Reserve(ctx context.Context, s *ent.Scoped, integrationID int64, l Limits, ...) (time.Duration, error)
- func Spend(ctx context.Context, s *ent.Scoped, integrationID int64, l Limits, ...) error
- func Statuses(ctx context.Context, s *ent.Scoped, rows []*ent.Integration, now time.Time) (map[int64]Status, error)
- func Usage(ctx context.Context, s *ent.Scoped, now time.Time) (map[int64]int, error)
- type Effective
- type Limits
- type Source
- type Status
- type Value
- type Warning
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RefreshQuota ¶
func RefreshQuota(ctx context.Context, s *ent.Scoped, cipher *secrets.Cipher, catalog *messaging.Catalog, integ *ent.Integration) error
RefreshQuota asks the Integration's provider for its send quota and stores the answer, which EffectiveOf then folds into the ceiling by the minimum rule. It runs when the Integration is saved and hourly from the periodic job, because SES quotas grow as an account matures.
A failed lookup (no ses:GetSendQuota permission, an SES-compatible service such as Postbox, a timeout) never blocks sending and is not returned: it is recorded as the moment it first failed (provider_quota_failed_at), which the UI shows as a warning, and the last known provider values stay so a transient blip does not lift a ceiling. A later success clears the warning. Providers that cannot report a quota (SMTP) are left untouched. An error is returned only when the stored config cannot be read or the row cannot be written.
func Reserve ¶
func Reserve(ctx context.Context, s *ent.Scoped, integrationID int64, l Limits, now time.Time) (time.Duration, error)
Reserve takes one token from both buckets at now. It returns a zero wait when the token was taken, or, when a bucket is short, the time until both buckets hold a token again (nothing is taken then). A call with no limit set does nothing.
func Spend ¶
Spend takes one token from both buckets at now without waiting and without the capacity check, so a bucket may go negative (Transactional, ADR 0023: never delayed, yet it makes later marketing sends wait longer). A call with no limit set does nothing.
func Statuses ¶
func Statuses(ctx context.Context, s *ent.Scoped, rows []*ent.Integration, now time.Time) (map[int64]Status, error)
Statuses pairs the 24-hour usage with the effective ceilings of each Integration, keyed by Integration id. Usage is read once for all rows.
func Usage ¶
Usage counts the messages each of the Workspace's Integrations sent over the 24 hours before now, keyed by Integration id (an Integration that sent nothing is absent). It counts every Outbound message the provider accepted, Transactional included, because that is what the provider's own quota sees.
Types ¶
type Effective ¶
type Effective struct {
PerSecond Value
PerDay Value
// Integration's provider_quota_failed_at is set), so
// the ceilings above rest on the manual value alone (or on none).
ProviderQuotaUnavailable bool
}
Effective is an Integration's ceilings as enforced: per second and per rolling 24 hours. Per ADR 0023 each is the minimum of the available values; when two sources give the same number the user's manual value is reported.
func EffectiveOf ¶
func EffectiveOf(integ *ent.Integration) Effective
EffectiveOf computes the ceilings an Integration is held to. It is the one place manual, provider (and later warmup) values are folded into a ceiling.
type Limits ¶
Limits are an Integration's configured ceilings; a nil field is not limited. The effective ceiling is the minimum of the available values (manual only for now), so this is where a provider-reported or warmup value would be folded in.
type Status ¶
Status is an Integration's Send rate limit as shown to a user: the ceilings as enforced, the messages sent over the last 24 hours and what is worth flagging. Each surface maps it to its own DTO.
A Value with a nil Limit has an empty Source (see Value), so no surface restates it.
type Value ¶
Value is one effective ceiling: Limit is nil (and Source empty) when the window is not limited.