TypeScript 97.5%
SQL 1.4%
Python 0.8%
1import * as React from "react";2import { Body, Button, Container, Head, Heading, Hr, Html, Link, Preview, Section, Text } from "@react-email/components";34const colors = {5 bg: "#f6f7f9",6 card: "#ffffff",7 text: "#0b0f19",8 muted: "#5b6472",9 accent: "#0f62fe",10 border: "#e5e8ec",11};1213const font = "-apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";1415function Layout({ preview, children, siteUrl }: { preview: string; children: React.ReactNode; siteUrl: string }) {16 return (17 <Html lang="en">18 <Head />19 <Preview>{preview}</Preview>20 <Body style={{ backgroundColor: colors.bg, fontFamily: font, margin: 0, padding: "32px 0" }}>21 <Container style={{ maxWidth: 560, margin: "0 auto", backgroundColor: colors.card, border: `1px solid ${colors.border}`, borderRadius: 10, padding: "36px 40px" }}>22 <Section style={{ marginBottom: 24 }}>23 <Link href={siteUrl} style={{ textDecoration: "none", color: colors.text, fontWeight: 700, fontSize: 18, letterSpacing: "-0.02em" }}>24 <span style={{ display: "inline-block", width: 10, height: 10, borderRadius: 2, backgroundColor: colors.accent, marginRight: 8, verticalAlign: "middle" }} />25 fetcha26 </Link>27 </Section>28 {children}29 <Hr style={{ borderColor: colors.border, margin: "32px 0 16px" }} />30 <Text style={{ color: colors.muted, fontSize: 12, lineHeight: "18px", margin: 0 }}>31 Fetcha — Intelligent Web Access Infrastructure. You received this email because an account on{" "}32 <Link href={siteUrl} style={{ color: colors.muted }}>33 fetcha.co34 </Link>{" "}35 uses this address. Questions? <Link href="mailto:support@fetcha.co" style={{ color: colors.muted }}>support@fetcha.co</Link>36 </Text>37 </Container>38 </Body>39 </Html>40 );41}4243const h1: React.CSSProperties = { fontSize: 22, fontWeight: 600, letterSpacing: "-0.02em", color: colors.text, margin: "0 0 12px" };44const p: React.CSSProperties = { fontSize: 15, lineHeight: "24px", color: colors.text, margin: "0 0 16px" };45const small: React.CSSProperties = { fontSize: 13, lineHeight: "20px", color: colors.muted, margin: "16px 0 0" };46const btn: React.CSSProperties = { backgroundColor: colors.accent, color: "#fff", fontSize: 14, fontWeight: 600, padding: "12px 20px", borderRadius: 6, textDecoration: "none", display: "inline-block" };47const code: React.CSSProperties = { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 13, backgroundColor: "#f1f3f6", padding: "2px 6px", borderRadius: 4 };4849export function VerificationEmail({ url, siteUrl }: { url: string; siteUrl: string }) {50 return (51 <Layout preview="Confirm your email address to activate your Fetcha account" siteUrl={siteUrl}>52 <Heading style={h1}>Confirm your email</Heading>53 <Text style={p}>Welcome to Fetcha. Click the button below to verify your email address and unlock production API access.</Text>54 <Button href={url} style={btn}>55 Verify email address56 </Button>57 <Text style={small}>58 This link expires in 24 hours. If the button does not work, paste this URL in your browser:59 <br />60 <Link href={url} style={{ color: colors.accent, wordBreak: "break-all" }}>61 {url}62 </Link>63 </Text>64 <Text style={small}>If you did not create a Fetcha account, you can safely ignore this email.</Text>65 </Layout>66 );67}6869export function WelcomeEmail({ name, dashboardUrl, docsUrl, siteUrl }: { name: string; dashboardUrl: string; docsUrl: string; siteUrl: string }) {70 return (71 <Layout preview="Your Fetcha account is ready" siteUrl={siteUrl}>72 <Heading style={h1}>Welcome to Fetcha{name ? `, ${name}` : ""}</Heading>73 <Text style={p}>Your email is verified and your account is ready. Here is the fastest path to your first request:</Text>74 <Text style={p}>75 1. Create a project and an API key in the dashboard.76 <br />77 2. Open the Playground, enter a URL, run it.78 <br />79 3. Copy the generated code for cURL, Python or JavaScript.80 </Text>81 <Button href={dashboardUrl} style={btn}>82 Open the dashboard83 </Button>84 <Text style={small}>85 Documentation lives at{" "}86 <Link href={docsUrl} style={{ color: colors.accent }}>87 {docsUrl}88 </Link>89 . Fetcha is a private platform: unlimited requests, no plans, no meters.90 </Text>91 </Layout>92 );93}9495export function InviteEmail({ inviterName, signupUrl, siteUrl }: { inviterName: string; signupUrl: string; siteUrl: string }) {96 const by = inviterName.trim();97 return (98 <Layout preview="You have been invited to Fetcha" siteUrl={siteUrl}>99 <Heading style={h1}>You have been invited to Fetcha</Heading>100 <Text style={p}>101 {by ? <><strong>{by}</strong> has added your email address to the Fetcha access list.</> : <>Your email address was added to the Fetcha access list.</>} Fetcha is a private,102 invitation-only platform: one API to reach any web page through managed proxy networks, a headless browser and crawl jobs, with no plans and no meters.103 </Text>104 <Text style={p}>Create your account with this same email address to get started. Your workspace, a default project and the Playground are ready as soon as you sign up.</Text>105 <Button href={signupUrl} style={btn}>106 Create my account107 </Button>108 <Text style={small}>109 If the button does not work, paste this URL in your browser:110 <br />111 <Link href={signupUrl} style={{ color: colors.accent, wordBreak: "break-all" }}>112 {signupUrl}113 </Link>114 </Text>115 <Text style={small}>If you were not expecting this invitation, you can safely ignore this email — no account is created until you sign up.</Text>116 </Layout>117 );118}119120export function PasswordResetEmail({ url, siteUrl }: { url: string; siteUrl: string }) {121 return (122 <Layout preview="Reset your Fetcha password" siteUrl={siteUrl}>123 <Heading style={h1}>Reset your password</Heading>124 <Text style={p}>We received a request to reset the password of your Fetcha account. This link is valid for one hour.</Text>125 <Button href={url} style={btn}>126 Choose a new password127 </Button>128 <Text style={small}>If you did not request a reset, ignore this email — your password will not change.</Text>129 </Layout>130 );131}132133export function EmailChangedEmail({ oldEmail, newEmail, siteUrl }: { oldEmail: string; newEmail: string; siteUrl: string }) {134 return (135 <Layout preview="The email address on your Fetcha account changed" siteUrl={siteUrl}>136 <Heading style={h1}>Email address changed</Heading>137 <Text style={p}>138 The email on your Fetcha account was changed from <span style={code}>{oldEmail}</span> to <span style={code}>{newEmail}</span>.139 </Text>140 <Text style={small}>If you did not make this change, contact support@fetcha.co immediately.</Text>141 </Layout>142 );143}144145export function EmailChangeApprovalEmail({ url, newEmail, siteUrl }: { url: string; newEmail: string; siteUrl: string }) {146 return (147 <Layout preview="Approve the email change on your Fetcha account" siteUrl={siteUrl}>148 <Heading style={h1}>Approve email change</Heading>149 <Text style={p}>150 A request was made to change your Fetcha account email to <span style={code}>{newEmail}</span>. Confirm below to proceed.151 </Text>152 <Button href={url} style={btn}>153 Approve change154 </Button>155 <Text style={small}>If you did not request this, ignore this email and consider changing your password.</Text>156 </Layout>157 );158}159160export function ApiKeyCreatedEmail({ keyName, prefix, projectName, when, siteUrl, dashboardUrl }: { keyName: string; prefix: string; projectName: string; when: string; siteUrl: string; dashboardUrl: string }) {161 return (162 <Layout preview="A new API key was created on your Fetcha account" siteUrl={siteUrl}>163 <Heading style={h1}>New API key created</Heading>164 <Text style={p}>165 The key <strong>{keyName}</strong> (<span style={code}>{prefix}…</span>) was created in project <strong>{projectName}</strong> on {when}.166 </Text>167 <Button href={dashboardUrl} style={btn}>168 Review API keys169 </Button>170 <Text style={small}>If you did not create this key, revoke it now from the dashboard and rotate your other keys.</Text>171 </Layout>172 );173}174175export function ApiKeyRevokedEmail({ keyName, prefix, siteUrl }: { keyName: string; prefix: string; siteUrl: string }) {176 return (177 <Layout preview="An API key was revoked" siteUrl={siteUrl}>178 <Heading style={h1}>API key revoked</Heading>179 <Text style={p}>180 The key <strong>{keyName}</strong> (<span style={code}>{prefix}…</span>) was revoked. Requests using it now fail with <span style={code}>INVALID_API_KEY</span>.181 </Text>182 </Layout>183 );184}185186export function NewLoginEmail({ ip, userAgent, when, siteUrl, securityUrl }: { ip: string; userAgent: string; when: string; siteUrl: string; securityUrl: string }) {187 return (188 <Layout preview="New sign-in to your Fetcha account" siteUrl={siteUrl}>189 <Heading style={h1}>New sign-in detected</Heading>190 <Text style={p}>191 Your account was accessed on {when} from IP <span style={code}>{ip}</span>.192 <br />193 Device: <span style={code}>{userAgent}</span>194 </Text>195 <Button href={securityUrl} style={btn}>196 Review active sessions197 </Button>198 <Text style={small}>If this was not you, revoke the session and change your password right away.</Text>199 </Layout>200 );201}202203export function UsageAlertEmail({ projectName, spent, limit, kind, siteUrl, usageUrl }: { projectName: string; spent: string; limit: string; kind: "soft" | "hard"; siteUrl: string; usageUrl: string }) {204 return (205 <Layout preview={kind === "hard" ? "Requests stopped: spending limit reached" : "Usage alert: spending threshold reached"} siteUrl={siteUrl}>206 <Heading style={h1}>{kind === "hard" ? "Spending limit reached — requests paused" : "Spending alert"}</Heading>207 <Text style={p}>208 Project <strong>{projectName}</strong> has spent {spent} this month against a {kind} limit of {limit}.209 {kind === "hard" ? " Further requests return USAGE_LIMIT_REACHED until the limit is raised or the month rolls over." : ""}210 </Text>211 <Button href={usageUrl} style={btn}>212 View usage213 </Button>214 </Layout>215 );216}217218export function AccountDeletionEmail({ url, siteUrl }: { url: string; siteUrl: string }) {219 return (220 <Layout preview="Confirm the deletion of your Fetcha account" siteUrl={siteUrl}>221 <Heading style={h1}>Confirm account deletion</Heading>222 <Text style={p}>You asked to delete your Fetcha account. Confirm below. After confirmation your account enters a 7-day grace period before data is deleted or anonymized. Invoices are retained as required by law.</Text>223 <Button href={url} style={{ ...btn, backgroundColor: "#c0392b" }}>224 Delete my account225 </Button>226 <Text style={small}>If you did not request this, ignore this email.</Text>227 </Layout>228 );229}230231export function BillingEmail({ title, body, siteUrl, billingUrl }: { title: string; body: string; siteUrl: string; billingUrl: string }) {232 return (233 <Layout preview={title} siteUrl={siteUrl}>234 <Heading style={h1}>{title}</Heading>235 <Text style={p}>{body}</Text>236 <Button href={billingUrl} style={btn}>237 Manage billing238 </Button>239 </Layout>240 );241}242