240 lines
24 KiB
TypeScript
240 lines
24 KiB
TypeScript
import { z } from "zod";
|
||
import { Resend } from "resend";
|
||
import { ask } from "./ask";
|
||
|
||
const resend_key = process.env.RESEND_API_KEY?.trim();
|
||
|
||
if (!resend_key) {
|
||
throw new Error("RESEND_API_KEY is required");
|
||
}
|
||
|
||
const resend = new Resend(resend_key);
|
||
|
||
export const ResendParams = z.object({
|
||
to: z.string().email(),
|
||
subject: z.string(),
|
||
html: z.string(),
|
||
});
|
||
|
||
export type ResendParams = z.infer<typeof ResendParams>;
|
||
|
||
export async function send_email({ to, subject, html }: ResendParams) {
|
||
if (to.includes("example.com")) {
|
||
return {
|
||
error:
|
||
"Invalid email, this is just an example email please find the user's real email using search user id tool",
|
||
};
|
||
}
|
||
|
||
try {
|
||
await resend.emails.send({
|
||
from: "anya@tri.raj.how",
|
||
to,
|
||
subject,
|
||
replyTo: "anya@raj.how",
|
||
html:
|
||
(await formatToHtml({
|
||
to,
|
||
subject,
|
||
html,
|
||
})) ?? html,
|
||
});
|
||
return {
|
||
response: "Email sent",
|
||
};
|
||
} catch (error) {
|
||
return {
|
||
error,
|
||
};
|
||
}
|
||
}
|
||
|
||
const vercel_invite_template = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||
<html dir="ltr" lang="en">
|
||
|
||
<head>
|
||
<link rel="preload" as="image" href="https://react-email-demo-hbzssj3q3-resend.vercel.app/static/vercel-logo.png" />
|
||
<link rel="preload" as="image" href="https://react-email-demo-hbzssj3q3-resend.vercel.app/static/vercel-user.png" />
|
||
<link rel="preload" as="image" href="https://react-email-demo-hbzssj3q3-resend.vercel.app/static/vercel-arrow.png" />
|
||
<link rel="preload" as="image" href="https://react-email-demo-hbzssj3q3-resend.vercel.app/static/vercel-team.png" />
|
||
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
|
||
<meta name="x-apple-disable-message-reformatting" /><!--$-->
|
||
</head>
|
||
<div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0">Join Alan on Vercel<div> </div>
|
||
</div>
|
||
|
||
<body style="background-color:rgb(255,255,255);margin-top:auto;margin-bottom:auto;margin-left:auto;margin-right:auto;font-family:ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";padding-left:0.5rem;padding-right:0.5rem">
|
||
<table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="border-width:1px;border-style:solid;border-color:rgb(234,234,234);border-radius:0.25rem;margin-top:40px;margin-bottom:40px;margin-left:auto;margin-right:auto;padding:20px;max-width:465px">
|
||
<tbody>
|
||
<tr style="width:100%">
|
||
<td>
|
||
<table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="margin-top:32px">
|
||
<tbody>
|
||
<tr>
|
||
<td><img alt="Vercel" height="37" src="https://react-email-demo-hbzssj3q3-resend.vercel.app/static/vercel-logo.png" style="margin-top:0px;margin-bottom:0px;margin-left:auto;margin-right:auto;display:block;outline:none;border:none;text-decoration:none" width="40" /></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<h1 style="color:rgb(0,0,0);font-size:24px;font-weight:400;text-align:center;padding:0px;margin-top:30px;margin-bottom:30px;margin-left:0px;margin-right:0px">Join <strong>Enigma</strong> on <strong>Vercel</strong></h1>
|
||
<p style="color:rgb(0,0,0);font-size:14px;line-height:24px;margin:16px 0">Hello <!-- -->alanturing<!-- -->,</p>
|
||
<p style="color:rgb(0,0,0);font-size:14px;line-height:24px;margin:16px 0"><strong>Alan</strong> (<a href="mailto:alan.turing@example.com" style="color:rgb(37,99,235);text-decoration-line:none;text-decoration:none" target="_blank">alan.turing@example.com</a>) has invited you to the <strong>Enigma</strong> team on<!-- --> <strong>Vercel</strong>.</p>
|
||
<table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation">
|
||
<tbody>
|
||
<tr>
|
||
<td>
|
||
<table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation">
|
||
<tbody style="width:100%">
|
||
<tr style="width:100%">
|
||
<td align="right" data-id="__react-email-column"><img height="64" src="https://react-email-demo-hbzssj3q3-resend.vercel.app/static/vercel-user.png" style="border-radius:9999px;display:block;outline:none;border:none;text-decoration:none" width="64" /></td>
|
||
<td align="center" data-id="__react-email-column"><img alt="invited you to" height="9" src="https://react-email-demo-hbzssj3q3-resend.vercel.app/static/vercel-arrow.png" style="display:block;outline:none;border:none;text-decoration:none" width="12" /></td>
|
||
<td align="left" data-id="__react-email-column"><img height="64" src="https://react-email-demo-hbzssj3q3-resend.vercel.app/static/vercel-team.png" style="border-radius:9999px;display:block;outline:none;border:none;text-decoration:none" width="64" /></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="text-align:center;margin-top:32px;margin-bottom:32px">
|
||
<tbody>
|
||
<tr>
|
||
<td><a href="https://vercel.com/teams/invite/foo" style="background-color:rgb(0,0,0);border-radius:0.25rem;color:rgb(255,255,255);font-size:12px;font-weight:600;text-decoration-line:none;text-align:center;padding-left:1.25rem;padding-right:1.25rem;padding-top:0.75rem;padding-bottom:0.75rem;line-height:100%;text-decoration:none;display:inline-block;max-width:100%;mso-padding-alt:0px;padding:12px 20px 12px 20px" target="_blank"><span><!--[if mso]><i style="mso-font-width:500%;mso-text-raise:18" hidden>  </i><![endif]--></span><span style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:9px">Join the team</span><span><!--[if mso]><i style="mso-font-width:500%" hidden>  ​</i><![endif]--></span></a></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p style="color:rgb(0,0,0);font-size:14px;line-height:24px;margin:16px 0">or copy and paste this URL into your browser:<!-- --> <a href="https://vercel.com/teams/invite/foo" style="color:rgb(37,99,235);text-decoration-line:none;text-decoration:none" target="_blank">https://vercel.com/teams/invite/foo</a></p>
|
||
<hr style="border-width:1px;border-style:solid;border-color:rgb(234,234,234);margin-top:26px;margin-bottom:26px;margin-left:0px;margin-right:0px;width:100%;border:none;border-top:1px solid #eaeaea" />
|
||
<p style="color:rgb(102,102,102);font-size:12px;line-height:24px;margin:16px 0">This invitation was intended for<!-- --> <span style="color:rgb(0,0,0)">alanturing</span>. This invite was sent from <span style="color:rgb(0,0,0)">204.13.186.218</span> <!-- -->located in<!-- --> <span style="color:rgb(0,0,0)">São Paulo, Brazil</span>. If you were not expecting this invitation, you can ignore this email. If you are concerned about your account's safety, please reply to this email to get in touch with us.</p>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table><!--/$-->
|
||
</body>
|
||
|
||
</html>`;
|
||
|
||
const stripe_welcome_template = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||
<html dir="ltr" lang="en">
|
||
|
||
<head>
|
||
<link rel="preload" as="image" href="https://react-email-demo-hbzssj3q3-resend.vercel.app/static/stripe-logo.png" />
|
||
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
|
||
<meta name="x-apple-disable-message-reformatting" /><!--$-->
|
||
</head>
|
||
<div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0">You're now ready to make live transactions with Stripe!<div> </div>
|
||
</div>
|
||
|
||
<body style="background-color:#f6f9fc;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif">
|
||
<table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="max-width:37.5em;background-color:#ffffff;margin:0 auto;padding:20px 0 48px;margin-bottom:64px">
|
||
<tbody>
|
||
<tr style="width:100%">
|
||
<td>
|
||
<table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="padding:0 48px">
|
||
<tbody>
|
||
<tr>
|
||
<td><img alt="Stripe" height="21" src="https://react-email-demo-hbzssj3q3-resend.vercel.app/static/stripe-logo.png" style="display:block;outline:none;border:none;text-decoration:none" width="49" />
|
||
<hr style="width:100%;border:none;border-top:1px solid #eaeaea;border-color:#e6ebf1;margin:20px 0" />
|
||
<p style="font-size:16px;line-height:24px;margin:16px 0;color:#525f7f;text-align:left">Thanks for submitting your account information. You're now ready to make live transactions with Stripe!</p>
|
||
<p style="font-size:16px;line-height:24px;margin:16px 0;color:#525f7f;text-align:left">You can view your payments and a variety of other information about your account right from your dashboard.</p><a href="https://dashboard.stripe.com/login" style="line-height:100%;text-decoration:none;display:block;max-width:100%;mso-padding-alt:0px;background-color:#656ee8;border-radius:5px;color:#fff;font-size:16px;font-weight:bold;text-align:center;width:100%;padding:10px 10px 10px 10px" target="_blank"><span><!--[if mso]><i style="mso-font-width:500%;mso-text-raise:15" hidden> </i><![endif]--></span><span style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:7.5px">View your Stripe Dashboard</span><span><!--[if mso]><i style="mso-font-width:500%" hidden> ​</i><![endif]--></span></a>
|
||
<hr style="width:100%;border:none;border-top:1px solid #eaeaea;border-color:#e6ebf1;margin:20px 0" />
|
||
<p style="font-size:16px;line-height:24px;margin:16px 0;color:#525f7f;text-align:left">If you haven't finished your integration, you might find our<!-- --> <a href="https://stripe.com/docs" style="color:#556cd6;text-decoration:none" target="_blank">docs</a> <!-- -->handy.</p>
|
||
<p style="font-size:16px;line-height:24px;margin:16px 0;color:#525f7f;text-align:left">Once you're ready to start accepting payments, you'll just need to use your live<!-- --> <a href="https://dashboard.stripe.com/login?redirect=%2Fapikeys" style="color:#556cd6;text-decoration:none" target="_blank">API keys</a> <!-- -->instead of your test API keys. Your account can simultaneously be used for both test and live requests, so you can continue testing while accepting live payments. Check out our<!-- --> <a href="https://stripe.com/docs/dashboard" style="color:#556cd6;text-decoration:none" target="_blank">tutorial about account basics</a>.</p>
|
||
<p style="font-size:16px;line-height:24px;margin:16px 0;color:#525f7f;text-align:left">Finally, we've put together a<!-- --> <a href="https://stripe.com/docs/checklist/website" style="color:#556cd6;text-decoration:none" target="_blank">quick checklist</a> <!-- -->to ensure your website conforms to card network standards.</p>
|
||
<p style="font-size:16px;line-height:24px;margin:16px 0;color:#525f7f;text-align:left">We'll be here to help you with any step along the way. You can find answers to most questions and get in touch with us on our<!-- --> <a href="https://support.stripe.com/" style="color:#556cd6;text-decoration:none" target="_blank">support site</a>.</p>
|
||
<p style="font-size:16px;line-height:24px;margin:16px 0;color:#525f7f;text-align:left">— The Stripe team</p>
|
||
<hr style="width:100%;border:none;border-top:1px solid #eaeaea;border-color:#e6ebf1;margin:20px 0" />
|
||
<p style="font-size:12px;line-height:16px;margin:16px 0;color:#8898aa">Stripe, 354 Oyster Point Blvd, South San Francisco, CA 94080</p>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table><!--/$-->
|
||
</body>
|
||
|
||
</html>`;
|
||
|
||
const linear_login_code_template = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||
<html dir="ltr" lang="en">
|
||
|
||
<head>
|
||
<link rel="preload" as="image" href="https://react-email-demo-hbzssj3q3-resend.vercel.app/static/linear-logo.png" />
|
||
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
|
||
<meta name="x-apple-disable-message-reformatting" /><!--$-->
|
||
</head>
|
||
<div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0">Your login code for Linear<div> </div>
|
||
</div>
|
||
|
||
<body style="background-color:#ffffff;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif">
|
||
<table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="max-width:560px;margin:0 auto;padding:20px 0 48px">
|
||
<tbody>
|
||
<tr style="width:100%">
|
||
<td><img alt="Linear" height="42" src="https://react-email-demo-hbzssj3q3-resend.vercel.app/static/linear-logo.png" style="display:block;outline:none;border:none;text-decoration:none;border-radius:21px;width:42px;height:42px" width="42" />
|
||
<h1 style="font-size:24px;letter-spacing:-0.5px;line-height:1.3;font-weight:400;color:#484848;padding:17px 0 0">Your login code for Linear</h1>
|
||
<table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="padding:27px 0 27px">
|
||
<tbody>
|
||
<tr>
|
||
<td><a href="https://linear.app" style="line-height:100%;text-decoration:none;display:block;max-width:100%;mso-padding-alt:0px;background-color:#5e6ad2;border-radius:3px;font-weight:600;color:#fff;font-size:15px;text-align:center;padding:11px 23px 11px 23px" target="_blank"><span><!--[if mso]><i style="mso-font-width:383.33333333333337%;mso-text-raise:16.5" hidden>   </i><![endif]--></span><span style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:8.25px">Login to Linear</span><span><!--[if mso]><i style="mso-font-width:383.33333333333337%" hidden>   ​</i><![endif]--></span></a></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p style="font-size:15px;line-height:1.4;margin:0 0 15px;color:#3c4149">This link and code will only be valid for the next 5 minutes. If the link does not work, you can use the login verification code directly:</p><code style="font-family:monospace;font-weight:700;padding:1px 4px;background-color:#dfe1e4;letter-spacing:-0.3px;font-size:21px;border-radius:4px;color:#3c4149">tt226-5398x</code>
|
||
<hr style="width:100%;border:none;border-top:1px solid #eaeaea;border-color:#dfe1e4;margin:42px 0 26px" /><a href="https://linear.app" style="color:#b4becc;text-decoration:none;font-size:14px" target="_blank">Linear</a>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table><!--/$-->
|
||
</body>
|
||
|
||
</html>`;
|
||
|
||
// use ask function to take some data and pick a relavent template and put the data in it and return the final html string
|
||
async function formatToHtml({
|
||
to,
|
||
subject,
|
||
html,
|
||
}: {
|
||
to: string;
|
||
subject: string;
|
||
html: string;
|
||
}) {
|
||
const response = await ask({
|
||
model: "gpt-4o-mini",
|
||
prompt: `Given some subject and html content
|
||
|
||
To: ${to}
|
||
Subject: ${subject}
|
||
HTML: ${html}
|
||
|
||
Example Templates to Pick from:
|
||
1. Vercel Invite Template:
|
||
${vercel_invite_template}
|
||
|
||
2. Stripe Welcome Template (use this for most simple messages):
|
||
${stripe_welcome_template}
|
||
|
||
3. Linear Login Code Template:
|
||
${linear_login_code_template}
|
||
|
||
Pick a template and put the data in it to create the final HTML string.
|
||
Do not Make up false data, use only the given data.
|
||
Make sure to replace all the example data in the template with data relavent to the context.
|
||
|
||
RETURN ONLY HTML STRING
|
||
`,
|
||
});
|
||
return response.choices[0].message.content
|
||
? extractHtmlString(response.choices[0].message.content)
|
||
: null;
|
||
}
|
||
|
||
function extractHtmlString(response: string): string {
|
||
// Use a regular expression to match the HTML string within the response
|
||
const htmlMatch = response.match(/<html[^>]*>([\s\S]*?)<\/html>/i);
|
||
|
||
// Return the matched HTML content or an empty string if no match is found
|
||
return htmlMatch ? htmlMatch[0] : "";
|
||
}
|