Retries & idempotency
When Slack rejects a post, RouteForms records the failure and gives you a Retry button. This page documents the retry semantics, the idempotency model that prevents double-posts, and the alerts that surface failed streaks.
Manual retry from the dashboard
Every FAILED row in the delivery log has a Retry action. Clicking it re-attempts delivery using the same payload that originally failed, against the destination webhook configured for that row.
The retry uses the latest webhook URL on file, so this is the right flow if:
- You revoked and replaced the webhook; the new URL is now configured.
- The Slack workspace had a transient outage; it's back.
- The channel was archived and you've switched the destination to a different channel.
A successful retry updates the row to DELIVERED and increments the attempts count. A failed retry stays as FAILED; the row still shows the latest Slack response so you can decode the new error.
Idempotency on the Google Forms response ID
Apps Script can retry on its own. UrlFetchApp surfaces transient errors and Google's scheduler sometimes re-invokes onFormSubmit after a partial failure. Without dedupe, this means two Slack posts for one form submission.
RouteForms prevents this by enforcing a unique constraint on the Google Forms response ID at the database layer. The script sends the response ID along with the payload; we use it as the dedupe key on insert. A second insert with the same response ID fails the unique-constraint check and is silently ignored, no duplicate Slack post.
How manual retry interacts with idempotency
When you click Retry on a FAILED row, RouteForms re-POSTs to Slack using the same response ID that originally failed. If Slack accepts this time, the row updates to DELIVERED, only one Slack post exists (the retried one). If Slack still rejects, the row stays FAILED with the new Slack response.
If a later Apps Script re-invocation tries to deliver the same response ID after you've already retried successfully, the dedupe layer rejects the duplicate insert. No second Slack post happens.
Apps Script's own retry behaviour
Apps Script doesn't have a built-in retry-on-failure mechanism for onFormSubmittriggers. If the handler throws, the execution is logged as failed and that's it. Google won't silently retry.
However, two subtler cases produce duplicate-looking invocations:
- UrlFetchApp transient errors.If the POST to RouteForms's endpoint times out, the script's
UrlFetchApp.fetchthrows. The next invocation (next form submission) is a separate event with its own response ID , not a retry. The transient-failed submission is lost unless RouteForms received it before the timeout. - Duplicate triggers.If two form-submit triggers exist on the form, each submission fires both handlers. RouteForms's dedupe catches this, the second handler's POST sees the same response ID and is rejected. But the extra Apps Script execution still consumes quota.
Our installer wipes duplicate onFormSubmit triggers when you run installRouteForms, so re-installing fixes the duplicate-trigger case.
Failed-streak email alerts (paid plans)
On Solo and Agency plans, RouteForms sends an email when a form's delivery streak goes red, three consecutive failed deliveries in a 10-minute window.
The email includes:
- The form name and dashboard link.
- The first failed delivery's timestamp.
- The Slack HTTP status and decoded error for that first failure.
- A direct link to the delivery log filtered to FAILED.
You get one email per streak, not one per failure, so a webhook revoked for two days produces one email at the start of the streak, not 200 emails. When deliveries start succeeding again, the streak ends; the next failed streak triggers a new email.
Alert recipients are managed in the form's Settings tab. By default, the email goes to the account owner; you can add additional recipients.
When not to retry
Some failures shouldn't be retried because the cause persists:
- no_service / no_team (HTTP 404). The webhook is dead. Retrying produces the same error. Generate a new webhook in Slack and update the destination first.
- invalid_payload (HTTP 400). The message body itself is malformed. Retrying without fixing the template produces the same error. Fix the template, then retry.
- action_prohibited (HTTP 403). A workspace admin policy is blocking the post. Talk to the admin first.
For rate-limited (HTTP 429) and transient 5xx errors, retry is the right move — the failure was timing-related and the underlying state is fine.