Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
648a06b
fix(integration): update task status logic to account for failed chec…
github-actions[bot] Jan 23, 2026
a8068c7
[dev] [tofikwest] tofik/auditor-training-timestamps (#2046)
github-actions[bot] Jan 23, 2026
c95c4a0
[dev] [tofikwest] tofik/fix-onboard-organization-risk-category-valida…
github-actions[bot] Jan 23, 2026
0dd53bb
[dev] [tofikwest] tofik/auditor-evidence-feedback (#2048)
github-actions[bot] Jan 24, 2026
31bc3a7
feat(tasks): add TaskAutomationStatusBadge component to display task …
github-actions[bot] Jan 24, 2026
86843f8
[FEAT] Upload images for failed device policy - BUGFIX (#2039)
github-actions[bot] Jan 26, 2026
7fc77b6
fix(portal): convert ReadonlyHeaders to a plain object and pass it to…
github-actions[bot] Jan 26, 2026
11d3837
[dev] [tofikwest] tofik/automation-status-badge-logic (#2050)
github-actions[bot] Jan 26, 2026
8cd9ea2
[dev] [tofikwest] tofik/add-findinds-logic (#2052)
github-actions[bot] Jan 27, 2026
f97258b
feat(tasks): add bulk task status and assignee update functionality w…
github-actions[bot] Jan 27, 2026
67a8edc
[dev] [tofikwest] tofik/auditor-allow-invite (#2055)
github-actions[bot] Jan 27, 2026
b0b3382
feat(integrations): enhance task integration logic with example promp…
github-actions[bot] Jan 27, 2026
8624d8d
[dev] [tofikwest] tofik/deep-link-support (#2057)
github-actions[bot] Jan 27, 2026
3e447db
[dev] [tofikwest] tofik/fix-auth-increase-invite-time (#2060)
github-actions[bot] Jan 27, 2026
4a9264b
refactor(policies): adjust chart dimensions and improve layout for as…
github-actions[bot] Jan 27, 2026
5dbf5ba
feat(integrations): add validation for target repositories in integra…
github-actions[bot] Jan 27, 2026
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
7 changes: 6 additions & 1 deletion apps/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,9 @@ TRIGGER_SECRET_KEY=

OPENAI_API_KEY=
ANTHROPIC_API_KEY=
GROQ_API_KEY=
GROQ_API_KEY=

# Resend (for sending emails)
RESEND_API_KEY=
RESEND_FROM_SYSTEM= # e.g., noreply@mail.trycomp.ai
RESEND_FROM_DEFAULT= # e.g., hello@mail.trycomp.ai
1 change: 1 addition & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"reflect-metadata": "^0.2.2",
"resend": "^6.4.2",
"rxjs": "^7.8.1",
"safe-stable-stringify": "^2.5.0",
"swagger-ui-express": "^5.0.1",
"xlsx": "^0.18.5",
"zod": "^4.0.14"
Expand Down
8 changes: 8 additions & 0 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ import { OrganizationModule } from './organization/organization.module';
import { PoliciesModule } from './policies/policies.module';
import { RisksModule } from './risks/risks.module';
import { TasksModule } from './tasks/tasks.module';
import { EvidenceExportModule } from './tasks/evidence-export/evidence-export.module';
import { VendorsModule } from './vendors/vendors.module';
import { ContextModule } from './context/context.module';
import { TrustPortalModule } from './trust-portal/trust-portal.module';
import { TaskTemplateModule } from './framework-editor/task-template/task-template.module';
import { FindingTemplateModule } from './finding-template/finding-template.module';
import { FindingsModule } from './findings/findings.module';
import { QuestionnaireModule } from './questionnaire/questionnaire.module';
import { VectorStoreModule } from './vector-store/vector-store.module';
import { KnowledgeBaseModule } from './knowledge-base/knowledge-base.module';
Expand All @@ -30,6 +33,7 @@ import { CloudSecurityModule } from './cloud-security/cloud-security.module';
import { BrowserbaseModule } from './browserbase/browserbase.module';
import { TaskManagementModule } from './task-management/task-management.module';
import { AssistantChatModule } from './assistant-chat/assistant-chat.module';
import { TrainingModule } from './training/training.module';

@Module({
imports: [
Expand Down Expand Up @@ -59,10 +63,13 @@ import { AssistantChatModule } from './assistant-chat/assistant-chat.module';
DeviceAgentModule,
AttachmentsModule,
TasksModule,
EvidenceExportModule,
CommentsModule,
HealthModule,
TrustPortalModule,
TaskTemplateModule,
FindingTemplateModule,
FindingsModule,
QuestionnaireModule,
VectorStoreModule,
KnowledgeBaseModule,
Expand All @@ -72,6 +79,7 @@ import { AssistantChatModule } from './assistant-chat/assistant-chat.module';
BrowserbaseModule,
TaskManagementModule,
AssistantChatModule,
TrainingModule,
],
controllers: [AppController],
providers: [
Expand Down
13 changes: 13 additions & 0 deletions apps/api/src/email/resend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ export const resend = process.env.RESEND_API_KEY
? new Resend(process.env.RESEND_API_KEY)
: null;

export interface EmailAttachment {
filename: string;
content: Buffer | string;
contentType?: string;
}

export const sendEmail = async ({
to,
subject,
Expand All @@ -14,6 +20,7 @@ export const sendEmail = async ({
test,
cc,
scheduledAt,
attachments,
}: {
to: string;
subject: string;
Expand All @@ -23,6 +30,7 @@ export const sendEmail = async ({
test?: boolean;
cc?: string | string[];
scheduledAt?: string;
attachments?: EmailAttachment[];
}) => {
if (!resend) {
throw new Error('Resend not initialized - missing API key');
Expand Down Expand Up @@ -64,6 +72,11 @@ export const sendEmail = async ({
// @ts-ignore – React node allowed by the SDK
react,
scheduledAt,
attachments: attachments?.map((att) => ({
filename: att.filename,
content: att.content,
contentType: att.contentType,
})),
});

if (error) {
Expand Down
147 changes: 147 additions & 0 deletions apps/api/src/email/templates/finding-notification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import * as React from 'react';
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';
import { getUnsubscribeUrl } from '@trycompai/email';

interface Props {
toName: string;
toEmail: string;
heading: string;
message: string;
taskTitle: string;
organizationName: string;
findingType: string;
findingContent: string;
newStatus?: string;
findingUrl: string;
}

export const FindingNotificationEmail = ({
toName,
toEmail,
heading,
message,
taskTitle,
organizationName,
findingType,
findingContent,
newStatus,
findingUrl,
}: Props) => {
const unsubscribeUrl = getUnsubscribeUrl(toEmail);
return (
<Html>
<Tailwind>
<head>
<Font
fontFamily="Geist"
fallbackFontFamily="Helvetica"
fontWeight={400}
fontStyle="normal"
/>
<Font
fontFamily="Geist"
fallbackFontFamily="Helvetica"
fontWeight={500}
fontStyle="normal"
/>
</head>
<Preview>
{heading}: {taskTitle}
</Preview>

<Body className="mx-auto my-auto bg-[#fff] font-sans">
<Container
className="mx-auto my-[40px] max-w-[600px] border-transparent p-[20px] md:border-[#E8E7E1]"
style={{ borderStyle: 'solid', borderWidth: 1 }}
>
<Logo />
<Heading className="mx-0 my-[30px] p-0 text-center text-[24px] font-normal text-[#121212]">
{heading}
</Heading>

<Text className="text-[14px] leading-[24px] text-[#121212]">
Hello {toName},
</Text>

<Text className="text-[14px] leading-[24px] text-[#121212]">
{message}
</Text>

{/* Finding Details Box */}
<Section
className="mt-[24px] mb-[24px] rounded-[8px] bg-[#f5f5f5] p-[16px]"
style={{ border: '1px solid #e0e0e0' }}
>
<Text className="m-0 text-[12px] font-medium uppercase tracking-wide text-[#666666]">
Finding Details
</Text>

<Text className="mt-[8px] mb-[4px] text-[14px] font-medium text-[#121212]">
Organization: {organizationName}
</Text>

<Text className="mt-[4px] mb-[4px] text-[14px] font-medium text-[#121212]">
Task: {taskTitle}
</Text>

<Text className="mt-[4px] mb-[4px] text-[13px] text-[#666666]">
Type: {findingType}
{newStatus && <> | Status: {newStatus}</>}
</Text>

<Text className="mt-[12px] mb-0 text-[13px] italic text-[#444444]">
"{findingContent}"
</Text>
</Section>

<Section className="mt-[32px] mb-[32px] text-center">
<Button
className="rounded-[3px] bg-[#121212] px-[20px] py-[12px] text-center text-[14px] font-semibold text-white no-underline"
href={findingUrl}
>
View Finding
</Button>
</Section>

<Text className="text-[14px] leading-[24px] text-[#121212]">
or copy and paste this URL into your browser:{' '}
<a href={findingUrl} className="text-[#121212] underline">
{findingUrl}
</a>
</Text>

<Section className="mt-[30px] mb-[20px]">
<Text className="text-[12px] leading-[20px] text-[#666666]">
Don't want to receive finding notifications?{' '}
<Link href={unsubscribeUrl} className="text-[#121212] underline">
Manage your email preferences
</Link>
.
</Text>
</Section>

<br />

<Footer />
</Container>
</Body>
</Tailwind>
</Html>
);
};

export default FindingNotificationEmail;
119 changes: 119 additions & 0 deletions apps/api/src/email/templates/task-assignee-changed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import * as React from 'react';
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';
import { getUnsubscribeUrl } from '@trycompai/email';

interface Props {
toName: string;
toEmail: string;
taskTitle: string;
oldAssigneeName: string;
newAssigneeName: string;
changedByName: string;
organizationName: string;
taskUrl: string;
}

export const TaskAssigneeChangedEmail = ({
toName,
toEmail,
taskTitle,
oldAssigneeName,
newAssigneeName,
changedByName,
organizationName,
taskUrl,
}: Props) => {
const unsubscribeUrl = getUnsubscribeUrl(toEmail);

return (
<Html>
<Tailwind>
<head>
<Font
fontFamily="Geist"
fallbackFontFamily="Helvetica"
fontWeight={400}
fontStyle="normal"
/>
<Font
fontFamily="Geist"
fallbackFontFamily="Helvetica"
fontWeight={500}
fontStyle="normal"
/>
</head>
<Preview>
{`Task "${taskTitle}" reassigned from ${oldAssigneeName} to ${newAssigneeName}`}
</Preview>

<Body className="mx-auto my-auto bg-[#fff] font-sans">
<Container
className="mx-auto my-[40px] max-w-[600px] border-transparent p-[20px] md:border-[#E8E7E1]"
style={{ borderStyle: 'solid', borderWidth: 1 }}
>
<Logo />
<Heading className="mx-0 my-[30px] p-0 text-center text-[24px] font-normal text-[#121212]">
Task Reassigned
</Heading>

<Text className="text-[14px] leading-[24px] text-[#121212]">
Hello {toName},
</Text>

<Text className="text-[14px] leading-[24px] text-[#121212]">
<strong>{changedByName}</strong> reassigned task <strong>"{taskTitle}"</strong> from{' '}
<strong>{oldAssigneeName}</strong> to <strong>{newAssigneeName}</strong> in{' '}
<strong>{organizationName}</strong>.
</Text>

<Section className="mt-[32px] mb-[32px] text-center">
<Button
className="rounded-[3px] bg-[#121212] px-[20px] py-[12px] text-center text-[14px] font-semibold text-white no-underline"
href={taskUrl}
>
View Task
</Button>
</Section>

<Text className="text-[14px] leading-[24px] text-[#121212]">
or copy and paste this URL into your browser:{' '}
<a href={taskUrl} className="text-[#121212] underline">
{taskUrl}
</a>
</Text>

<Section className="mt-[30px] mb-[20px]">
<Text className="text-[12px] leading-[20px] text-[#666666]">
Don't want to receive task assignment notifications?{' '}
<Link href={unsubscribeUrl} className="text-[#121212] underline">
Manage your email preferences
</Link>
.
</Text>
</Section>

<br />

<Footer />
</Container>
</Body>
</Tailwind>
</Html>
);
};

export default TaskAssigneeChangedEmail;
Loading
Loading