fix: emails are not sent on welcome

This commit is contained in:
Harshith Mullapudi 2025-08-27 22:44:32 +05:30
parent 26a2d04ca9
commit d062df14aa
14 changed files with 166 additions and 101 deletions

View File

@ -22,7 +22,7 @@ export function SpacePatternCard({ pattern }: SpacePatternCardProps) {
actionType,
patternId: pattern.id,
},
{ method: "POST" }
{ method: "POST" },
);
setDialog(false);
};
@ -77,19 +77,19 @@ export function SpacePatternCard({ pattern }: SpacePatternCardProps) {
<div className="flex justify-end">
<div className="flex gap-2">
<Button
variant="ghost"
<Button
variant="ghost"
onClick={() => handleAction("delete")}
disabled={fetcher.state === "submitting"}
>
Delete
</Button>
<Button
variant="secondary"
<Button
variant="secondary"
onClick={() => handleAction("add")}
disabled={fetcher.state === "submitting"}
>
Add
Add to memory
</Button>
</div>
</div>

View File

@ -1,5 +1,6 @@
import { type Workspace } from "@core/database";
import { prisma } from "~/db.server";
import { sendEmail } from "~/services/email.server";
import { SpaceService } from "~/services/space.server";
interface CreateWorkspaceDto {
@ -31,7 +32,7 @@ export async function createWorkspace(
},
});
await prisma.user.update({
const user = await prisma.user.update({
where: { id: input.userId },
data: {
confirmedBasicDetails: true,
@ -45,6 +46,8 @@ export async function createWorkspace(
workspaceId: workspace.id,
});
await sendEmail({ email: "welcome", to: user.email });
return workspace;
}

View File

@ -1,5 +1,8 @@
import { z } from "zod";
import { createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server";
import {
createActionApiRoute,
createHybridActionApiRoute,
} from "~/services/routeBuilders/apiBuilder.server";
import { SearchService } from "~/services/search.server";
import { json } from "@remix-run/node";
@ -19,7 +22,7 @@ export const SearchBodyRequest = z.object({
});
const searchService = new SearchService();
const { action, loader } = createActionApiRoute(
const { action, loader } = createHybridActionApiRoute(
{
body: SearchBodyRequest,
allowJWT: true,

View File

@ -1,5 +1,5 @@
import { z } from "zod";
import { useActionData, useLoaderData } from "@remix-run/react";
import { useActionData } from "@remix-run/react";
import {
type ActionFunctionArgs,
json,
@ -17,12 +17,7 @@ import {
} from "~/components/ui/card";
import { Button } from "~/components/ui";
import { Input } from "~/components/ui/input";
import { useState } from "react";
import {
requireUser,
requireUserId,
requireWorkpace,
} from "~/services/session.server";
import { requireUser, requireUserId } from "~/services/session.server";
import { redirectWithSuccessMessage } from "~/models/message.server";
import { rootPath } from "~/utils/pathBuilder";
import { createWorkspace, getWorkspaceByUser } from "~/models/workspace.server";
@ -76,7 +71,6 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
export default function ConfirmBasicDetails() {
const lastSubmission = useActionData<typeof action>();
const { workspace } = useLoaderData<typeof loader>();
const [form, fields] = useForm({
lastSubmission: lastSubmission as any,

View File

@ -16,8 +16,8 @@ const client = singleton(
new EmailClient({
transport: buildTransportOptions(),
imagesBaseUrl: env.APP_ORIGIN,
from: env.FROM_EMAIL ?? "Harshith <harshith@tegon.ai>",
replyTo: env.REPLY_TO_EMAIL ?? "harshith@tegon.ai",
from: env.FROM_EMAIL ?? "Manik <manik@poozle.dev>",
replyTo: env.REPLY_TO_EMAIL ?? "manik@poozle.dev",
}),
);
@ -85,5 +85,9 @@ export async function scheduleEmail(
) {}
export async function sendEmail(data: DeliverEmail) {
return client.send(data);
try {
return client.send(data);
} catch (e) {
logger.error(`Error: ${e}`);
}
}

View File

@ -6,6 +6,7 @@ import { MCP } from "../utils/mcp";
import { type HistoryStep } from "../utils/types";
import {
createConversationHistoryForAgent,
deletePersonalAccessToken,
getCreditsForUser,
getPreviousExecutionHistory,
init,
@ -120,9 +121,15 @@ export const chat = task({
);
usageCredits && (await updateUserCredits(usageCredits, 1));
if (init?.tokenId) {
await deletePersonalAccessToken(init.tokenId);
}
} catch (e) {
await updateConversationStatus("failed", payload.conversationId);
if (init?.tokenId) {
await deletePersonalAccessToken(init.tokenId);
}
throw new Error(e as string);
}
},

View File

@ -4,8 +4,12 @@ import { z } from "zod";
import { openai } from "@ai-sdk/openai";
import { logger } from "~/services/logger.service";
import { getOrCreatePersonalAccessToken } from "../utils/utils";
import {
deletePersonalAccessToken,
getOrCreatePersonalAccessToken,
} from "../utils/utils";
import axios from "axios";
import { nanoid } from "nanoid";
export const ExtensionSearchBodyRequest = z.object({
userInput: z.string().min(1, "User input is required"),
@ -24,8 +28,10 @@ export const extensionSearch = task({
const { userInput, userId, context } =
ExtensionSearchBodyRequest.parse(body);
const randomKeyName = `extensionSearch_${nanoid(10)}`;
const pat = await getOrCreatePersonalAccessToken({
name: "extensionSearch",
name: randomKeyName,
userId: userId as string,
});
@ -106,8 +112,12 @@ If no relevant information is found, provide a brief statement indicating that.`
finalText = finalText + chunk;
}
await deletePersonalAccessToken(pat?.id);
return finalText;
} catch (error) {
await deletePersonalAccessToken(pat?.id);
logger.error(`SearchMemoryAgent error: ${error}`);
return `Context related to: ${userInput}. Looking for relevant background information, previous discussions, and related concepts that would help provide a comprehensive answer.`;

View File

@ -170,7 +170,7 @@ export const init = async ({ payload }: { payload: InitChatPayload }) => {
return { conversation, conversationHistory };
}
const randomKeyName = `chat`;
const randomKeyName = `chat_${nanoid(10)}`;
const pat = await getOrCreatePersonalAccessToken({
name: randomKeyName,
userId: workspace.userId as string,

View File

@ -1,16 +1,16 @@
import { Hr, Link, Text } from "@react-email/components";
import { Hr, Text } from "@react-email/components";
import React from "react";
import { footer, footerAnchor, hr } from "./styles";
import { footer, hr, paragraphLight } from "./styles";
export function Footer() {
return (
<>
<Hr style={hr} />
<Text style={paragraphLight}>happy building your digital brain!</Text>
<Text style={footer}>
©Sol.ai
<Link style={footerAnchor} href="https://core.heysol.ai/">
C.O.R.E
</Link>
the Core team P.S Questions?
<br />
Just hit reply - we're here to help.
</Text>
</>
);

View File

@ -8,10 +8,7 @@ export const h1 = {
padding: "0",
};
export const main = {
backgroundColor: "#15171A",
padding: "0 20px",
};
export const main = {};
export const container = {
backgroundColor: "#15171A",
@ -30,29 +27,42 @@ export const hr = {
};
export const paragraph = {
color: "#878C99",
color: "black",
fontFamily:
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
fontSize: "16px",
fontSize: "14px",
lineHeight: "24px",
textAlign: "left" as const,
};
export const paragraphLight = {
color: "#D7D9DD",
color: "black",
fontFamily:
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
fontSize: "16px",
fontSize: "14px",
lineHeight: "24px",
textAlign: "left" as const,
margin: 0,
};
export const heading = {
color: "black",
fontFamily:
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
fontSize: "14px",
lineHeight: "24px",
textAlign: "left" as const,
fontWeight: "bold",
margin: 0,
marginTop: "20px",
};
export const paragraphTight = {
color: "#D7D9DD",
color: "black",
fontFamily:
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
fontSize: "16px",
lineHeight: "16px",
fontSize: "14px",
lineHeight: "14px",
textAlign: "left" as const,
};
@ -60,18 +70,19 @@ export const bullets = {
color: "#D7D9DD",
fontFamily:
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
fontSize: "16px",
fontSize: "14px",
lineHeight: "24px",
textAlign: "left" as const,
margin: "0",
};
export const anchor = {
color: "#826DFF",
marginRight: "3px",
fontFamily:
"-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
fontSize: "16px",
fontSize: "14px",
textDecoration: "underline",
color: "black",
};
export const button = {
@ -80,7 +91,7 @@ export const button = {
color: "#D7D9DD",
fontFamily:
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
fontSize: "16px",
fontSize: "14px",
fontWeight: "bold",
textDecoration: "none",
textAlign: "center" as const,
@ -92,7 +103,7 @@ export const footer = {
fontFamily:
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
fontSize: "12px",
lineHeight: "16px",
lineHeight: "14px",
};
export const footerItalic = {
@ -101,7 +112,7 @@ export const footerItalic = {
fontFamily:
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
fontSize: "12px",
lineHeight: "16px",
lineHeight: "14px",
};
export const footerAnchor = {

View File

@ -1,59 +1,86 @@
import { Body, Head, Html, Link, Preview, Section, Text } from "@react-email/components";
import { Body, Head, Html, Img, Link, Preview, Text } from "@react-email/components";
import { Footer } from "./components/Footer";
import { anchor, bullets, footerItalic, main, paragraphLight } from "./components/styles";
import { anchor, heading, main, paragraphLight } from "./components/styles";
import { z } from "zod";
export const WelcomeEmailSchema = z.object({
email: z.literal("welcome"),
orgName: z.string(),
inviterName: z.string().optional(),
inviterEmail: z.string(),
inviteLink: z.string().url(),
});
export function WelcomeEmail({ orgName }: { orgName?: string }) {
export default function WelcomeEmail() {
return (
<Html>
<Head />
<Preview>Welcome to C.O.R.E. - Your Personal AI Assistant</Preview>
<Preview>building your digital brain</Preview>
<Body style={main}>
<Text style={paragraphLight}>Hey {orgName ?? "there"},</Text>
<Text style={paragraphLight}>Welcome to C.O.R.E., your new personal AI assistant!</Text>
<Text style={paragraphLight}>
I'm excited to help you streamline your daily tasks, boost your productivity, and make
your work life easier. C.O.R.E. is designed to be intuitive and powerful, adapting to your
unique needs and preferences.
</Text>
<Text style={paragraphLight}>
To get started, you can{" "}
<Link style={anchor} href="https://core.heysol.ai/home">
visit your dashboard
</Link>{" "}
where you'll find all the features and capabilities at your disposal. Whether it's
managing your schedule, handling communications, or automating repetitive tasks, I'm here
to help.
</Text>
<Text style={paragraphLight}>
If you have any questions or need assistance, don't hesitate to reach out. You can:{"\n"}
Ask me directly through the chat interface{"\n"}{" "}
<Link style={anchor} href="https://core.heysol.ai/support">
Visit our support center
<Text style={paragraphLight}>hi there,</Text>
<Text
style={{
...paragraphLight,
marginTop: "10px",
}}
>
<Link style={anchor} href="https://x.com/manikagg01">
Manik
</Link>
{"\n"} Join our{" "}
<Link style={anchor} href="https://discord.gg/heysol">
Discord community
</Link>{" "}
to connect with other users and our team
from core here. welcome to core. when i first tried core memory, two actions made it click
for me. each came down to the same thing: understanding how I can add relevant context
about everything that matters to me in core memory and recall it wherever I want.
</Text>
<Text style={heading}>core mcp</Text>
<Text style={paragraphLight}>
seamlessly add your code context from cursor/claude-code, project context from linear, or
brainstorming sessions from claude desktop via mcp. solve context loss problems across ai
tools with persistent, cross-session memory. add this url and get started
</Text>
<Link
style={{
...anchor,
marginTop: "10px",
marginBottom: "10px",
}}
>
https://core.heysol.ai/api/v1/mcp?source='Your Coding Agent'
</Link>
<Img
alt="Claude"
style={{
marginLeft: "auto",
marginRight: "auto",
width: "100%",
borderRadius: "2%",
marginTop: "10px",
}}
src="https://integrations.heysol.ai/core-claude.gif"
/>
<Text style={heading}>browser extension</Text>
<Text style={paragraphLight}>
recall relevant context from core memory in chatgpt, grok, and gemini. save conversations
and content from chatgpt, grok, gemini, twitter, youtube, blog posts, and any webpage
directly into your Core memory with simple text selection.
</Text>
<Img
alt="Claude"
style={{
marginLeft: "auto",
marginRight: "auto",
width: "100%",
borderRadius: "2%",
marginTop: "10px",
}}
src="https://integrations.heysol.ai/core-extension.gif"
/>
<Text style={paragraphLight}>Looking forward to being your trusted assistant!</Text>
<Text style={bullets}>Best regards,</Text>
<Text style={bullets}>C.O.R.E.</Text>
<Text style={paragraphLight}>Your AI Assistant</Text>
<Text style={footerItalic}>
You can customize your notification preferences anytime in your account settings.
<Text style={heading}>need real-time, human help to get started? </Text>
<Text style={paragraphLight}>
- join our discord community & get direct help from our team + over 100+ enthusiasts using
Core memory
</Text>
<Text style={paragraphLight}>
- We are open-source us on our repo -{" "}
<Link style={anchor} href="https://github.com/RedPlanetHQ/core">
https://github.com/RedPlanetHQ/core
</Link>
</Text>
<Footer />
</Body>

View File

@ -4,7 +4,7 @@ import { z } from "zod";
import { setGlobalBasePath } from "../emails/components/BasePath";
import { WelcomeEmail, WelcomeEmailSchema } from "../emails/welcome";
import WelcomeEmail, { WelcomeEmailSchema } from "../emails/welcome";
import { constructMailTransport, MailTransport, MailTransportOptions } from "./transports";
import MagicLinkEmail from "../emails/magic-link";
@ -73,14 +73,14 @@ export class EmailClient {
switch (data.email) {
case "magic_link":
return {
subject: "Magic sign-in link for Trigger.dev",
subject: "Magic sign-in link for Core",
component: <MagicLinkEmail magicLink={data.magicLink} />,
};
case "welcome":
return {
subject: `You've been invited to join ${data.orgName} on C.O.R.E.`,
component: <WelcomeEmail {...data} />,
subject: `You've been invited to join on C.O.R.E.`,
component: <WelcomeEmail />,
};
}
}

View File

@ -2,20 +2,20 @@ import { EmailError, MailMessage, MailTransport, PlainTextMailMessage } from "./
import { Resend } from "resend";
export type ResendMailTransportOptions = {
type: 'resend',
type: "resend";
config: {
apiKey?: string
}
}
apiKey?: string;
};
};
export class ResendMailTransport implements MailTransport {
#client: Resend;
constructor(options: ResendMailTransportOptions) {
this.#client = new Resend(options.config.apiKey)
this.#client = new Resend(options.config.apiKey);
}
async send({to, from, replyTo, subject, react}: MailMessage): Promise<void> {
async send({ to, from, replyTo, subject, react }: MailMessage): Promise<void> {
const result = await this.#client.emails.send({
from: from,
to,
@ -25,6 +25,7 @@ export class ResendMailTransport implements MailTransport {
});
if (result.error) {
console.log(result);
console.error(
`Failed to send email to ${to}, ${subject}. Error ${result.error.name}: ${result.error.message}`
);
@ -32,7 +33,7 @@ export class ResendMailTransport implements MailTransport {
}
}
async sendPlainText({to, from, replyTo, subject, text}: PlainTextMailMessage): Promise<void> {
async sendPlainText({ to, from, replyTo, subject, text }: PlainTextMailMessage): Promise<void> {
const result = await this.#client.emails.send({
from: from,
to,

View File

@ -74,6 +74,11 @@
"TRIGGER_API_URL",
"TRIGGER_SECRET_KEY",
"EMBEDDING_MODEL",
"MODEL"
"MODEL",
"COHERE_API_KEY",
"RESEND_API_KEY",
"FROM_EMAIL",
"REPLY_TO_EMAIL",
"EMAIL_TRANSPORT"
]
}