amir20/dozzle · warning
cloud rate limited, backing off for
Error message
cloud rate limited, backing off for %s
What it means
Dozzle Cloud responded with HTTP 429 Too Many Requests. The dispatcher stores a blockedUntil timestamp (Retry-After header if present, otherwise 60s), logs a warning that the circuit breaker tripped, and returns this error for the current notification, which is dropped.
Solutions
- Respect the backoff: don't resend until the breaker window (Retry-After or default 60s) passes
- Throttle or debounce notification rules so volume stays under the cloud quota
- Batch related notifications instead of sending one per event
- Check your Dozzle Cloud plan limits if 429s recur regularly
Defensive patterns
Strategy: retry
Try / catch
if err := d.Send(ctx, n); err != nil {
if strings.Contains(err.Error(), "backing off") {
// respect the 60s+ backoff; queue and resend later
}
} Prevention
- Keep notification volume below cloud quota; batch and debounce alerts
- Never retry sends in a tight loop
- Monitor for recurring 429s and adjust plan or rules
When it happens
Trigger: CloudDispatcher.Send received resp.StatusCode == 429 from the cloud /api/events endpoint, i.e. the account exceeded the cloud-side notification rate limit.
Common situations: Notification rules that fire on every log line; many containers emitting alerts simultaneously; retry loops without backoff hammering the endpoint; shared/demo API keys near quota.
Related errors
- cloud dispatcher rate limited, retry after
- registry rate limited the request
- (await res.json().catch(() =>
- (await res.json().catch(() =>
- Failed to save destination
AI-assisted analysis of amir20/dozzle@d9463cbe21 (2026-09-07).
Data as JSON: /api/errors/b669a076325263be.
Report an issue: GitHub.
Appendix: source
Thrown at internal/notification/dispatcher/cloud.go:110
resp, err := c.client.Do(req)
if err != nil {
return fmt.Errorf("failed to send to cloud: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusTooManyRequests {
retryAfter := defaultRetryAfter
if ra := resp.Header.Get("Retry-After"); ra != "" {
if seconds, err := strconv.Atoi(ra); err == nil {
retryAfter = time.Duration(seconds) * time.Second
}
}
c.blockedUntil.Store(time.Now().Add(retryAfter).UnixNano())
log.Warn().
Str("cloud", c.Name).
Dur("retry_after", retryAfter).
Msg("rate limited by cloud, circuit breaker tripped")
return fmt.Errorf("cloud rate limited, backing off for %s", retryAfter)
}
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
limitedReader := io.LimitReader(resp.Body, 1024*1024)
responseBody, _ := io.ReadAll(limitedReader)
c.blockedUntil.Store(time.Now().Add(unauthorizedRetryAfter).UnixNano())
log.Warn().
Str("cloud", c.Name).
Int("status_code", resp.StatusCode).
Dur("retry_after", unauthorizedRetryAfter).
Msg("cloud rejected API key, circuit breaker tripped")
return fmt.Errorf("cloud returned status code %d: %s", resp.StatusCode, string(responseBody))
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
limitedReader := io.LimitReader(resp.Body, 1024*1024)
responseBody, _ := io.ReadAll(limitedReader)
log.Debug().View on GitHub (pinned to d9463cbe21)