[PR #2519] [MERGED] feat: per-recipient envelope expiration #2361

Closed
opened 2026-02-26 20:33:33 +03:00 by kerem · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/documenso/documenso/pull/2519
Author: @Mythie
Created: 2/19/2026
Status: Merged
Merged: 2/20/2026
Merged by: @Mythie

Base: mainHead: feat/recipient-expiration


📝 Commits (9)

  • bdbb0f8 feat: implement per-recipient envelope expiration
  • 9884a38 test: add E2E tests for envelope expiration and fix settings cascade
  • e460518 fix: remove console.log
  • ef5186c fix: address feedback
  • 12db356 Merge branch 'main' into feat/recipient-expiration
  • 2e94085 fix: audit log message format
  • e67b091 chore: update ci
  • 0d3144f chore: update ci
  • 2d9d8df chore: update ci

📊 Changes

70 files changed (+2705 additions, -93 deletions)

View changed files

.agents/plans/calm-violet-tide-envelope-expiration.md (+519 -0)
📝 apps/remix/app/components/embed/authoring/configure-fields-view.tsx (+3 -1)
apps/remix/app/components/embed/embed-recipient-expired.tsx (+46 -0)
📝 apps/remix/app/components/forms/document-preferences-form.tsx (+38 -0)
📝 apps/remix/app/components/general/document/document-page-view-recipients.tsx (+43 -11)
📝 apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx (+41 -1)
📝 apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.document.tsx (+2 -0)
📝 apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.document.tsx (+2 -0)
📝 apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx (+17 -7)
apps/remix/app/routes/_recipient+/sign.$token+/expired.tsx (+114 -0)
📝 apps/remix/app/routes/embed+/_v0+/_layout.tsx (+5 -0)
📝 apps/remix/app/routes/embed+/_v0+/sign.$token.tsx (+24 -1)
📝 apps/remix/server/router.ts (+5 -0)
📝 package-lock.json (+13 -0)
📝 package.json (+1 -0)
📝 packages/api/v1/schema.ts (+4 -3)
📝 packages/app-tests/e2e/api/v2/envelopes-api.spec.ts (+3 -0)
packages/app-tests/e2e/envelopes/envelope-expiration-send.spec.ts (+291 -0)
packages/app-tests/e2e/envelopes/envelope-expiration-settings.spec.ts (+150 -0)
packages/app-tests/e2e/envelopes/envelope-expiration-signing.spec.ts (+131 -0)

...and 50 more files

📄 Description

Summary

When a document is sent for signing, each recipient now gets an expiresAt deadline. Once that passes, they can no longer sign — the signing page redirects to an expiry notice, and field/completion guards reject server-side. The document itself stays PENDING so other recipients aren't affected. The owner gets an email notification and can resend (which extends the deadline) or cancel.

Why

Documents sitting unsigned indefinitely is a real problem. Org admins need a way to enforce signing deadlines without manually tracking and cancelling stale documents. This also lays groundwork for future recipient-facing expiry warnings.

How it works

Settings cascade — Expiration period is configurable at three levels: Organisation → Team → Document. Each level can set a custom duration, disable expiration entirely, or inherit from the parent. Resolved at send time.

Signing guards — Four server-side guards (sign field, complete, remove field, reject) check recipient.expiresAt and throw RECIPIENT_EXPIRED. The signing page loader redirects expired recipients to /sign/{token}/expired.

Background jobs — A cron sweep runs every 15 minutes, finds newly-expired recipients, then fans out to per-recipient processing (audit log + webhook) and owner email notification. Honestly, the cron infrastructure additions (local poller, Inngest cron support) should have been a separate PR — they're general-purpose and not specific to expiration. But they're here now and cleanly isolated in the job client files.

Embeds — V1/V2 embed signing checks expiration and shows an EmbedRecipientExpired component.

What changed

  • Schema: expiresAt, expirationNotifiedAt on Recipient; envelopeExpirationPeriod (JSON) on DocumentMeta, OrgGlobalSettings, TeamGlobalSettings; RECIPIENT_EXPIRED webhook event
  • Removed EXPIRED from DocumentStatus enum (expiration is per-recipient now, not per-document)
  • UI: ExpirationPeriodPicker in org/team settings and envelope editor; expiry badges on recipient list; ownerRecipientExpired email checkbox
  • Email: RecipientExpiredTemplate notifying the document owner
  • Audit log: DOCUMENT_RECIPIENT_EXPIRED type
  • Tests: 12 E2E tests covering signing redirect, settings cascade, API send/distribute, and resend

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/documenso/documenso/pull/2519 **Author:** [@Mythie](https://github.com/Mythie) **Created:** 2/19/2026 **Status:** ✅ Merged **Merged:** 2/20/2026 **Merged by:** [@Mythie](https://github.com/Mythie) **Base:** `main` ← **Head:** `feat/recipient-expiration` --- ### 📝 Commits (9) - [`bdbb0f8`](https://github.com/documenso/documenso/commit/bdbb0f82bfeea112377ffdb4a5e8bd3ef732c738) feat: implement per-recipient envelope expiration - [`9884a38`](https://github.com/documenso/documenso/commit/9884a382c207e02dea7ae08dff50dcfd0c102f78) test: add E2E tests for envelope expiration and fix settings cascade - [`e460518`](https://github.com/documenso/documenso/commit/e46051847890632409642429a4d774ac82d8d447) fix: remove console.log - [`ef5186c`](https://github.com/documenso/documenso/commit/ef5186c6d50cb281501f142c16abcc77bf3f8302) fix: address feedback - [`12db356`](https://github.com/documenso/documenso/commit/12db356ac67a38e41a02965c20fde8dc591512ce) Merge branch 'main' into feat/recipient-expiration - [`2e94085`](https://github.com/documenso/documenso/commit/2e94085fdaf3146744f8017920733f288f8f9ff3) fix: audit log message format - [`e67b091`](https://github.com/documenso/documenso/commit/e67b091d0bd37aba38a0d0d62a0981018ba64aab) chore: update ci - [`0d3144f`](https://github.com/documenso/documenso/commit/0d3144fd940f3299dcf1a13c8809b31369a32ad1) chore: update ci - [`2d9d8df`](https://github.com/documenso/documenso/commit/2d9d8df7cb11d4fc0972bdfbacb9baf4d42314db) chore: update ci ### 📊 Changes **70 files changed** (+2705 additions, -93 deletions) <details> <summary>View changed files</summary> ➕ `.agents/plans/calm-violet-tide-envelope-expiration.md` (+519 -0) 📝 `apps/remix/app/components/embed/authoring/configure-fields-view.tsx` (+3 -1) ➕ `apps/remix/app/components/embed/embed-recipient-expired.tsx` (+46 -0) 📝 `apps/remix/app/components/forms/document-preferences-form.tsx` (+38 -0) 📝 `apps/remix/app/components/general/document/document-page-view-recipients.tsx` (+43 -11) 📝 `apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx` (+41 -1) 📝 `apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.document.tsx` (+2 -0) 📝 `apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.document.tsx` (+2 -0) 📝 `apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx` (+17 -7) ➕ `apps/remix/app/routes/_recipient+/sign.$token+/expired.tsx` (+114 -0) 📝 `apps/remix/app/routes/embed+/_v0+/_layout.tsx` (+5 -0) 📝 `apps/remix/app/routes/embed+/_v0+/sign.$token.tsx` (+24 -1) 📝 `apps/remix/server/router.ts` (+5 -0) 📝 `package-lock.json` (+13 -0) 📝 `package.json` (+1 -0) 📝 `packages/api/v1/schema.ts` (+4 -3) 📝 `packages/app-tests/e2e/api/v2/envelopes-api.spec.ts` (+3 -0) ➕ `packages/app-tests/e2e/envelopes/envelope-expiration-send.spec.ts` (+291 -0) ➕ `packages/app-tests/e2e/envelopes/envelope-expiration-settings.spec.ts` (+150 -0) ➕ `packages/app-tests/e2e/envelopes/envelope-expiration-signing.spec.ts` (+131 -0) _...and 50 more files_ </details> ### 📄 Description ## Summary When a document is sent for signing, each recipient now gets an `expiresAt` deadline. Once that passes, they can no longer sign — the signing page redirects to an expiry notice, and field/completion guards reject server-side. The document itself stays PENDING so other recipients aren't affected. The owner gets an email notification and can resend (which extends the deadline) or cancel. ## Why Documents sitting unsigned indefinitely is a real problem. Org admins need a way to enforce signing deadlines without manually tracking and cancelling stale documents. This also lays groundwork for future recipient-facing expiry warnings. ## How it works **Settings cascade** — Expiration period is configurable at three levels: Organisation → Team → Document. Each level can set a custom duration, disable expiration entirely, or inherit from the parent. Resolved at send time. **Signing guards** — Four server-side guards (sign field, complete, remove field, reject) check `recipient.expiresAt` and throw `RECIPIENT_EXPIRED`. The signing page loader redirects expired recipients to `/sign/{token}/expired`. **Background jobs** — A cron sweep runs every 15 minutes, finds newly-expired recipients, then fans out to per-recipient processing (audit log + webhook) and owner email notification. Honestly, the cron infrastructure additions (local poller, Inngest cron support) should have been a separate PR — they're general-purpose and not specific to expiration. But they're here now and cleanly isolated in the job client files. **Embeds** — V1/V2 embed signing checks expiration and shows an `EmbedRecipientExpired` component. ## What changed - **Schema**: `expiresAt`, `expirationNotifiedAt` on Recipient; `envelopeExpirationPeriod` (JSON) on DocumentMeta, OrgGlobalSettings, TeamGlobalSettings; `RECIPIENT_EXPIRED` webhook event - **Removed** `EXPIRED` from `DocumentStatus` enum (expiration is per-recipient now, not per-document) - **UI**: `ExpirationPeriodPicker` in org/team settings and envelope editor; expiry badges on recipient list; `ownerRecipientExpired` email checkbox - **Email**: `RecipientExpiredTemplate` notifying the document owner - **Audit log**: `DOCUMENT_RECIPIENT_EXPIRED` type - **Tests**: 12 E2E tests covering signing redirect, settings cascade, API send/distribute, and resend --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
kerem 2026-02-26 20:33:33 +03:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/documenso#2361
No description provided.