diff --git a/apps/app/src/actions/policies/publish-all.ts b/apps/app/src/actions/policies/publish-all.ts index c164efc79..4aa24a9e5 100644 --- a/apps/app/src/actions/policies/publish-all.ts +++ b/apps/app/src/actions/policies/publish-all.ts @@ -1,6 +1,7 @@ 'use server'; -import { db, PolicyStatus } from '@db'; +import { sendPublishAllPoliciesEmail } from '@/jobs/tasks/email/publish-all-policies-email'; +import { db, PolicyStatus, Role } from '@db'; import { revalidatePath } from 'next/cache'; import { z } from 'zod'; import { authActionClient } from '../safe-action'; @@ -93,6 +94,51 @@ export const publishAllPoliciesAction = authActionClient } } + // Get organization info and all members to send emails + const organization = await db.organization.findUnique({ + where: { id: parsedInput.organizationId }, + select: { name: true }, + }); + + const members = await db.member.findMany({ + where: { + organizationId: parsedInput.organizationId, + isActive: true, + role: { + contains: Role.employee, + }, + }, + include: { + user: { + select: { + email: true, + name: true, + }, + }, + }, + }); + + // Trigger email tasks for all employees using batchTrigger + const emailPayloads = members + .filter((orgMember) => orgMember.user.email) + .map((orgMember) => ({ + payload: { + email: orgMember.user.email, + userName: orgMember.user.name || 'there', + organizationName: organization?.name || 'Your organization', + organizationId: parsedInput.organizationId, + }, + })); + + if (emailPayloads.length > 0) { + try { + await sendPublishAllPoliciesEmail.batchTrigger(emailPayloads); + } catch (emailError) { + console.error('[publish-all-policies] Failed to trigger bulk emails:', emailError); + // Don't throw - the policies are published successfully + } + } + revalidatePath(`/${parsedInput.organizationId}/policies`); revalidatePath(`/${parsedInput.organizationId}/frameworks`); return { diff --git a/apps/app/src/jobs/tasks/email/publish-all-policies-email.ts b/apps/app/src/jobs/tasks/email/publish-all-policies-email.ts new file mode 100644 index 000000000..ae03d786c --- /dev/null +++ b/apps/app/src/jobs/tasks/email/publish-all-policies-email.ts @@ -0,0 +1,48 @@ +import { sendAllPolicyNotificationEmail } from '@comp/email'; +import { logger, queue, task } from '@trigger.dev/sdk'; + +// Queue with concurrency limit to ensure rate limiting +const allPolicyEmailQueue = queue({ + name: 'all-policy-email-queue', + concurrencyLimit: 2, +}); + +interface AllPolicyEmailPayload { + email: string; + userName: string; + organizationId: string; + organizationName: string; +} + +export const sendPublishAllPoliciesEmail = task({ + id: 'send-publish-all-policies-email', + queue: allPolicyEmailQueue, + run: async (payload: AllPolicyEmailPayload) => { + logger.info('Sending all policies published email', { + email: payload.email, + organizationName: payload.organizationName, + }); + + try { + await sendAllPolicyNotificationEmail(payload); + + logger.info('Successfully sent all policies email', { + email: payload.email, + organizationName: payload.organizationName, + }); + + return { + success: true, + email: payload.email, + }; + } catch (error) { + logger.error('Failed to send all policies email', { + email: payload.email, + error: error instanceof Error ? error.message : String(error), + }); + + throw error; + } + }, +}); + diff --git a/packages/docs/cloud-tests/aws.mdx b/packages/docs/cloud-tests/aws.mdx index dfece9fcf..a4c338c49 100644 --- a/packages/docs/cloud-tests/aws.mdx +++ b/packages/docs/cloud-tests/aws.mdx @@ -32,9 +32,9 @@ Before setting up the integration, ensure you have: - On the permissions screen, click **Attach policies directly** - Attach the following AWS managed policies: - `SecurityAudit` - - `ReadOnlyAccess` + - `AmazonEC2ReadOnlyAccess & AWSSecurityHubReadOnlyAccess` - _(Or use a custom least-privilege policy — see example below)_ + _(Or use a custom least-privilege policy — see example below)_ 6. **Create the User** - Click **Next**, then **Create user** - Copy and securely store the **Access Key ID** and **Secret Access Key** diff --git a/packages/email/emails/all-policy-notification.tsx b/packages/email/emails/all-policy-notification.tsx new file mode 100644 index 000000000..89275b835 --- /dev/null +++ b/packages/email/emails/all-policy-notification.tsx @@ -0,0 +1,118 @@ +import { + Body, + Button, + Container, + Font, + Heading, + Html, + Link, + Preview, + Section, + Tailwind, + Text, +} from '@react-email/components'; +import { Footer } from '../components/footer'; +import { Logo } from '../components/logo'; + +interface Props { + email: string; + userName: string; + organizationName: string; + organizationId: string; +} + +export const AllPolicyNotificationEmail = ({ + email, + userName, + organizationName, + organizationId, +}: Props) => { + const link = `${process.env.NEXT_PUBLIC_PORTAL_URL ?? 'https://portal.trycomp.ai'}/${organizationId}`; + const subjectText = 'Please review and accept the policies'; + + return ( + + + + + + + + + {subjectText} + + + + + + {subjectText} + + + + Hi {userName}, + + + + All policies have been published and require your review. + + + + Your organization {organizationName} requires all employees to review and accept these policies. + + +
+ +
+ + + or copy and paste this URL into your browser{' '} + + {link} + + + +
+
+ + This notification was intended for {email}. + +
+ +
+ +