---
title: "Remind the signers of an envelope"
method: POST
path: "/envelopes/{envelopeId}/reminders"
tags: ["Envelopes"]
---

# Remind the signers of an envelope

`POST /envelopes/{envelopeId}/reminders`

## Request

- Base URL: `https://inkseal.net/api/v1`
- URL: `https://inkseal.net/api/v1/envelopes/{envelopeId}/reminders`
- Auth: HTTP bearer

Asks for reminders on an envelope that is out for signature, and reports what happened to every signer on it.

**There is no per-signer targeting and no body.** The endpoint takes no parameters at all: it runs the same reminder policy the scheduled sweep and the dashboard button run, and there is deliberately no way to name recipients or to bypass the cadence, because an API that could mail every signer on demand would be a spam engine with a key.

**Throttling and cooldown.** A signer is reminded only when the envelope is `pending`, has been sent, is not past its deadline, was sent at least one reminder interval ago (3 days), and has not been contacted within that same interval. Sequential envelopes hold everyone behind the first unsigned signer, and a sequential signer's clock starts at the hand-off (the last signature ahead of them), not at the envelope's send. The cadence slot is claimed with a compare-and-set before the message goes out, so an un-keyed retry inside the window finds the slot taken and reports `reminder_not_due` rather than mailing anyone twice. Two overlapping callers cannot both mail the same signer.

Every mailed link is freshly reissued through the canonical token strategy, which is why no raw token appears in the response: the link is bearer authority over one envelope and belongs only in the signer's mailbox. The caller gets a per-signer outcome keyed by public signer id instead.

**Preconditions.** Only `pending` is actionable. A draft, a finished envelope, or one past its deadline is refused whole with **409 `invalid_state`** and `details.status`. `expired` there is derived from the deadline rather than read from the column, so an envelope the expiry sweep has not caught yet is still refused as `expired`. Note that `void` and the inbound actions report comparable refusals as 400 `invalid_status`; reminders use 409 `invalid_state`.

A 200 is not a promise that anyone was mailed: read `summary` and the per-recipient `outcome`. Honors `Idempotency-Key`; metered in the `send` rate bucket.

## Headers

- `Idempotency-Key` string

## Response `200`

One outcome per signer on the envelope, including the ones nothing was sent to, plus counts.

- object
  - `data` ReminderDispatchResult, required: The result of asking for reminders on one envelope.
    - `id` string, required
    - `status` 'draft' | 'sending' | 'pending' | 'completing' | 'completed' | 'declined' | 'voided' | 'expired', required: Where the envelope is in its lifecycle. `sending` and `completing` are transient; `completed`, `declined`, `voided`, and `expired` are terminal.
    - `reminders` ReminderOutcome[], required
      - `id` string, required: Public signer id.
      - `email` string, email, required
      - `outcome` 'sent' | 'skipped' | 'failed', required: `sent` means a fresh signing link was mailed. `skipped` means the policy passed this signer over. `failed` means a reminder was owed but could not be delivered, and the cadence slot was released so a retry can take it.
      - `reason` 'null' | 'envelope_not_pending' | 'envelope_not_sent' | 'envelope_expired' | 'envelope_too_new' | 'signer_already_signed' | 'signer_declined' | 'signer_not_notified' | 'signer_awaiting_turn' | 'link_revoked' | 'reminder_not_due' | 'email_failed' | 'dispatch_error', nullable, required: Null on a send. On a skip: `envelope_not_pending`, `envelope_not_sent`, `envelope_expired`, `envelope_too_new` (sent less than one 3-day interval ago), `signer_already_signed`, `signer_declined`, `signer_not_notified` (a later signer on a sequential envelope), `signer_awaiting_turn`, `link_revoked`, or `reminder_not_due` (contacted inside the cadence window, which also covers losing the claim to a concurrent dispatch). On a failure: `link_revoked`, `email_failed`, or `dispatch_error`.
    - `summary` ReminderSummary, required: Counts across the dispatch. Read this before assuming anyone was mailed.
      - `sent` integer, required
      - `skipped` integer, required
      - `failed` integer, required

## Other responses

- `400`: The request is malformed or the resource is in the wrong state for a synchronous check. Codes: `validation_error`, `invalid_body`, `invalid_parameter`, `invalid_idempotency_key`, `invalid_event_id`, `invalid_file`, `invalid_url`, `invalid_status`, `key_mismatch`, `no_signers`, `not_completed`, `envelope_exists`, `document_not_ready`, `upload_failed`.
- `401`: The credential is missing, malformed, unknown, expired, revoked, or past its rotation grace window. Code: `unauthorized`. Every authentication failure is deliberately indistinguishable.
- `403`: The credential is valid but may not do this. Codes: `insufficient_scope` (the route's scopes are not all held; `details` names `requiredScopes` and `missingScopes`), `forbidden` (the capability check on the resolved resource failed), `scope_escalation` (a grant wider than the caller's own was requested).
- `404`: The resource does not exist, was deleted, or belongs to another workspace. Code: `not_found`. The three cases are answered identically on purpose, so a 404 never confirms that an identifier exists elsewhere.
- `409`: `invalid_state`: only an envelope out for signature can be reminded. `details.status` is the envelope's state, with `expired` derived from the deadline. The same status also covers an `Idempotency-Key` another request is still working.
- `422`: The request parsed but its content is not acceptable. Codes: `validation_error` (envelope configuration, with an `issues` array in `details`), `invalid_envelope`, `envelope_not_ready`, `document_page_limit`, `idempotency_key_reuse`.
- `429`: A limit was reached. Codes: `rate_limited` (the credential's per-bucket HTTP limit, or the per-workspace detection throttle), `workspace_limit_exceeded` (a workspace content or send limit; `details.limit` names it), `daily_budget_exceeded` (the workspace's daily detection budget). The four rate-limit headers are sent on this response and on no successful one, so a client cannot read its remaining budget without being refused. Retrying after `Retry-After` is always safe: a rate-limit refusal happens before the handler runs.
- `503`: A dependency the request needs is unreachable and nothing was changed. Codes: `queue_unavailable` (a background job could not be enqueued; retry the call), `billing_unavailable` (Stripe would not open a checkout session; nothing was charged, retry the call), `service_unavailable` (the rate-limit store is down, so the request was refused before running).

---

[API](https://skmtc.dev/inkseal/apis/inkseal-api.md) · [All operations](https://skmtc.dev/inkseal/apis/inkseal-api/llms.txt) · [OpenAPI document](https://skmtc.dev/inkseal/apis/inkseal-api/revisions/c440c981d578?raw)
