Skip to main content
Webhook inboxes and email testing share one set of quotas per organization. Every error code the testing APIs return is in the error reference, with its fix.

Testing plans

Quotas apply per organization. Captured webhook events and received emails share the monthly event count. Testing plans are separate from monitoring plans.
  • An organization with no testing subscription is on Free. Your monitoring plan does not change these numbers.
  • Inboxes and email domains are counted across the whole organization, not per workspace. On Free, an email domain in one workspace blocks a second workspace from getting one. Disabled domains count.
  • Monthly events count every captured webhook request and every received or injected email. At the cap, webhook senders and inject get 403 and nothing is stored until the next month. Real mail sent over the cap can be accepted by the mail server and then dropped.
  • The monthly counter updates asynchronously. A burst that arrives at the cap can go slightly over it before refusals start.
  • Retention ceiling is the highest retentionDays an inbox accepts, and the default. DevHelm does not currently delete captured data on a schedule. See Security and data.
The limits on Rate limits belong to the monitoring plans, a separate ladder.

Rate and size limits

Also per inbox: maxEvents (1 to 100000, default 10000) caps stored events. When the inbox is full, the oldest event is dropped as a new one arrives.

How the limits apply

  • Ingest checks the per-IP window first, then resolves the token, then checks the per-inbox and per-workspace windows and the monthly cap. Requests to unknown tokens count against the IP window.
  • The wait limit counts calls, not time spent waiting. One 60-second wait costs the same as a 1-second one. Webhook and email waits share the 40 per minute across every workspace in the organization.
  • Size limits refuse the whole request. Nothing is stored or clipped to fit.
  • Email inject has no testing-specific rate limit. It counts against your organization’s general API rate limit and shares the size limits and the monthly cap.

Rate-limit responses

Every 429 carries Retry-After in seconds and the standard error body:
Neither SDK retries on its own. Wait Retry-After seconds, then call again. Retrying a wait with the same receivedAfter is safe: wait does not consume what it returns.

Error reference

Errors share one body shape. Branch on code; message is for people.
Field validation errors (for example a mock reply status outside 200–599) return 400 with a field-specific code and an errors[] entry naming the field. The 403 quota codes carry the same errors[] entry.

408 that should have matched

A wait returns the oldest item with receivedAt at or after receivedAfter. The CLI and both SDKs default receivedAfter to the client clock when the call starts, so an item that arrived first is missed. Capture the time before you trigger the send and pass it:
Wait on a disabled inbox or domain is not an error. It times out with 408.

Errors in the SDKs and CLI

Every class extends DevhelmApiError and carries status and code. A quota 403 is a DevhelmAuthError, so check code to tell it apart from a bad key. There is no timeout class: a 408 is a DevhelmApiError with code WAIT_TIMEOUT. Catch DevhelmRateLimitError before DevhelmApiError. The CLI prints the error class, message, and code, and exits non-zero (11 for a wait timeout):
TypeScript errors and Python errors show the catch order in code.

Next steps

Testing in CI

Fit parallel jobs into the inbox cap and the wait budget.

Security and data

Who can read captured data and how to clean it up.

Wait for a webhook

Wait semantics, receivedAfter, and negative assertions.

Wait for an email

One address per test and repeat sends.