diff --git a/apps/remix/app/components/general/organisations/organisation-quota-banner.tsx b/apps/remix/app/components/general/organisations/organisation-quota-banner.tsx
index c25f23bb3..b2a5e1a93 100644
--- a/apps/remix/app/components/general/organisations/organisation-quota-banner.tsx
+++ b/apps/remix/app/components/general/organisations/organisation-quota-banner.tsx
@@ -3,6 +3,7 @@ import { SUPPORT_EMAIL } from '@documenso/lib/constants/app';
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION, SKIP_QUERY_BATCH_META } from '@documenso/lib/constants/trpc';
import { INTERNAL_CLAIM_ID } from '@documenso/lib/types/subscription';
import { trpc } from '@documenso/trpc/react';
+import { cn } from '@documenso/ui/lib/utils';
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
import { Button } from '@documenso/ui/primitives/button';
import {
@@ -38,12 +39,18 @@ export const OrganisationQuotaBanner = () => {
quotaFlags?.isDocumentQuotaExceeded || quotaFlags?.isEmailQuotaExceeded || quotaFlags?.isApiQuotaExceeded,
);
- // Every member of the organisation sees the banner when a quota is exhausted.
+ const isAnyQuotaNearing = Boolean(
+ quotaFlags?.isDocumentQuotaNearing || quotaFlags?.isEmailQuotaNearing || quotaFlags?.isApiQuotaNearing,
+ );
+
+ // Every member of the organisation sees the banner when a quota is exhausted or
+ // nearing its limit. When both states apply, "exceeded" wins for the banner copy
+ // and the dialog lists both exceeded and nearing items.
// Note: Skipping free plan banner for now because their quota can incorrectly show as exceeded.
if (
!organisation ||
!quotaFlags ||
- !isAnyQuotaExceeded ||
+ (!isAnyQuotaExceeded && !isAnyQuotaNearing) ||
organisation.organisationClaim.originalSubscriptionClaimId === INTERNAL_CLAIM_ID.FREE
) {
return null;
@@ -51,17 +58,29 @@ export const OrganisationQuotaBanner = () => {
return (
<>
-
+
- Your organisation has exceeded a fair use limit
+ {isAnyQuotaExceeded ? (
+ Your organisation has exceeded a fair use limit
+ ) : (
+ Your organisation is approaching a fair use limit
+ )}
- {_(previewText)}
-
-
-
-
-
-
-
-
- Organisation Review Required
-
-
- {kind === 'quota' ? (
-
- {match(counter)
- .with('document', () => (
-
- We've noticed document activity on your account that exceeds the fair use limits of your current
- plan. As a precaution, new document activity has been temporarily paused pending review.
-
- ))
- .with('email', () => (
-
- We've noticed email sending activity on your account that exceeds the fair use limits of your
- current plan. As a precaution, new email activity has been temporarily paused pending review.
-
- ))
- .with('api', () => (
-
- We've noticed API activity on your account that exceeds the fair use limits of your current
- plan. As a precaution, new API activity has been temporarily paused pending review.
-
- ))
- .exhaustive()}
-
- ) : (
-
- {match(counter)
- .with('document', () => (
-
- Your organisation is generating documents faster than normal, so some requests are being
- temporarily throttled.
-
- ))
- .with('email', () => (
-
- Your organisation is generating emails faster than normal, so some requests are being
- temporarily throttled.
-
- ))
- .with('api', () => (
-
- Your organisation is generating API requests faster than normal, so some requests are being
- temporarily throttled.
-
- ))
- .exhaustive()}
-
- )}
-
-
- Please contact support at {SUPPORT_EMAIL} and we will review your account.
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default OrganisationLimitExceededEmailTemplate;
diff --git a/packages/lib/jobs/client.ts b/packages/lib/jobs/client.ts
index 083c28424..ae23cb092 100644
--- a/packages/lib/jobs/client.ts
+++ b/packages/lib/jobs/client.ts
@@ -4,7 +4,7 @@ import { SEND_CONFIRMATION_EMAIL_JOB_DEFINITION } from './definitions/emails/sen
import { SEND_DOCUMENT_CANCELLED_EMAILS_JOB_DEFINITION } from './definitions/emails/send-document-cancelled-emails';
import { SEND_DOCUMENT_COMPLETED_EMAILS_JOB_DEFINITION } from './definitions/emails/send-document-completed-emails';
import { SEND_DOCUMENT_CREATED_FROM_DIRECT_TEMPLATE_EMAIL_JOB_DEFINITION } from './definitions/emails/send-document-created-from-direct-template-email';
-import { SEND_ORGANISATION_LIMIT_EXCEEDED_EMAIL_JOB_DEFINITION } from './definitions/emails/send-organisation-limit-exceeded-email';
+import { SEND_ORGANISATION_LIMIT_ALERT_EMAIL_JOB_DEFINITION } from './definitions/emails/send-organisation-limit-alert-email';
import { SEND_ORGANISATION_MEMBER_JOINED_EMAIL_JOB_DEFINITION } from './definitions/emails/send-organisation-member-joined-email';
import { SEND_ORGANISATION_MEMBER_LEFT_EMAIL_JOB_DEFINITION } from './definitions/emails/send-organisation-member-left-email';
import { SEND_OWNER_RECIPIENT_EXPIRED_EMAIL_JOB_DEFINITION } from './definitions/emails/send-owner-recipient-expired-email';
@@ -37,7 +37,7 @@ export const jobsClient = new JobClient([
SEND_CONFIRMATION_EMAIL_JOB_DEFINITION,
SEND_ORGANISATION_MEMBER_JOINED_EMAIL_JOB_DEFINITION,
SEND_ORGANISATION_MEMBER_LEFT_EMAIL_JOB_DEFINITION,
- SEND_ORGANISATION_LIMIT_EXCEEDED_EMAIL_JOB_DEFINITION,
+ SEND_ORGANISATION_LIMIT_ALERT_EMAIL_JOB_DEFINITION,
SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION,
SEAL_DOCUMENT_JOB_DEFINITION,
SEAL_DOCUMENT_SWEEP_JOB_DEFINITION,
diff --git a/packages/lib/jobs/definitions/emails/send-organisation-limit-exceeded-email.handler.ts b/packages/lib/jobs/definitions/emails/send-organisation-limit-alert-email.handler.ts
similarity index 51%
rename from packages/lib/jobs/definitions/emails/send-organisation-limit-exceeded-email.handler.ts
rename to packages/lib/jobs/definitions/emails/send-organisation-limit-alert-email.handler.ts
index ea4ebe36b..d43a661b1 100644
--- a/packages/lib/jobs/definitions/emails/send-organisation-limit-exceeded-email.handler.ts
+++ b/packages/lib/jobs/definitions/emails/send-organisation-limit-alert-email.handler.ts
@@ -1,21 +1,25 @@
-import OrganisationLimitExceededEmailTemplate from '@documenso/email/templates/organisation-limit-exceeded';
+import { mailer } from '@documenso/email/mailer';
+import OrganisationLimitAlertEmailTemplate from '@documenso/email/templates/organisation-limit-alert';
+import { SUPPORT_EMAIL } from '@documenso/lib/constants/app';
+
import { prisma } from '@documenso/prisma';
import { msg } from '@lingui/core/macro';
import { createElement } from 'react';
import { getI18nInstance } from '../../../client-only/providers/i18n-server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
+import { DOCUMENSO_INTERNAL_EMAIL } from '../../../constants/email';
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '../../../constants/organisations';
import { getEmailContext } from '../../../server-only/email/get-email-context';
import { INTERNAL_CLAIM_ID } from '../../../types/subscription';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import type { JobRunIO } from '../../client/_internal/job';
-import type { TSendOrganisationLimitExceededEmailJobDefinition } from './send-organisation-limit-exceeded-email';
+import type { TSendOrganisationLimitAlertEmailJobDefinition } from './send-organisation-limit-alert-email';
export const run = async ({
payload,
io,
}: {
- payload: TSendOrganisationLimitExceededEmailJobDefinition;
+ payload: TSendOrganisationLimitAlertEmailJobDefinition;
io: JobRunIO;
}) => {
const organisation = await prisma.organisation.findFirstOrThrow({
@@ -24,6 +28,16 @@ export const run = async ({
},
include: {
organisationClaim: true,
+ monthlyStats: {
+ where: {
+ period: payload.period,
+ },
+ select: {
+ documentCount: true,
+ emailCount: true,
+ apiCount: true,
+ },
+ },
members: {
where: {
organisationGroupMembers: {
@@ -60,16 +74,19 @@ export const run = async ({
// Do not send emails for "free" claims.
if (organisation.organisationClaim.originalSubscriptionClaimId === INTERNAL_CLAIM_ID.FREE) {
io.logger.info({
- msg: 'Skipping organisation limit exceeded email for "free" claim',
+ msg: 'Skipping organisation limit alert email for "free" claim',
organisationId: organisation.id,
});
return;
}
+ const memberSubject =
+ payload.kind === 'quotaNearing' ? msg`Approaching Your Plan Limits` : msg`Organisation Review Required`;
+
for (const member of organisation.members) {
- await io.runTask(`send-organisation-limit-exceeded-email-${member.id}`, async () => {
- const emailContent = createElement(OrganisationLimitExceededEmailTemplate, {
+ await io.runTask(`send-organisation-limit-alert-email-${member.id}`, async () => {
+ const emailContent = createElement(OrganisationLimitAlertEmailTemplate, {
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
organisationName: organisation.name,
counter: payload.counter,
@@ -87,7 +104,7 @@ export const run = async ({
await emailTransport.sendMail({
to: member.user.email,
from: senderEmail,
- subject: i18n._(msg`Organisation Review Required`),
+ subject: i18n._(memberSubject),
html,
text,
});
@@ -95,20 +112,31 @@ export const run = async ({
}
// Todo: Logging
- // Todo: Decide if we want to send an email or alert via another software.
- // const i18n = await getI18nInstance('en');
+ const i18n = await getI18nInstance('en');
- // // Email our support team.
- // await mailer.sendMail({
- // to: SUPPORT_EMAIL,
- // from: senderEmail,
- // subject: i18n._(msg`An organisation has exceeded their fair use limits`),
- // text: `
- // Organisation: ${organisation.name}
- // Organisation ID: ${organisation.id}
- // Counter: ${payload.counter}
- // Kind: ${payload.kind}
- // Period: ${payload.period}
- // `,
- // });
+ const supportSubject =
+ payload.kind === 'quotaNearing'
+ ? msg`An organisation is nearing their fair use limits`
+ : msg`An organisation has exceeded their fair use limits`;
+
+ // Email our support team. Purposefully sent from the internal email since the
+ // global mailer is not authorized to send from custom per-plan transport addresses.
+ await io.runTask('send-organisation-limit-alert-support-email', async () => {
+ await mailer.sendMail({
+ to: SUPPORT_EMAIL,
+ from: DOCUMENSO_INTERNAL_EMAIL,
+ subject: i18n._(supportSubject),
+ text: `
+ Organisation: ${organisation.name}
+ Organisation ID: ${organisation.id}
+ Organisation Claim Original ID: ${organisation.organisationClaim.originalSubscriptionClaimId}
+ Email Quota: ${organisation.monthlyStats[0]?.emailCount || 0}/${organisation.organisationClaim.emailQuota ?? 'Unlimited'}
+ API Quota: ${organisation.monthlyStats[0]?.apiCount || 0}/${organisation.organisationClaim.apiQuota ?? 'Unlimited'}
+ Document Quota: ${organisation.monthlyStats[0]?.documentCount || 0}/${organisation.organisationClaim.documentQuota ?? 'Unlimited'}
+ Counter: ${payload.counter}
+ Kind: ${payload.kind}
+ Period: ${payload.period}
+ `,
+ });
+ });
};
diff --git a/packages/lib/jobs/definitions/emails/send-organisation-limit-alert-email.ts b/packages/lib/jobs/definitions/emails/send-organisation-limit-alert-email.ts
new file mode 100644
index 000000000..e4b14f1ef
--- /dev/null
+++ b/packages/lib/jobs/definitions/emails/send-organisation-limit-alert-email.ts
@@ -0,0 +1,34 @@
+import { z } from 'zod';
+
+import type { JobDefinition } from '../../client/_internal/job';
+
+const SEND_ORGANISATION_LIMIT_ALERT_EMAIL_JOB_DEFINITION_ID = 'send.organisation-limit-alert.email';
+
+const SEND_ORGANISATION_LIMIT_ALERT_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
+ organisationId: z.string(),
+ counter: z.enum(['document', 'email', 'api']),
+ kind: z.enum(['rateLimit', 'quota', 'quotaNearing']),
+ period: z.string(),
+});
+
+export type TSendOrganisationLimitAlertEmailJobDefinition = z.infer<
+ typeof SEND_ORGANISATION_LIMIT_ALERT_EMAIL_JOB_DEFINITION_SCHEMA
+>;
+
+export const SEND_ORGANISATION_LIMIT_ALERT_EMAIL_JOB_DEFINITION = {
+ id: SEND_ORGANISATION_LIMIT_ALERT_EMAIL_JOB_DEFINITION_ID,
+ name: 'Send Organisation Limit Alert Email',
+ version: '1.0.0',
+ trigger: {
+ name: SEND_ORGANISATION_LIMIT_ALERT_EMAIL_JOB_DEFINITION_ID,
+ schema: SEND_ORGANISATION_LIMIT_ALERT_EMAIL_JOB_DEFINITION_SCHEMA,
+ },
+ handler: async ({ payload, io }) => {
+ const handler = await import('./send-organisation-limit-alert-email.handler');
+
+ await handler.run({ payload, io });
+ },
+} as const satisfies JobDefinition<
+ typeof SEND_ORGANISATION_LIMIT_ALERT_EMAIL_JOB_DEFINITION_ID,
+ TSendOrganisationLimitAlertEmailJobDefinition
+>;
diff --git a/packages/lib/jobs/definitions/emails/send-organisation-limit-exceeded-email.ts b/packages/lib/jobs/definitions/emails/send-organisation-limit-exceeded-email.ts
deleted file mode 100644
index 06595c8b5..000000000
--- a/packages/lib/jobs/definitions/emails/send-organisation-limit-exceeded-email.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { z } from 'zod';
-
-import type { JobDefinition } from '../../client/_internal/job';
-
-const SEND_ORGANISATION_LIMIT_EXCEEDED_EMAIL_JOB_DEFINITION_ID = 'send.organisation-limit-exceeded.email';
-
-const SEND_ORGANISATION_LIMIT_EXCEEDED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
- organisationId: z.string(),
- counter: z.enum(['document', 'email', 'api']),
- kind: z.enum(['rateLimit', 'quota']),
- period: z.string(),
-});
-
-export type TSendOrganisationLimitExceededEmailJobDefinition = z.infer<
- typeof SEND_ORGANISATION_LIMIT_EXCEEDED_EMAIL_JOB_DEFINITION_SCHEMA
->;
-
-export const SEND_ORGANISATION_LIMIT_EXCEEDED_EMAIL_JOB_DEFINITION = {
- id: SEND_ORGANISATION_LIMIT_EXCEEDED_EMAIL_JOB_DEFINITION_ID,
- name: 'Send Organisation Limit Exceeded Email',
- version: '1.0.0',
- trigger: {
- name: SEND_ORGANISATION_LIMIT_EXCEEDED_EMAIL_JOB_DEFINITION_ID,
- schema: SEND_ORGANISATION_LIMIT_EXCEEDED_EMAIL_JOB_DEFINITION_SCHEMA,
- },
- handler: async ({ payload, io }) => {
- const handler = await import('./send-organisation-limit-exceeded-email.handler');
-
- await handler.run({ payload, io });
- },
-} as const satisfies JobDefinition<
- typeof SEND_ORGANISATION_LIMIT_EXCEEDED_EMAIL_JOB_DEFINITION_ID,
- TSendOrganisationLimitExceededEmailJobDefinition
->;
diff --git a/packages/lib/server-only/rate-limit/check-monthly-quota.ts b/packages/lib/server-only/rate-limit/check-monthly-quota.ts
index 26dd70774..9066a2c95 100644
--- a/packages/lib/server-only/rate-limit/check-monthly-quota.ts
+++ b/packages/lib/server-only/rate-limit/check-monthly-quota.ts
@@ -3,6 +3,7 @@ import { AppError, AppErrorCode } from '../../errors/app-error';
import { jobsClient } from '../../jobs/client';
import { generateDatabaseId } from '../../universal/id';
import { currentMonthlyPeriod } from '../../universal/monthly-period';
+import { getQuotaAlertKind } from './get-quota-alert-kind';
import type { LimitCounter } from './types';
type CheckMonthlyQuotaOptions = {
@@ -56,29 +57,33 @@ export const checkMonthlyQuota = async (opts: CheckMonthlyQuotaOptions): Promise
const newCount = latestMonthlyStat[column];
const previousCount = newCount - opts.count;
- const isOverQuota = newCount > opts.quota;
+ // Returns 'quota' on the single request that reached (or jumped past) the quota,
+ // 'quotaNearing' on the single request that reached the warning threshold,
+ // otherwise null. See getQuotaAlertKind for the exactly-once guarantee.
+ const alertKind = getQuotaAlertKind({
+ previousCount,
+ newCount,
+ quota: opts.quota,
+ });
- // Only notify on the single request that crossed the threshold: the count was
- // at/under quota before this request and over it after. Because the DB
- // serializes the atomic increment, the post-increment values are distinct and
- // monotonic, so exactly one request's (previousCount, newCount] interval
- // contains the quota boundary — guaranteeing the notification fires once.
- const didCrossQuota = isOverQuota && previousCount <= opts.quota;
-
- if (didCrossQuota) {
+ // Trigger the alert before the over-quota check — the 'quota' alert usually fires
+ // on the successful request that consumes the last unit of allowance, but when a
+ // batch jumps past the boundary it fires on this rejected request. Either way it
+ // will never fire again this period, so it must be enqueued before any throw.
+ if (alertKind) {
await jobsClient
.triggerJob({
- name: 'send.organisation-limit-exceeded.email',
+ name: 'send.organisation-limit-alert.email',
payload: {
organisationId: opts.organisationId,
counter: opts.counter,
- kind: 'quota',
+ kind: alertKind,
period,
},
})
.catch((error) => {
console.error({
- msg: 'Failed to send organisation limit exceeded email',
+ msg: 'Failed to send organisation limit alert email',
error,
});
@@ -86,7 +91,7 @@ export const checkMonthlyQuota = async (opts: CheckMonthlyQuotaOptions): Promise
});
}
- if (isOverQuota) {
+ if (newCount > opts.quota) {
throw new AppError(AppErrorCode.TOO_MANY_REQUESTS, {
message:
'Your request could not be completed at this time due to your account exceeding the fair use limits of your current plan. Please contact support.',
diff --git a/packages/lib/server-only/rate-limit/compute-quota-flags.ts b/packages/lib/server-only/rate-limit/compute-quota-flags.ts
index c53256606..ea463cf69 100644
--- a/packages/lib/server-only/rate-limit/compute-quota-flags.ts
+++ b/packages/lib/server-only/rate-limit/compute-quota-flags.ts
@@ -1,7 +1,12 @@
+import { QUOTA_WARNING_THRESHOLD } from './get-quota-alert-kind';
+
export type QuotaFlags = {
isDocumentQuotaExceeded: boolean;
isEmailQuotaExceeded: boolean;
isApiQuotaExceeded: boolean;
+ isDocumentQuotaNearing: boolean;
+ isEmailQuotaNearing: boolean;
+ isApiQuotaNearing: boolean;
};
type ComputeQuotaFlagsOptions = {
@@ -20,11 +25,6 @@ type ComputeQuotaFlagsOptions = {
/**
* A quota of `null` means unlimited (never exceeded). A quota of `0` means
* blocked (always exceeded). Otherwise usage `>=` quota is exceeded.
- *
- * Note: this `>=` is intentionally the "reached" signal for the banner and is
- * distinct from enforcement in `check-monthly-quota.ts`, which blocks the
- * action that crosses the boundary using a strict `>` on the post-increment
- * count. Do not "align" them — they answer different questions.
*/
const isQuotaExceeded = (quota: number | null, usage: number): boolean => {
if (quota === null) {
@@ -38,10 +38,30 @@ const isQuotaExceeded = (quota: number | null, usage: number): boolean => {
return usage >= quota;
};
+/**
+ * A counter is "nearing" its quota once usage reaches the warning threshold
+ * (80% of the quota, rounded up) but has not yet been exceeded. Nearing and
+ * exceeded are mutually exclusive per counter.
+ */
+const isQuotaNearing = (quota: number | null, usage: number): boolean => {
+ if (quota === null || quota === 0) {
+ return false;
+ }
+
+ if (isQuotaExceeded(quota, usage)) {
+ return false;
+ }
+
+ return usage >= Math.ceil(quota * QUOTA_WARNING_THRESHOLD);
+};
+
export const computeQuotaFlags = ({ quotas, usage }: ComputeQuotaFlagsOptions): QuotaFlags => {
return {
isDocumentQuotaExceeded: isQuotaExceeded(quotas.documentQuota, usage?.documentCount ?? 0),
isEmailQuotaExceeded: isQuotaExceeded(quotas.emailQuota, usage?.emailCount ?? 0),
isApiQuotaExceeded: isQuotaExceeded(quotas.apiQuota, usage?.apiCount ?? 0),
+ isDocumentQuotaNearing: isQuotaNearing(quotas.documentQuota, usage?.documentCount ?? 0),
+ isEmailQuotaNearing: isQuotaNearing(quotas.emailQuota, usage?.emailCount ?? 0),
+ isApiQuotaNearing: isQuotaNearing(quotas.apiQuota, usage?.apiCount ?? 0),
};
};
diff --git a/packages/lib/server-only/rate-limit/get-quota-alert-kind.ts b/packages/lib/server-only/rate-limit/get-quota-alert-kind.ts
new file mode 100644
index 000000000..afaaeaa14
--- /dev/null
+++ b/packages/lib/server-only/rate-limit/get-quota-alert-kind.ts
@@ -0,0 +1,40 @@
+export const QUOTA_WARNING_THRESHOLD = 0.8;
+
+export type QuotaAlertKind = 'quota' | 'quotaNearing';
+
+type GetQuotaAlertKindOptions = {
+ previousCount: number;
+ newCount: number;
+ quota: number;
+};
+
+/**
+ * Determines whether the request that moved the counter from `previousCount` to
+ * `newCount` crossed an alert threshold.
+ *
+ * - 'quota': this request reached (or jumped past) the monthly quota.
+ * - 'quotaNearing': this request reached the warning threshold (80% of quota).
+ * - null: no threshold crossed by this request.
+ *
+ * Precondition: callers must handle `quota === null` (unlimited) and `quota === 0`
+ * (blocked) before calling — this function assumes a positive quota.
+ */
+export const getQuotaAlertKind = (opts: GetQuotaAlertKindOptions): QuotaAlertKind | null => {
+ const { previousCount, newCount, quota } = opts;
+
+ if (newCount >= quota) {
+ // Only the single request that reached the quota boundary should alert. If the
+ // same request also skipped past the warning threshold, the quota alert
+ // supersedes the warning.
+ return previousCount < quota ? 'quota' : null;
+ }
+
+ // From here newCount < quota, so for tiny quotas (1-4) where the rounded-up
+ // warning threshold equals the quota itself, the warning can never fire — the
+ // exhausting request is handled by the quota branch above.
+ const warningCount = Math.ceil(quota * QUOTA_WARNING_THRESHOLD);
+
+ const didCrossWarning = newCount >= warningCount && previousCount < warningCount;
+
+ return didCrossWarning ? 'quotaNearing' : null;
+};
diff --git a/packages/lib/translations/de/web.po b/packages/lib/translations/de/web.po
index fba721d3e..d748ef36c 100644
--- a/packages/lib/translations/de/web.po
+++ b/packages/lib/translations/de/web.po
@@ -8,7 +8,7 @@ msgstr ""
"Language: de\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2026-06-09 05:23\n"
+"PO-Revision-Date: 2026-06-12 07:37\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -642,8 +642,8 @@ msgstr "{visibleRows, plural, one {Eine # Ergebnis wird angezeigt.} other {# Erg
#. placeholder {0}: envelope.title
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
-msgid "<0>\"{0}\"0>is no longer available to sign"
-msgstr "<0>\"{0}\"0> steht nicht mehr zur Unterschrift zur Verfügung"
+msgid "<0>\"{0}\"0> is no longer available to sign"
+msgstr "<0>„{0}“0> ist nicht mehr zum Unterschreiben verfügbar"
#: packages/email/templates/organisation-account-link-confirmation.tsx
msgid "<0>{organisationName}0> has requested to create an account on your behalf."
@@ -5778,6 +5778,11 @@ msgctxt "Subscription status"
msgid "Free"
msgstr "Kostenlos"
+#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
+msgctxt "Subscription status"
+msgid "Free (Pending)"
+msgstr "Kostenlos (ausstehend)"
+
#: packages/lib/utils/fields.ts
#: packages/ui/primitives/document-flow/types.ts
msgid "Free Signature"
@@ -6146,6 +6151,7 @@ msgstr "Eingabefelder werden identifiziert"
msgid "Identifying recipients"
msgstr "Empfänger werden identifiziert"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
msgid "If there is any issue with your subscription, please contact us at <0>{SUPPORT_EMAIL}0>."
msgstr "Bei einem Problem mit Ihrem Abo kontaktieren Sie uns bitte unter <0>{SUPPORT_EMAIL}0>."
@@ -6780,6 +6786,7 @@ msgstr "Vorlage verwalten und anzeigen"
msgid "Manage billing"
msgstr "Rechnungsmanagement"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
msgid "Manage Billing"
@@ -7974,6 +7981,11 @@ msgstr "Überfällig"
msgid "Payment overdue"
msgstr "Zahlung überfällig"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+msgid "Payment required"
+msgstr "Zahlung erforderlich"
+
#: apps/remix/app/components/dialogs/envelope-download-dialog.tsx
msgid "PDF Document"
msgstr "PDF-Dokument"
@@ -11461,6 +11473,10 @@ msgstr "Dieses Element kann nicht gelöscht werden"
msgid "This link is invalid or has expired. Please contact your team to resend a verification."
msgstr "Dieser Link ist ungültig oder abgelaufen. Bitte kontaktieren Sie Ihr Team, um eine neue Bestätigungsanfrage zu senden."
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+msgid "This organisation is awaiting payment. Complete checkout to unlock it."
+msgstr "Für diese Organisation steht eine Zahlung aus. Schließen Sie den Checkout ab, um sie freizuschalten."
+
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
msgid "This organisation will have administrative control over your account. You can revoke this access later, but they will retain access to any data they've already collected."
msgstr "Diese Organisation wird administrative Kontrolle über Ihr Konto haben. Sie können den Zugriff später widerrufen, aber sie behalten den Zugriff auf alle bereits gesammelten Daten."
diff --git a/packages/lib/translations/es/web.po b/packages/lib/translations/es/web.po
index 2c147da78..b7cba4cad 100644
--- a/packages/lib/translations/es/web.po
+++ b/packages/lib/translations/es/web.po
@@ -8,7 +8,7 @@ msgstr ""
"Language: es\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2026-06-09 05:23\n"
+"PO-Revision-Date: 2026-06-12 07:37\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -642,7 +642,7 @@ msgstr "{visibleRows, plural, one {Mostrando # resultado.} other {Mostrando # re
#. placeholder {0}: envelope.title
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
-msgid "<0>\"{0}\"0>is no longer available to sign"
+msgid "<0>\"{0}\"0> is no longer available to sign"
msgstr "<0>\"{0}\"0> ya no está disponible para firmar"
#: packages/email/templates/organisation-account-link-confirmation.tsx
@@ -5778,6 +5778,11 @@ msgctxt "Subscription status"
msgid "Free"
msgstr "Gratis"
+#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
+msgctxt "Subscription status"
+msgid "Free (Pending)"
+msgstr "Gratis (pendiente)"
+
#: packages/lib/utils/fields.ts
#: packages/ui/primitives/document-flow/types.ts
msgid "Free Signature"
@@ -6146,6 +6151,7 @@ msgstr "Identificando campos de entrada"
msgid "Identifying recipients"
msgstr "Identificando destinatarios"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
msgid "If there is any issue with your subscription, please contact us at <0>{SUPPORT_EMAIL}0>."
msgstr "Si tienes algún problema con tu suscripción, por favor contáctanos en <0>{SUPPORT_EMAIL}0>."
@@ -6780,6 +6786,7 @@ msgstr "Gestionar y ver plantilla"
msgid "Manage billing"
msgstr "Gestionar la facturación"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
msgid "Manage Billing"
@@ -7974,6 +7981,11 @@ msgstr "Vencida"
msgid "Payment overdue"
msgstr "Pago atrasado"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+msgid "Payment required"
+msgstr "Pago requerido"
+
#: apps/remix/app/components/dialogs/envelope-download-dialog.tsx
msgid "PDF Document"
msgstr "Documento PDF"
@@ -11461,6 +11473,10 @@ msgstr "Este elemento no se puede eliminar"
msgid "This link is invalid or has expired. Please contact your team to resend a verification."
msgstr "Este enlace es inválido o ha expirado. Por favor, contacta a tu equipo para reenviar una verificación."
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+msgid "This organisation is awaiting payment. Complete checkout to unlock it."
+msgstr "Esta organización está a la espera de pago. Completa el proceso de pago para desbloquearla."
+
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
msgid "This organisation will have administrative control over your account. You can revoke this access later, but they will retain access to any data they've already collected."
msgstr "Esta organización tendrá control administrativo sobre su cuenta. Puede revocar este acceso más tarde, pero conservarán el acceso a cualquier dato que ya hayan recopilado."
diff --git a/packages/lib/translations/fr/web.po b/packages/lib/translations/fr/web.po
index 9cfb42946..cb5e87a58 100644
--- a/packages/lib/translations/fr/web.po
+++ b/packages/lib/translations/fr/web.po
@@ -8,7 +8,7 @@ msgstr ""
"Language: fr\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2026-06-09 05:23\n"
+"PO-Revision-Date: 2026-06-12 07:37\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
@@ -642,8 +642,8 @@ msgstr "{visibleRows, plural, one {Affichage de # résultat.} other {Affichage d
#. placeholder {0}: envelope.title
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
-msgid "<0>\"{0}\"0>is no longer available to sign"
-msgstr "<0>\"{0}\"0> n'est plus disponible pour signer"
+msgid "<0>\"{0}\"0> is no longer available to sign"
+msgstr "<0>\"{0}\"0> n’est plus disponible pour signature"
#: packages/email/templates/organisation-account-link-confirmation.tsx
msgid "<0>{organisationName}0> has requested to create an account on your behalf."
@@ -5778,6 +5778,11 @@ msgctxt "Subscription status"
msgid "Free"
msgstr "Gratuit"
+#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
+msgctxt "Subscription status"
+msgid "Free (Pending)"
+msgstr "Gratuit (en attente)"
+
#: packages/lib/utils/fields.ts
#: packages/ui/primitives/document-flow/types.ts
msgid "Free Signature"
@@ -6146,6 +6151,7 @@ msgstr "Identification des champs de saisie"
msgid "Identifying recipients"
msgstr "Identification des destinataires"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
msgid "If there is any issue with your subscription, please contact us at <0>{SUPPORT_EMAIL}0>."
msgstr "Si vous rencontrez un problème avec votre abonnement, veuillez nous contacter à <0>{SUPPORT_EMAIL}0>."
@@ -6780,6 +6786,7 @@ msgstr "Gérer et afficher le modèle"
msgid "Manage billing"
msgstr "Gérer la facturation"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
msgid "Manage Billing"
@@ -7974,6 +7981,11 @@ msgstr "En retard de paiement"
msgid "Payment overdue"
msgstr "Paiement en retard"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+msgid "Payment required"
+msgstr "Paiement requis"
+
#: apps/remix/app/components/dialogs/envelope-download-dialog.tsx
msgid "PDF Document"
msgstr "Document PDF"
@@ -11461,6 +11473,10 @@ msgstr "Cet élément ne peut pas être supprimé"
msgid "This link is invalid or has expired. Please contact your team to resend a verification."
msgstr "Ce lien est invalide ou a expiré. Veuillez contacter votre équipe pour renvoyer une vérification."
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+msgid "This organisation is awaiting payment. Complete checkout to unlock it."
+msgstr "Cette organisation est en attente de paiement. Terminez le paiement pour la déverrouiller."
+
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
msgid "This organisation will have administrative control over your account. You can revoke this access later, but they will retain access to any data they've already collected."
msgstr "Cette organisation aura un contrôle administratif sur votre compte. Vous pourrez révoquer cet accès plus tard, mais ils conserveront l'accès à toutes les données qu'ils ont déjà collectées."
diff --git a/packages/lib/translations/it/web.po b/packages/lib/translations/it/web.po
index 96b4fab99..9fe90e2b8 100644
--- a/packages/lib/translations/it/web.po
+++ b/packages/lib/translations/it/web.po
@@ -8,7 +8,7 @@ msgstr ""
"Language: it\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2026-06-09 05:23\n"
+"PO-Revision-Date: 2026-06-12 07:37\n"
"Last-Translator: \n"
"Language-Team: Italian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -642,8 +642,8 @@ msgstr "{visibleRows, plural, one {Visualizzazione di # risultato.} other {Visua
#. placeholder {0}: envelope.title
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
-msgid "<0>\"{0}\"0>is no longer available to sign"
-msgstr "<0>\"{0}\"0>non è più disponibile per la firma"
+msgid "<0>\"{0}\"0> is no longer available to sign"
+msgstr "<0>\"{0}\"0> non è più disponibile per la firma"
#: packages/email/templates/organisation-account-link-confirmation.tsx
msgid "<0>{organisationName}0> has requested to create an account on your behalf."
@@ -5778,6 +5778,11 @@ msgctxt "Subscription status"
msgid "Free"
msgstr "Gratuito"
+#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
+msgctxt "Subscription status"
+msgid "Free (Pending)"
+msgstr "Gratuito (in sospeso)"
+
#: packages/lib/utils/fields.ts
#: packages/ui/primitives/document-flow/types.ts
msgid "Free Signature"
@@ -6146,6 +6151,7 @@ msgstr "Identificazione dei campi di input in corso"
msgid "Identifying recipients"
msgstr "Identificazione dei destinatari in corso"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
msgid "If there is any issue with your subscription, please contact us at <0>{SUPPORT_EMAIL}0>."
msgstr "Se ci sono problemi con il tuo abbonamento, contattaci a <0>{SUPPORT_EMAIL}0>."
@@ -6780,6 +6786,7 @@ msgstr "Gestisci e visualizza il modello"
msgid "Manage billing"
msgstr "Gestisci la fatturazione"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
msgid "Manage Billing"
@@ -7974,6 +7981,11 @@ msgstr "In ritardo"
msgid "Payment overdue"
msgstr "Pagamento scaduto"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+msgid "Payment required"
+msgstr "Pagamento richiesto"
+
#: apps/remix/app/components/dialogs/envelope-download-dialog.tsx
msgid "PDF Document"
msgstr "Documento PDF"
@@ -11461,6 +11473,10 @@ msgstr "Questo elemento non può essere eliminato"
msgid "This link is invalid or has expired. Please contact your team to resend a verification."
msgstr "Questo link è invalido o è scaduto. Si prega di contattare il tuo team per inviare nuovamente una verifica."
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+msgid "This organisation is awaiting payment. Complete checkout to unlock it."
+msgstr "Questa organizzazione è in attesa di pagamento. Completa il checkout per sbloccarla."
+
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
msgid "This organisation will have administrative control over your account. You can revoke this access later, but they will retain access to any data they've already collected."
msgstr "Questa organizzazione avrà il controllo amministrativo sul tuo account. Puoi revocare questo accesso in seguito, ma conserveranno l'accesso a qualsiasi dato che hanno già raccolto."
diff --git a/packages/lib/translations/ja/web.po b/packages/lib/translations/ja/web.po
index faf53549f..b2b2f78df 100644
--- a/packages/lib/translations/ja/web.po
+++ b/packages/lib/translations/ja/web.po
@@ -8,7 +8,7 @@ msgstr ""
"Language: ja\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2026-06-09 05:23\n"
+"PO-Revision-Date: 2026-06-12 07:37\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"Plural-Forms: nplurals=1; plural=0;\n"
@@ -642,8 +642,8 @@ msgstr "{visibleRows, plural, other {# 件の結果を表示中}}"
#. placeholder {0}: envelope.title
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
-msgid "<0>\"{0}\"0>is no longer available to sign"
-msgstr "<0>\"{0}\"0>は署名できなくなりました"
+msgid "<0>\"{0}\"0> is no longer available to sign"
+msgstr "<0>\"{0}\"0> は署名できなくなりました"
#: packages/email/templates/organisation-account-link-confirmation.tsx
msgid "<0>{organisationName}0> has requested to create an account on your behalf."
@@ -5778,6 +5778,11 @@ msgctxt "Subscription status"
msgid "Free"
msgstr "無料"
+#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
+msgctxt "Subscription status"
+msgid "Free (Pending)"
+msgstr "無料(保留中)"
+
#: packages/lib/utils/fields.ts
#: packages/ui/primitives/document-flow/types.ts
msgid "Free Signature"
@@ -6146,6 +6151,7 @@ msgstr "入力フィールドを特定しています"
msgid "Identifying recipients"
msgstr "受信者を特定しています"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
msgid "If there is any issue with your subscription, please contact us at <0>{SUPPORT_EMAIL}0>."
msgstr "サブスクリプションに問題がある場合は、<0>{SUPPORT_EMAIL}0> までお問い合わせください。"
@@ -6780,6 +6786,7 @@ msgstr "テンプレートを管理・閲覧"
msgid "Manage billing"
msgstr "請求を管理"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
msgid "Manage Billing"
@@ -7974,6 +7981,11 @@ msgstr "支払い遅延"
msgid "Payment overdue"
msgstr "支払い期限超過"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+msgid "Payment required"
+msgstr "支払いが必要です"
+
#: apps/remix/app/components/dialogs/envelope-download-dialog.tsx
msgid "PDF Document"
msgstr "PDF文書"
@@ -11461,6 +11473,10 @@ msgstr "この項目は削除できません"
msgid "This link is invalid or has expired. Please contact your team to resend a verification."
msgstr "このリンクは無効か有効期限が切れています。チームに連絡して、認証を再送してもらってください。"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+msgid "This organisation is awaiting payment. Complete checkout to unlock it."
+msgstr "この組織は支払い待ちの状態です。ロックを解除するにはチェックアウトを完了してください。"
+
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
msgid "This organisation will have administrative control over your account. You can revoke this access later, but they will retain access to any data they've already collected."
msgstr "この組織はあなたのアカウントに対して管理権限を持ちます。このアクセスは後で取り消せますが、すでに収集済みのデータには引き続きアクセスできます。"
diff --git a/packages/lib/translations/ko/web.po b/packages/lib/translations/ko/web.po
index 2a214b9fb..7c345f934 100644
--- a/packages/lib/translations/ko/web.po
+++ b/packages/lib/translations/ko/web.po
@@ -8,7 +8,7 @@ msgstr ""
"Language: ko\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2026-06-09 05:23\n"
+"PO-Revision-Date: 2026-06-12 07:37\n"
"Last-Translator: \n"
"Language-Team: Korean\n"
"Plural-Forms: nplurals=1; plural=0;\n"
@@ -642,7 +642,7 @@ msgstr "{visibleRows, plural, other {총 #개 결과 중 표시 중입니다.}}"
#. placeholder {0}: envelope.title
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
-msgid "<0>\"{0}\"0>is no longer available to sign"
+msgid "<0>\"{0}\"0> is no longer available to sign"
msgstr "<0>\"{0}\"0>은(는) 더 이상 서명할 수 없습니다"
#: packages/email/templates/organisation-account-link-confirmation.tsx
@@ -5778,6 +5778,11 @@ msgctxt "Subscription status"
msgid "Free"
msgstr "무료"
+#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
+msgctxt "Subscription status"
+msgid "Free (Pending)"
+msgstr "무료(보류 중)"
+
#: packages/lib/utils/fields.ts
#: packages/ui/primitives/document-flow/types.ts
msgid "Free Signature"
@@ -6146,6 +6151,7 @@ msgstr "입력 필드 식별 중"
msgid "Identifying recipients"
msgstr "수신자 식별 중"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
msgid "If there is any issue with your subscription, please contact us at <0>{SUPPORT_EMAIL}0>."
msgstr "구독 관련 문제가 있는 경우 <0>{SUPPORT_EMAIL}0>로 연락해 주세요."
@@ -6780,6 +6786,7 @@ msgstr "템플릿 관리 및 보기"
msgid "Manage billing"
msgstr "결제 관리"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
msgid "Manage Billing"
@@ -7974,6 +7981,11 @@ msgstr "연체"
msgid "Payment overdue"
msgstr "결제 지연"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+msgid "Payment required"
+msgstr "결제가 필요합니다"
+
#: apps/remix/app/components/dialogs/envelope-download-dialog.tsx
msgid "PDF Document"
msgstr "PDF 문서"
@@ -11461,6 +11473,10 @@ msgstr "이 항목은 삭제할 수 없습니다"
msgid "This link is invalid or has expired. Please contact your team to resend a verification."
msgstr "이 링크는 유효하지 않거나 만료되었습니다. 팀에 문의해 인증 메일을 다시 보내 달라고 요청해 주세요."
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+msgid "This organisation is awaiting payment. Complete checkout to unlock it."
+msgstr "이 조직은 결제를 기다리고 있습니다. 잠금 해제를 위해 결제를 완료하세요."
+
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
msgid "This organisation will have administrative control over your account. You can revoke this access later, but they will retain access to any data they've already collected."
msgstr "이 조직은 귀하의 계정을 관리할 수 있는 권한을 갖게 됩니다. 나중에 이 액세스를 취소할 수 있지만, 이미 수집한 데이터에는 계속 액세스할 수 있습니다."
diff --git a/packages/lib/translations/nl/web.po b/packages/lib/translations/nl/web.po
index 08bcb0cb9..c39321ca0 100644
--- a/packages/lib/translations/nl/web.po
+++ b/packages/lib/translations/nl/web.po
@@ -8,7 +8,7 @@ msgstr ""
"Language: nl\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2026-06-09 05:23\n"
+"PO-Revision-Date: 2026-06-12 07:37\n"
"Last-Translator: \n"
"Language-Team: Dutch\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -642,8 +642,8 @@ msgstr "{visibleRows, plural, one {# resultaat wordt getoond.} other {# resultat
#. placeholder {0}: envelope.title
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
-msgid "<0>\"{0}\"0>is no longer available to sign"
-msgstr "<0>\"{0}\"0>is niet langer beschikbaar om te ondertekenen"
+msgid "<0>\"{0}\"0> is no longer available to sign"
+msgstr "<0>\"{0}\"0> is niet langer beschikbaar om te ondertekenen"
#: packages/email/templates/organisation-account-link-confirmation.tsx
msgid "<0>{organisationName}0> has requested to create an account on your behalf."
@@ -5778,6 +5778,11 @@ msgctxt "Subscription status"
msgid "Free"
msgstr "Gratis"
+#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
+msgctxt "Subscription status"
+msgid "Free (Pending)"
+msgstr "Gratis (in behandeling)"
+
#: packages/lib/utils/fields.ts
#: packages/ui/primitives/document-flow/types.ts
msgid "Free Signature"
@@ -6146,6 +6151,7 @@ msgstr "Invoervelden identificeren"
msgid "Identifying recipients"
msgstr "Ontvangers identificeren"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
msgid "If there is any issue with your subscription, please contact us at <0>{SUPPORT_EMAIL}0>."
msgstr "Als er een probleem is met je abonnement, neem dan contact met ons op via <0>{SUPPORT_EMAIL}0>."
@@ -6780,6 +6786,7 @@ msgstr "Sjabloon beheren en bekijken"
msgid "Manage billing"
msgstr "Facturering beheren"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
msgid "Manage Billing"
@@ -7974,6 +7981,11 @@ msgstr "Achterstallig"
msgid "Payment overdue"
msgstr "Betaling achterstallig"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+msgid "Payment required"
+msgstr "Betaling vereist"
+
#: apps/remix/app/components/dialogs/envelope-download-dialog.tsx
msgid "PDF Document"
msgstr "PDF-document"
@@ -11461,6 +11473,10 @@ msgstr "Dit item kan niet worden verwijderd"
msgid "This link is invalid or has expired. Please contact your team to resend a verification."
msgstr "Deze link is ongeldig of verlopen. Neem contact op met je team om de verificatie opnieuw te laten verzenden."
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+msgid "This organisation is awaiting payment. Complete checkout to unlock it."
+msgstr "Deze organisatie wacht op betaling. Rond de betaling af om deze te ontgrendelen."
+
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
msgid "This organisation will have administrative control over your account. You can revoke this access later, but they will retain access to any data they've already collected."
msgstr "Deze organisatie krijgt administratieve controle over uw account. U kunt deze toegang later intrekken, maar zij behouden toegang tot alle gegevens die zij al hebben verzameld."
diff --git a/packages/lib/translations/pl/web.po b/packages/lib/translations/pl/web.po
index 90eee3401..a5ad7e196 100644
--- a/packages/lib/translations/pl/web.po
+++ b/packages/lib/translations/pl/web.po
@@ -8,7 +8,7 @@ msgstr ""
"Language: pl\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2026-06-09 05:23\n"
+"PO-Revision-Date: 2026-06-12 07:37\n"
"Last-Translator: \n"
"Language-Team: Polish\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
@@ -642,8 +642,8 @@ msgstr "{visibleRows, plural, one {# wynik} few {# wyniki} many {# wyników} oth
#. placeholder {0}: envelope.title
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
-msgid "<0>\"{0}\"0>is no longer available to sign"
-msgstr "Dokument <0>„{0}”0>nie jest już dostępny do podpisu"
+msgid "<0>\"{0}\"0> is no longer available to sign"
+msgstr "<0>„{0}”0> nie jest już dostępny do podpisu"
#: packages/email/templates/organisation-account-link-confirmation.tsx
msgid "<0>{organisationName}0> has requested to create an account on your behalf."
@@ -5778,6 +5778,11 @@ msgctxt "Subscription status"
msgid "Free"
msgstr "Darmowa"
+#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
+msgctxt "Subscription status"
+msgid "Free (Pending)"
+msgstr "Darmowy (oczekujący)"
+
#: packages/lib/utils/fields.ts
#: packages/ui/primitives/document-flow/types.ts
msgid "Free Signature"
@@ -6146,6 +6151,7 @@ msgstr "Identyfikowanie pól formularzy"
msgid "Identifying recipients"
msgstr "Identyfikowanie odbiorców"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
msgid "If there is any issue with your subscription, please contact us at <0>{SUPPORT_EMAIL}0>."
msgstr "Jeśli masz problemy z subskrypcją, skontaktuj się z nami pod adresem <0>{SUPPORT_EMAIL}0>."
@@ -6780,6 +6786,7 @@ msgstr "Zarządzanie szablonem"
msgid "Manage billing"
msgstr "Zarządzaj płatnościami"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
msgid "Manage Billing"
@@ -7974,6 +7981,11 @@ msgstr "Przeterminowana"
msgid "Payment overdue"
msgstr "Zaległa płatność"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+msgid "Payment required"
+msgstr "Wymagana płatność"
+
#: apps/remix/app/components/dialogs/envelope-download-dialog.tsx
msgid "PDF Document"
msgstr "Dokument PDF"
@@ -11461,6 +11473,10 @@ msgstr "Nie można usunąć elementu"
msgid "This link is invalid or has expired. Please contact your team to resend a verification."
msgstr "Link jest nieprawidłowy lub wygasł. Skontaktuj się ze swoim zespołem, aby ponownie wysłać wiadomość weryfikacyjną."
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+msgid "This organisation is awaiting payment. Complete checkout to unlock it."
+msgstr "Ta organizacja oczekuje na płatność. Dokończ proces zakupu, aby ją odblokować."
+
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
msgid "This organisation will have administrative control over your account. You can revoke this access later, but they will retain access to any data they've already collected."
msgstr "Organizacja będzie miała kontrolę administracyjną nad Twoim kontem. Możesz później cofnąć ten dostęp, ale nadal będą mieli dostęp do danych, które już zgromadzili."
diff --git a/packages/lib/translations/zh/web.po b/packages/lib/translations/zh/web.po
index 748083097..cca0ca86e 100644
--- a/packages/lib/translations/zh/web.po
+++ b/packages/lib/translations/zh/web.po
@@ -8,7 +8,7 @@ msgstr ""
"Language: zh\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2026-06-09 05:23\n"
+"PO-Revision-Date: 2026-06-12 07:37\n"
"Last-Translator: \n"
"Language-Team: Chinese Simplified\n"
"Plural-Forms: nplurals=1; plural=0;\n"
@@ -642,8 +642,8 @@ msgstr "{visibleRows, plural, other {正在显示 # 条结果。}}"
#. placeholder {0}: envelope.title
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
-msgid "<0>\"{0}\"0>is no longer available to sign"
-msgstr "<0>\"{0}\"0>已不再可供签署"
+msgid "<0>\"{0}\"0> is no longer available to sign"
+msgstr "<0>“{0}”0>已不再可供签署"
#: packages/email/templates/organisation-account-link-confirmation.tsx
msgid "<0>{organisationName}0> has requested to create an account on your behalf."
@@ -5778,6 +5778,11 @@ msgctxt "Subscription status"
msgid "Free"
msgstr "免费"
+#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
+msgctxt "Subscription status"
+msgid "Free (Pending)"
+msgstr "免费(待处理)"
+
#: packages/lib/utils/fields.ts
#: packages/ui/primitives/document-flow/types.ts
msgid "Free Signature"
@@ -6146,6 +6151,7 @@ msgstr "正在识别输入字段"
msgid "Identifying recipients"
msgstr "正在识别收件人"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
msgid "If there is any issue with your subscription, please contact us at <0>{SUPPORT_EMAIL}0>."
msgstr "如果您的订阅出现任何问题,请通过 <0>{SUPPORT_EMAIL}0> 联系我们。"
@@ -6780,6 +6786,7 @@ msgstr "管理并查看模板"
msgid "Manage billing"
msgstr "管理计费"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
msgid "Manage Billing"
@@ -7974,6 +7981,11 @@ msgstr "逾期"
msgid "Payment overdue"
msgstr "付款逾期"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+msgid "Payment required"
+msgstr "需要付款"
+
#: apps/remix/app/components/dialogs/envelope-download-dialog.tsx
msgid "PDF Document"
msgstr "PDF 文档"
@@ -11461,6 +11473,10 @@ msgstr "无法删除此条目"
msgid "This link is invalid or has expired. Please contact your team to resend a verification."
msgstr "此链接无效或已过期。请联系你的团队重新发送验证邮件。"
+#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
+msgid "This organisation is awaiting payment. Complete checkout to unlock it."
+msgstr "此组织正在等待付款。完成结账以解锁。"
+
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
msgid "This organisation will have administrative control over your account. You can revoke this access later, but they will retain access to any data they've already collected."
msgstr "此组织将对您的账户拥有管理控制权。您可以稍后撤销此访问权限,但他们将保留对已收集数据的访问权。"
diff --git a/packages/trpc/server/enterprise-router/get-subscription.ts b/packages/trpc/server/enterprise-router/get-subscription.ts
index 9c7ffb4ea..f3368d792 100644
--- a/packages/trpc/server/enterprise-router/get-subscription.ts
+++ b/packages/trpc/server/enterprise-router/get-subscription.ts
@@ -1,7 +1,14 @@
import { getInternalClaimPlans } from '@documenso/ee/server-only/stripe/get-internal-claim-plans';
import { getSubscription } from '@documenso/ee/server-only/stripe/get-subscription';
+import { syncStripeCustomerSubscription } from '@documenso/ee/server-only/stripe/sync-stripe-customer-subscription';
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
+import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
+import { Stripe } from '@documenso/lib/server-only/stripe';
+import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
+import { prisma } from '@documenso/prisma';
+
+import type { Logger } from 'pino';
import { authenticatedProcedure } from '../trpc';
import { ZGetSubscriptionRequestSchema } from './get-subscription.types';
@@ -26,9 +33,17 @@ export const getSubscriptionRoute = authenticatedProcedure
}
const [subscription, plans] = await Promise.all([
+ // If the subscription is not found or there's an error, we return null to
+ // avoid failing the entire request.
getSubscription({
organisationId,
userId,
+ }).catch(async (e) => {
+ ctx.logger.error(`Failed to get subscription for organisation ${organisationId}`, e);
+
+ await reconcileMissingStripeSubscription({ logger: ctx.logger, organisationId, userId, error: e });
+
+ return null;
}),
getInternalClaimPlans(),
]);
@@ -38,3 +53,51 @@ export const getSubscriptionRoute = authenticatedProcedure
plans,
};
});
+
+type ReconcileMissingStripeSubscriptionOptions = {
+ logger: Logger;
+ organisationId: string;
+ userId: number;
+ error: unknown;
+};
+
+/**
+ * When the Stripe subscription no longer exists (e.g. deleted by Stripe's
+ * test-mode retention policy, or removed manually), fire-and-forget a reconcile
+ * so the stale local subscription row and any billing banner converge on the
+ * next load. Reconcile failures must never break the read path that calls this.
+ */
+const reconcileMissingStripeSubscription = async ({
+ logger,
+ organisationId,
+ userId,
+ error,
+}: ReconcileMissingStripeSubscriptionOptions) => {
+ if (!(error instanceof Stripe.errors.StripeInvalidRequestError) || error.code !== 'resource_missing') {
+ return;
+ }
+
+ const organisation = await prisma.organisation.findFirst({
+ where: buildOrganisationWhereQuery({
+ organisationId,
+ userId,
+ roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
+ }),
+ select: {
+ customerId: true,
+ },
+ });
+
+ if (!organisation?.customerId) {
+ return;
+ }
+
+ void syncStripeCustomerSubscription({
+ customerId: organisation.customerId,
+ }).catch((syncError) => {
+ logger.error(
+ `Failed to reconcile subscription after resource_missing for organisation ${organisationId}`,
+ syncError,
+ );
+ });
+};
diff --git a/packages/trpc/server/organisation-router/get-organisation-quota-flags.types.ts b/packages/trpc/server/organisation-router/get-organisation-quota-flags.types.ts
index e7713aeec..3dea662a4 100644
--- a/packages/trpc/server/organisation-router/get-organisation-quota-flags.types.ts
+++ b/packages/trpc/server/organisation-router/get-organisation-quota-flags.types.ts
@@ -12,6 +12,9 @@ export const ZGetOrganisationQuotaFlagsResponseSchema = z.object({
isDocumentQuotaExceeded: z.boolean(),
isEmailQuotaExceeded: z.boolean(),
isApiQuotaExceeded: z.boolean(),
+ isDocumentQuotaNearing: z.boolean(),
+ isEmailQuotaNearing: z.boolean(),
+ isApiQuotaNearing: z.boolean(),
});
export type TGetOrganisationQuotaFlagsResponse = z.infer;