Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions apps/web/src/emails/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,4 @@ Every template must include this branding footer below the content table:
| `clawEarlybirdExpiresTomorrow.html` | `expiry_date`, `claw_url`, `year` | `30` |
| `clawComplementaryInferenceEnded.html` | `claw_url`, `year` | — |
| `accountDeletionRequest.html` | `email`, `year` | — |
| `kiloClawSubscriptionStarted.html` | `plan_name`, `price_usd`, `billing_period`, `next_billing_date`, `manage_url`, `year` | — |
212 changes: 212 additions & 0 deletions apps/web/src/emails/kiloClawSubscriptionStarted.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Your KiloClaw subscription is active</title>
</head>
<body
style="
margin: 0;
padding: 0;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
background-color: #ffffff;
color: #1a1a1a;
"
>
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #ffffff">
<tr>
<td align="center" style="padding: 40px 20px">
<table width="520" cellpadding="0" cellspacing="0">
<!-- Header -->
<tr>
<td style="padding: 40px 40px 20px">
<h1
style="
margin: 0;
font-size: 24px;
font-weight: 700;
color: #1a1a1a;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
sans-serif;
"
>
Your KiloClaw subscription is active
</h1>
</td>
</tr>
<!-- Body -->
<tr>
<td style="padding: 0 40px 20px">
<p
style="
margin: 0 0 16px;
font-size: 14px;
line-height: 22px;
color: #333;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
sans-serif;
"
>
A KiloClaw hosting billing period has started. Here are the details:
</p>
</td>
</tr>
<!-- Detail box -->
<tr>
<td style="padding: 0 40px 20px">
<table
width="100%"
cellpadding="0"
cellspacing="0"
style="background-color: #f6f6f4; border: 1px solid #ebebea; border-radius: 10px"
>
<tr>
<td style="padding: 16px 20px">
<p
style="
margin: 0 0 8px;
font-size: 13px;
line-height: 20px;
color: #555;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
sans-serif;
"
>
<strong style="color: #1a1a1a">Plan:</strong> {{ plan_name }}
</p>
<p
style="
margin: 0 0 8px;
font-size: 13px;
line-height: 20px;
color: #555;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
sans-serif;
"
>
<strong style="color: #1a1a1a">Price:</strong> ${{ price_usd }} USD
</p>
<p
style="
margin: 0 0 8px;
font-size: 13px;
line-height: 20px;
color: #555;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
sans-serif;
"
>
<strong style="color: #1a1a1a">Billing period:</strong> {{ billing_period }}
</p>
<p
style="
margin: 0;
font-size: 13px;
line-height: 20px;
color: #555;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
sans-serif;
"
>
<strong style="color: #1a1a1a">Next billing date:</strong>
{{ next_billing_date }}
</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- CTA -->
<tr>
<td style="padding: 0 40px 20px">
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td align="center" style="padding: 10px 0 20px">
<a
href="{{ manage_url }}"
style="
display: inline-block;
padding: 10px 20px;
background-color: #1a1a1a;
color: #ffffff;
text-decoration: none;
border-radius: 7px;
font-size: 13px;
font-weight: 600;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
sans-serif;
"
>
Manage subscription
</a>
</td>
</tr>
</table>
</td>
</tr>
<!-- Sign-off -->
<tr>
<td style="padding: 0 40px 40px; border-top: 1px solid #ebebea">
<p
style="
margin: 20px 0 0;
font-size: 14px;
line-height: 20px;
color: #333;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
sans-serif;
"
>
If you have any questions, reply to this email — we're here to help.
</p>
<p
style="
margin: 16px 0 0;
font-size: 14px;
line-height: 20px;
color: #333;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
sans-serif;
"
>
— The Kilo Team
</p>
</td>
</tr>
</table>
<!-- Branding Footer -->
<table width="520" cellpadding="0" cellspacing="0">
<tr>
<td align="center" style="padding: 30px 20px; border-top: 1px solid #eee">
<p
style="
margin: 0;
font-size: 11px;
color: #ccc;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
sans-serif;
"
>
© {{ year }} Kilo Code, Inc<br />455 Market St, Ste 1940 PMB 993504<br />San
Francisco, CA 94105, USA
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
41 changes: 41 additions & 0 deletions apps/web/src/lib/email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const subjects = {
clawCreditRenewalFailed: 'Action Required: KiloClaw Hosting Renewal Failed',
clawComplementaryInferenceEnded: 'Your Free AI Inference Period Has Ended',
accountDeletionRequest: 'Kilo: Account Deletion Request Received',
kiloClawSubscriptionStarted: 'Your KiloClaw subscription is active',
} as const;

export type TemplateName = keyof typeof subjects;
Expand Down Expand Up @@ -391,3 +392,43 @@ export async function sendAccountDeletionSupportNotification(
replyTo: userEmail,
});
}

function formatUsd(cents: number): string {
return (cents / 100).toFixed(2);
}

function formatDate(date: Date): string {
// Dates surfaced to end-users; the server locale is stable (UTC in prod) so
// explicit en-US formatting avoids surprise month-name changes in tests.
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
timeZone: 'UTC',
});
}

type SendKiloClawSubscriptionStartedEmailProps = {
to: string;
planName: string;
priceCents: number;
billingPeriod: string;
nextBillingDate: Date;
};

export async function sendKiloClawSubscriptionStartedEmail(
props: SendKiloClawSubscriptionStartedEmailProps
): Promise<SendResult> {
const manage_url = `${NEXTAUTH_URL}/claw/subscription`;
return send({
to: props.to,
templateName: 'kiloClawSubscriptionStarted',
templateVars: {
plan_name: props.planName,
price_usd: formatUsd(props.priceCents),
billing_period: props.billingPeriod,
next_billing_date: formatDate(props.nextBillingDate),
manage_url,
},
});
}
Loading