Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,16 @@ See `docs/PRD.md` for full requirements and `docs/ARCHITECTURE.md` for system de
- [x] All branches rebased to latest `main` before opening PRs
- [x] PRs opened in dependency order: Person 1 → Person 2 + Person 3 (parallel) → Person 4 + Person 5 (parallel)
- [x] Security reviewer (B. Mackenzie) reviewed all PRs touching `packages/prisma/`, `packages/lib/jobs/`, `packages/lib/types/`, `packages/email/`
- [x] Post-merge security review run against integrated `main` — three blocking findings fixed (see below)
- [ ] Full end-to-end test: send document with reminders enabled → wait one interval → confirm recipient email fires → confirm sender digest fires → confirm both appear in activity feed
- [x] `npm run build` passes (verified locally; CI Build App passes on all PRs)
- [x] Local API test suite: 258 passed, 5 flaky (retried and passed), 10 skipped — no failures
- [ ] PR description follows upstream Documenso template and references the issue
- [ ] No unreviewed AI-generated code in any PR (CONTRIBUTING.md requirement)

**Note:** E2E tests were bypassed on all PRs due to a persistent Warp runner queue issue (jobs stuck in `queued` state indefinitely). All code was reviewed manually. A full E2E pass should be completed before opening the upstream contribution PR.
**Post-merge fixes applied (B. Mackenzie):**
- `send-recipient-reminder-email.handler.ts`: added `reminderEnabled` gate — handler now returns early if owner disabled reminders between sweep and execution
- `send-owner-reminder-digest-email.handler.ts`: filters all envelopes by `ownerReminderDigest` (not just `firstEnvelope`); added `status: PENDING` + `teamId` + `userId` scoping to `findMany`; fixed subject pluralization to use Lingui `plural()` macro
- `packages/lib/server-only/ai/google.ts`: applied `as any` cast to suppress pre-existing upstream TS2353 error (`apiKey` removed from `GoogleVertexProviderSettings` in current SDK version)

**Note:** E2E tests were bypassed on all PRs due to a persistent Warp runner queue issue (jobs stuck in `queued` state indefinitely). All code was reviewed manually. Local API test suite ran clean. UI E2E tests could not complete locally due to auth cookie setup issue in dev environment (unrelated to reminder code). A full E2E pass should be completed before opening the upstream contribution PR.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createElement } from 'react';

import { msg } from '@lingui/core/macro';
import { SigningStatus } from '@prisma/client';
import { msg, plural } from '@lingui/core/macro';
import { DocumentStatus, SigningStatus } from '@prisma/client';

import { mailer } from '@documenso/email/mailer';
import { SenderReminderDigestEmailTemplate } from '@documenso/email/templates/sender-reminder-digest';
Expand All @@ -25,10 +25,10 @@ export const run = async ({
payload: TSendOwnerReminderDigestEmailJobDefinition;
io: JobRunIO;
}) => {
const { teamId, envelopeIds } = payload;
const { teamId, userId, envelopeIds } = payload;

const envelopes = await prisma.envelope.findMany({
where: { id: { in: envelopeIds } },
where: { id: { in: envelopeIds }, teamId, userId, status: DocumentStatus.PENDING },
include: {
user: {
select: { id: true, email: true, name: true },
Expand All @@ -47,16 +47,16 @@ export const run = async ({
return;
}

const firstEnvelope = envelopes[0];
const eligibleEnvelopes = envelopes.filter(
(e) => extractDerivedDocumentEmailSettings(e.documentMeta).ownerReminderDigest,
);

const isDigestEnabled = extractDerivedDocumentEmailSettings(
firstEnvelope.documentMeta,
).ownerReminderDigest;

if (!isDigestEnabled) {
if (eligibleEnvelopes.length === 0) {
return;
}

const firstEnvelope = eligibleEnvelopes[0];

const { branding, emailLanguage, senderEmail } = await getEmailContext({
emailType: 'INTERNAL',
source: { type: 'team', teamId },
Expand All @@ -65,7 +65,7 @@ export const run = async ({

const i18n = await getI18nInstance(emailLanguage);

const pendingDocuments = envelopes.map((envelope) => {
const pendingDocuments = eligibleEnvelopes.map((envelope) => {
const pendingRecipients = envelope.recipients.filter(
(r) => r.signingStatus === SigningStatus.NOT_SIGNED,
);
Expand All @@ -89,7 +89,7 @@ export const run = async ({

const owner = firstEnvelope.user;
const teamName = firstEnvelope.team.name;
const count = envelopes.length;
const count = eligibleEnvelopes.length;

const template = createElement(SenderReminderDigestEmailTemplate, {
ownerName: owner.name || owner.email,
Expand All @@ -111,22 +111,24 @@ export const run = async ({
},
from: senderEmail,
subject: i18n._(
msg`Reminder: ${count} document${count === 1 ? '' : 's'} awaiting signatures in "${teamName}"`,
msg`Reminder: ${plural(count, { one: '# document', other: '# documents' })} awaiting signatures in "${teamName}"`,
),
html,
text,
});
});

const eligibleEnvelopeIds = eligibleEnvelopes.map((e) => e.id);

await io.runTask('create-reminder-logs', async () => {
await prisma.documentReminderLog.createMany({
data: envelopeIds.map((eid) => ({ envelopeId: eid })),
data: eligibleEnvelopeIds.map((eid) => ({ envelopeId: eid })),
});
});

await io.runTask('create-audit-logs', async () => {
await prisma.documentAuditLog.createMany({
data: envelopeIds.map((eid) =>
data: eligibleEnvelopeIds.map((eid) =>
createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.REMINDER_SENT,
envelopeId: eid,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export const run = async ({
return;
}

if (!envelope.documentMeta?.reminderEnabled) {
return;
}

const recipient = await prisma.recipient.findFirst({
where: { id: recipientId, envelopeId },
});
Expand Down
3 changes: 2 additions & 1 deletion packages/lib/server-only/ai/google.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export const vertex = createVertex({
project: env('GOOGLE_VERTEX_PROJECT_ID'),
location: env('GOOGLE_VERTEX_LOCATION') || 'global',
apiKey: env('GOOGLE_VERTEX_API_KEY'),
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any);
58 changes: 57 additions & 1 deletion packages/lib/translations/de/web.po
Original file line number Diff line number Diff line change
Expand Up @@ -1831,6 +1831,10 @@ msgstr "Ein Fehler ist aufgetreten, während dein Dokument hochgeladen wurde."
msgid "An error occurred. Please try again later."
msgstr "Ein Fehler ist aufgetreten. Bitte versuchen Sie es später erneut."

#: packages/lib/types/document-meta.ts
msgid "An expiration period is required to enable reminders"
msgstr ""

#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
msgid "An organisation wants to create an account for you. Please review the details below."
msgstr "Eine Organisation möchte ein Konto für Sie erstellen. Bitte überprüfen Sie die Details unten."
Expand Down Expand Up @@ -2156,6 +2160,14 @@ msgstr "Authentifizierung erforderlich"
msgid "Authenticator app"
msgstr "Authenticator-App"

#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
msgid "Automatic Reminders"
msgstr ""

#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
msgid "Automatically send reminder emails to unsigned recipients on a recurring interval until the document expires."
msgstr ""

#: apps/remix/app/components/general/document-signing/document-signing-auto-sign.tsx
msgid "Automatically sign fields"
msgstr "Felder automatisch unterschreiben"
Expand Down Expand Up @@ -8269,8 +8281,30 @@ msgstr "Neu laden"
msgid "Remembered your password? <0>Sign In</0>"
msgstr "Haben Sie Ihr Passwort vergessen? <0>Einloggen</0>"

#. placeholder {0}: data.recipientName || data.recipientEmail
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Reminder digest email"
msgstr ""

#: packages/lib/utils/document-audit-logs.ts
msgctxt "Audit log format"
msgid "Reminder digest sent to owner"
msgstr ""

#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
msgid "Reminder Interval (days)"
msgstr ""

#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
#: packages/lib/types/document-meta.ts
msgid "Reminder interval is required when reminders are enabled"
msgstr ""

#: packages/lib/utils/document-audit-logs.ts
msgctxt "Audit log format"
msgid "Reminder sent"
msgstr ""

#. placeholder {0}: data.recipientName || data.recipientEmail
#: packages/lib/utils/document-audit-logs.ts
#: packages/lib/utils/document-audit-logs.ts
msgid "Reminder sent to {0}"
Expand All @@ -8286,6 +8320,11 @@ msgstr "Erinnerung: {0}"
msgid "Reminder: {0} invited you to {recipientActionVerb} a document"
msgstr "Erinnerung: {0} hat dich eingeladen, ein Dokument {recipientActionVerb}"

#. placeholder {0}: count === 1 ? '' : 's'
#: packages/lib/jobs/definitions/emails/send-owner-reminder-digest-email.handler.ts
msgid "Reminder: {count} document{0} awaiting signatures in \"{teamName}\""
msgstr ""

#: packages/email/templates/sender-reminder-digest.tsx
msgid "Reminder: {count} documents in {teamName} are awaiting signatures"
msgstr ""
Expand All @@ -8302,6 +8341,11 @@ msgstr "Erinnerung: Bitte {recipientActionVerb} dieses Dokument"
msgid "Reminder: Please {recipientActionVerb} your document"
msgstr "Erinnerung: Bitte {recipientActionVerb} dein Dokument"

#. placeholder {0}: envelope.title
#: packages/lib/jobs/definitions/emails/send-recipient-reminder-email.handler.ts
msgid "Reminder: please sign \"{0}\""
msgstr ""

#. placeholder {0}: daysRemaining === 1 ? '' : 's'
#: packages/email/templates/document-reminder.tsx
msgid "Reminder: you have {daysRemaining} day{0} left to sign \"{documentName}\""
Expand Down Expand Up @@ -9019,6 +9063,10 @@ msgstr "Dokumente sofort an Empfänger senden"
msgid "Send Envelope"
msgstr "Umschlag senden"

#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Send me a reminder digest when recipients haven't signed"
msgstr ""

#: apps/remix/app/components/forms/document-preferences-form.tsx
msgid "Send on Behalf of Team"
msgstr "Im Namen des Teams senden"
Expand Down Expand Up @@ -9073,6 +9121,10 @@ msgstr "Sitzungen wurden widerrufen"
msgid "Set a password"
msgstr "Ein Passwort festlegen"

#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
msgid "Set an expiration date to enable automatic reminders."
msgstr ""

#: apps/remix/app/components/embed/authoring/configure-document-view.tsx
msgid "Set up your document properties and recipient information"
msgstr "Richten Sie Ihre Dokumenteigenschaften und Empfängerinformationen ein"
Expand Down Expand Up @@ -12425,6 +12477,10 @@ msgstr "Was Sie mit Teams machen können:"
msgid "When enabled, signers can choose who should sign next in the sequence instead of following the predefined order."
msgstr "Wenn aktiviert, können Unterzeichner auswählen, wer als nächster in der Reihenfolge unterzeichnen soll, anstatt der vorgegebenen Reihenfolge zu folgen."

#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "When reminders are sent to unsigned recipients, this aggregates all pending documents for your team into a single digest email sent to you."
msgstr ""

#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
msgid "When you click continue, you will be prompted to add the first available authenticator on your system."
msgstr "Wenn Sie auf Fortfahren klicken, werden Sie aufgefordert, den ersten verfügbaren Authenticator auf Ihrem System hinzuzufügen."
Expand Down
58 changes: 57 additions & 1 deletion packages/lib/translations/en/web.po
Original file line number Diff line number Diff line change
Expand Up @@ -1826,6 +1826,10 @@ msgstr "An error occurred while uploading your document."
msgid "An error occurred. Please try again later."
msgstr "An error occurred. Please try again later."

#: packages/lib/types/document-meta.ts
msgid "An expiration period is required to enable reminders"
msgstr "An expiration period is required to enable reminders"

#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
msgid "An organisation wants to create an account for you. Please review the details below."
msgstr "An organisation wants to create an account for you. Please review the details below."
Expand Down Expand Up @@ -2151,6 +2155,14 @@ msgstr "Authentication required"
msgid "Authenticator app"
msgstr "Authenticator app"

#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
msgid "Automatic Reminders"
msgstr "Automatic Reminders"

#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
msgid "Automatically send reminder emails to unsigned recipients on a recurring interval until the document expires."
msgstr "Automatically send reminder emails to unsigned recipients on a recurring interval until the document expires."

#: apps/remix/app/components/general/document-signing/document-signing-auto-sign.tsx
msgid "Automatically sign fields"
msgstr "Automatically sign fields"
Expand Down Expand Up @@ -8264,8 +8276,30 @@ msgstr "Reload"
msgid "Remembered your password? <0>Sign In</0>"
msgstr "Remembered your password? <0>Sign In</0>"

#. placeholder {0}: data.recipientName || data.recipientEmail
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Reminder digest email"
msgstr "Reminder digest email"

#: packages/lib/utils/document-audit-logs.ts
msgctxt "Audit log format"
msgid "Reminder digest sent to owner"
msgstr "Reminder digest sent to owner"

#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
msgid "Reminder Interval (days)"
msgstr "Reminder Interval (days)"

#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
#: packages/lib/types/document-meta.ts
msgid "Reminder interval is required when reminders are enabled"
msgstr "Reminder interval is required when reminders are enabled"

#: packages/lib/utils/document-audit-logs.ts
msgctxt "Audit log format"
msgid "Reminder sent"
msgstr "Reminder sent"

#. placeholder {0}: data.recipientName || data.recipientEmail
#: packages/lib/utils/document-audit-logs.ts
#: packages/lib/utils/document-audit-logs.ts
msgid "Reminder sent to {0}"
Expand All @@ -8281,6 +8315,11 @@ msgstr "Reminder: {0}"
msgid "Reminder: {0} invited you to {recipientActionVerb} a document"
msgstr "Reminder: {0} invited you to {recipientActionVerb} a document"

#. placeholder {0}: count === 1 ? '' : 's'
#: packages/lib/jobs/definitions/emails/send-owner-reminder-digest-email.handler.ts
msgid "Reminder: {count} document{0} awaiting signatures in \"{teamName}\""
msgstr "Reminder: {count} document{0} awaiting signatures in \"{teamName}\""

#: packages/email/templates/sender-reminder-digest.tsx
msgid "Reminder: {count} documents in {teamName} are awaiting signatures"
msgstr "Reminder: {count} documents in {teamName} are awaiting signatures"
Expand All @@ -8297,6 +8336,11 @@ msgstr "Reminder: Please {recipientActionVerb} this document"
msgid "Reminder: Please {recipientActionVerb} your document"
msgstr "Reminder: Please {recipientActionVerb} your document"

#. placeholder {0}: envelope.title
#: packages/lib/jobs/definitions/emails/send-recipient-reminder-email.handler.ts
msgid "Reminder: please sign \"{0}\""
msgstr "Reminder: please sign \"{0}\""

#. placeholder {0}: daysRemaining === 1 ? '' : 's'
#: packages/email/templates/document-reminder.tsx
msgid "Reminder: you have {daysRemaining} day{0} left to sign \"{documentName}\""
Expand Down Expand Up @@ -9014,6 +9058,10 @@ msgstr "Send documents to recipients immediately"
msgid "Send Envelope"
msgstr "Send Envelope"

#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Send me a reminder digest when recipients haven't signed"
msgstr "Send me a reminder digest when recipients haven't signed"

#: apps/remix/app/components/forms/document-preferences-form.tsx
msgid "Send on Behalf of Team"
msgstr "Send on Behalf of Team"
Expand Down Expand Up @@ -9068,6 +9116,10 @@ msgstr "Sessions have been revoked"
msgid "Set a password"
msgstr "Set a password"

#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
msgid "Set an expiration date to enable automatic reminders."
msgstr "Set an expiration date to enable automatic reminders."

#: apps/remix/app/components/embed/authoring/configure-document-view.tsx
msgid "Set up your document properties and recipient information"
msgstr "Set up your document properties and recipient information"
Expand Down Expand Up @@ -12420,6 +12472,10 @@ msgstr "What you can do with teams:"
msgid "When enabled, signers can choose who should sign next in the sequence instead of following the predefined order."
msgstr "When enabled, signers can choose who should sign next in the sequence instead of following the predefined order."

#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "When reminders are sent to unsigned recipients, this aggregates all pending documents for your team into a single digest email sent to you."
msgstr "When reminders are sent to unsigned recipients, this aggregates all pending documents for your team into a single digest email sent to you."

#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
msgid "When you click continue, you will be prompted to add the first available authenticator on your system."
msgstr "When you click continue, you will be prompted to add the first available authenticator on your system."
Expand Down
Loading
Loading