flagon-io/g1t

public

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t/deploy/self-host/workers/mail/index.js

57 lines2,239 bytesCodeBlame
1// The Email Sending binding, for self-hosted g1t.
2//
3// The identity service sends mail through Cloudflare Email Sending:
4// `env.EMAIL.send({ to, from, subject, text, html })`. Self-hosted, EMAIL is
5// a service binding to this Worker, which offers the same method and:
6//
7// - always prints the message to the log, so a self-hoster with no mail
8// server can still confirm an address by following the link;
9// - with MAIL_URL set, hands it to a Mailpit server's send API
10// (`POST <MAIL_URL>/api/v1/send`). Mailpit keeps it in its inbox, or
11// relays it to a real SMTP server when it is configured to.
12//
13// Links in messages name g1t.sh, where hosted g1t lives; they are rewritten
14// to PUBLIC_URL so they come back to this installation.
15
16import { WorkerEntrypoint } from "cloudflare:workers";
17
18const HOSTED = /https:\/\/g1t\.sh/g;
19
20function address(value) {
21 const match = /^(.*)<([^>]+)>\s*$/.exec(value ?? "");
22 return match ? { Name: match[1].trim(), Email: match[2].trim() } : { Email: String(value ?? "") };
23}
24
25export default class Mail extends WorkerEntrypoint {
26 async send(message) {
27 const site = (this.env.PUBLIC_URL ?? "http://localhost:8787").replace(/\/$/, "");
28 const text = String(message?.text ?? "").replace(HOSTED, site);
29 const html = String(message?.html ?? "").replace(HOSTED, site);
30 const from = this.env.MAIL_FROM || message?.from;
31 const to = Array.isArray(message?.to) ? message.to : [message?.to];
32
33 console.log(`[mail] to ${to.join(", ")}: ${message?.subject}\n${text}`);
34
35 if (this.env.MAIL_URL) {
36 const response = await fetch(`${this.env.MAIL_URL.replace(/\/$/, "")}/api/v1/send`, {
37 method: "POST",
38 headers: { "content-type": "application/json" },
39 body: JSON.stringify({
40 From: address(from),
41 To: to.map(address),
42 Subject: message?.subject ?? "",
43 Text: text,
44 HTML: html,
45 }),
46 });
47 if (!response.ok) {
48 throw new Error(`mail server answered ${response.status}: ${await response.text()}`);
49 }
50 }
51 return { messageId: crypto.randomUUID() };
52 }
53
54 async fetch() {
55 return new Response("The Email Sending binding for self-hosted g1t.", { status: 404 });
56 }
57}