Troubleshooting
IllegalArgumentException: API Key is required. / Error: API Key is required.
Thrown from Builder.build() when apiKey is null, empty, or blank. Confirm the environment variable or config value you're passing to .apiKey(...) is actually set at the point build() is called.
IllegalArgumentException: Base URL must use HTTPS for production environments.
You passed a baseUrl starting with http:// for a host other than localhost. Use https://, or omit baseUrl entirely to use the production default.
IllegalStateException: At least one dispatch channel must be specified. / Error: At least one dispatch channel must be specified.
Thrown from NotificationRequest.Builder.build() when no .addChannel(...) call was made. Add at least one channel before calling .build().
IllegalStateException: Template name is required. (or subject / content)
Thrown from TemplateRequest.Builder.build(). All three of name, subject, and content are required and can't be blank.
Requests fail with a 401/403 (NotificationHubAuthException / NotificationHubAuthError)
- Double-check
apiKeyandapiSecretmatch a currently active credential pair for the project. - If the secret was recently rotated, the old secret is invalid immediately — make sure every running instance of your app picked up the new one.
- Clock drift matters: the signature includes the current timestamp — make sure the host's system clock is accurate.
Requests fail with a 429 (NotificationHubRateLimitException / NotificationHubRateLimitError)
You're sending faster than the project's allowed rate. Back off and retry — see Error Handling.
Requests fail with a 4xx other than 401/403/429 (NotificationHubValidationException / NotificationHubValidationError)
Inspect getResponseBody() / error.responseBody — this is almost always a malformed or incomplete request body (e.g. a missing recipient field for the channel you specified).
Requests fail with a 5xx (NotificationHubServerException / NotificationHubServerError)
Transient server-side issue. Safe to retry with backoff. If it persists across multiple attempts, check your provider configuration with testConnection.
Notification sent successfully but never delivered
Check the dead-letter queue — a notification can be accepted by the API and still fail at the provider layer afterward. getDlqEntry(dlqId) shows the specific failure reason.
Template renders with unresolved placeholders
Confirm every variable your template's content/subject references is passed via .addVariable(key, value) (send-time) or in the variables map (preview/previewRaw). Missing variables aren't automatically inferred from the template content.
trackOpen / tracking pixel returns an error
trackOpen is unsigned and hits {baseUrl}/track/open/{notificationId} directly — confirm the notificationId is correct and that the notification was actually sent through an email-capable channel.