Skip to content

Commit 21819fb

Browse files
authored
feat: mailer backend (#310)
1 parent 0ce21b9 commit 21819fb

File tree

3 files changed

+82
-1
lines changed

3 files changed

+82
-1
lines changed

.env.sample

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ COOKIE_SIGN_SECRET=128-digit-long-random-generated-secret
99
SQL_LOGS=true
1010
ARTIFICIAL_NETWORK_LATENCY=1500
1111

12-
#cloudflare
12+
# cloudflare
1313
CLOUDFLARE_R2_ACCESS_KEY=xxx
1414
CLOUDFLARE_R2_SECRET_KEY=xxx
1515
CLOUDFLARE_R2_TOKEN=xxx
1616

17+
# mailing
18+
MAILING_BREVO_API_KEY=xkeysib-xxx
19+
1720
# web
1821
NEXT_PUBLIC_API_ENDPOINT=http://localhost:3000
1922
## firebase

server/email/index.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { Context } from "hono";
2+
import { env } from "../lib/env.ts";
3+
4+
type APIStructure = {
5+
subject: string;
6+
sender: { name: string; email: string };
7+
type?: string;
8+
htmlContent: string;
9+
to: { email: string }[];
10+
};
11+
12+
function createBody(to: { name: string; email: string }[], subject: string, body: string): APIStructure {
13+
return {
14+
subject,
15+
sender: { name: "UT-Bridge", email: "[email protected]" },
16+
type: "classic",
17+
htmlContent: body,
18+
to,
19+
};
20+
}
21+
22+
export async function send(
23+
to: { name: string; email: string }[],
24+
subject: string,
25+
body: string,
26+
env: {
27+
apiKey: string;
28+
},
29+
) {
30+
const resp = await fetch("https://api.brevo.com/v3/smtp/email", {
31+
method: "POST",
32+
headers: {
33+
"api-key": env.apiKey,
34+
"content-type": "application/json",
35+
},
36+
body: JSON.stringify(createBody(to, subject, body)),
37+
});
38+
console.log(await resp.text());
39+
}
40+
41+
export type MailOptions = {
42+
to: { name: string; email: string }[];
43+
subject: string;
44+
/** HTML */
45+
body: string;
46+
};
47+
export async function sendEmail(c: Context, options: MailOptions) {
48+
return await send(options.to, options.subject, options.body, {
49+
apiKey: env(c, "MAILING_BREVO_API_KEY"),
50+
});
51+
}

server/lib/env.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,33 @@ export function env(c: Context, name: string, options?: { fallback?: string }):
55
return hono_env(c)[name] ?? options?.fallback ?? panic(`Environment variable not found: ${name}`);
66
}
77

8+
export function env_int(c: Context, name: string, options?: { fallback?: string }): number {
9+
const val = env(c, name, options);
10+
const parsed = Number.parseInt(val);
11+
if (Number.isNaN(parsed)) throw new Error(`[env_int] expected int-parsable value, got ${val}`);
12+
return parsed;
13+
}
14+
15+
export function env_bool(c: Context, name: string, options?: { fallback?: string }): boolean {
16+
const val = env(c, name, options);
17+
switch (val.toLowerCase()) {
18+
case "1":
19+
case "true":
20+
case "t":
21+
case "yes":
22+
case "y":
23+
return true;
24+
case "0":
25+
case "false":
26+
case "f":
27+
case "no":
28+
case "n":
29+
return false;
30+
default:
31+
throw new Error(`[env_bool] expected bool-castable value, got "${val}"`);
32+
}
33+
}
34+
835
export function panic(message: string): never {
936
throw new Error(message);
1037
}

0 commit comments

Comments
 (0)