diff --git a/packages/lib/jobs/definitions/emails/send-signing-email.handler.ts b/packages/lib/jobs/definitions/emails/send-signing-email.handler.ts index 9c057cc16..a3156b7d5 100644 --- a/packages/lib/jobs/definitions/emails/send-signing-email.handler.ts +++ b/packages/lib/jobs/definitions/emails/send-signing-email.handler.ts @@ -16,6 +16,7 @@ import { createElement } from 'react'; import { getI18nInstance } from '../../../client-only/providers/i18n-server'; import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app'; import { RECIPIENT_ROLE_TO_EMAIL_TYPE, RECIPIENT_ROLES_DESCRIPTION } from '../../../constants/recipient-roles'; +import { buildEnvelopeEmailHeaders } from '../../../server-only/email/build-envelope-email-headers'; import { getEmailContext } from '../../../server-only/email/get-email-context'; import { assertOrganisationRatesAndLimits } from '../../../server-only/rate-limit/assert-organisation-rates-and-limits'; import { updateRecipientNextReminder } from '../../../server-only/recipient/update-recipient-next-reminder'; @@ -224,6 +225,11 @@ export const run = async ({ payload, io }: { payload: TSendSigningEmailJobDefini subject: renderCustomEmailTemplate(documentMeta?.subject || emailSubject, customEmailTemplate), html, text, + headers: buildEnvelopeEmailHeaders({ + userId, + envelopeId: envelope.id, + teamId: envelope.teamId, + }), }); }); } diff --git a/packages/lib/jobs/definitions/internal/process-signing-reminder.handler.ts b/packages/lib/jobs/definitions/internal/process-signing-reminder.handler.ts index 5f9ef9e11..b755de015 100644 --- a/packages/lib/jobs/definitions/internal/process-signing-reminder.handler.ts +++ b/packages/lib/jobs/definitions/internal/process-signing-reminder.handler.ts @@ -16,6 +16,7 @@ import { createElement } from 'react'; import { getI18nInstance } from '../../../client-only/providers/i18n-server'; import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app'; import { RECIPIENT_ROLES_DESCRIPTION } from '../../../constants/recipient-roles'; +import { buildEnvelopeEmailHeaders } from '../../../server-only/email/build-envelope-email-headers'; import { getEmailContext } from '../../../server-only/email/get-email-context'; import { assertOrganisationRatesAndLimits } from '../../../server-only/rate-limit/assert-organisation-rates-and-limits'; import { updateRecipientNextReminder } from '../../../server-only/recipient/update-recipient-next-reminder'; @@ -211,6 +212,11 @@ export const run = async ({ payload, io }: { payload: TProcessSigningReminderJob subject: emailSubject, html, text, + headers: buildEnvelopeEmailHeaders({ + userId: envelope.userId, + envelopeId: envelope.id, + teamId: envelope.teamId, + }), }); await prisma.documentAuditLog.create({ diff --git a/packages/lib/server-only/document/resend-document.ts b/packages/lib/server-only/document/resend-document.ts index 21f4034d6..e0f119bcc 100644 --- a/packages/lib/server-only/document/resend-document.ts +++ b/packages/lib/server-only/document/resend-document.ts @@ -27,6 +27,7 @@ import { isDocumentCompleted } from '../../utils/document'; import type { EnvelopeIdOptions } from '../../utils/envelope'; import { isRecipientEmailValidForSending } from '../../utils/recipients'; import { renderEmailWithI18N } from '../../utils/render-email-with-i18n'; +import { buildEnvelopeEmailHeaders } from '../email/build-envelope-email-headers'; import { getEmailContext } from '../email/get-email-context'; import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id'; import { assertOrganisationRatesAndLimits } from '../rate-limit/assert-organisation-rates-and-limits'; @@ -255,6 +256,11 @@ export const resendDocument = async ({ id, userId, recipients, teamId, requestMe : emailSubject, html, text, + headers: buildEnvelopeEmailHeaders({ + userId: envelope.userId, + envelopeId: envelope.id, + teamId: envelope.teamId, + }), }); await prisma.documentAuditLog.create({ diff --git a/packages/lib/server-only/email/build-envelope-email-headers.ts b/packages/lib/server-only/email/build-envelope-email-headers.ts new file mode 100644 index 000000000..c6a0e102c --- /dev/null +++ b/packages/lib/server-only/email/build-envelope-email-headers.ts @@ -0,0 +1,26 @@ +export type BuildEnvelopeEmailHeadersOptions = { + userId: number; + envelopeId: string; + teamId: number; +}; + +/** + * Builds opaque sender-attribution headers stamped onto outgoing user-triggered + * envelope emails. These appear in AWS SES bounce/complaint notifications (when + * "include original headers" is enabled) so an abusive send can be traced back + * to the originating Documenso user via the admin panel. + * + * Only opaque IDs are included so recipients cannot see the sender's email + * address or name in the delivered message source. + */ +export const buildEnvelopeEmailHeaders = ({ + userId, + envelopeId, + teamId, +}: BuildEnvelopeEmailHeadersOptions): Record => { + return { + 'X-Documenso-Sender-User-Id': String(userId), + 'X-Documenso-Envelope-Id': envelopeId, + 'X-Documenso-Team-Id': String(teamId), + }; +};