Add sending script
This commit is contained in:
parent
d5cf8d5a26
commit
5b070efc4f
1 changed files with 152 additions and 0 deletions
152
scripts/send-confirmation-reminder.js
Normal file
152
scripts/send-confirmation-reminder.js
Normal file
|
@ -0,0 +1,152 @@
|
|||
import { Kysely, SqliteDialect, sql } from "kysely";
|
||||
import Database from "better-sqlite3";
|
||||
|
||||
import { z } from "zod";
|
||||
import { snakeCase } from "change-case";
|
||||
import { join } from "node:path";
|
||||
|
||||
import "dotenv/config";
|
||||
|
||||
// --- DB
|
||||
|
||||
const path = process.env.DATABASE_PATH;
|
||||
if (!path) {
|
||||
throw new Error("DATABASE_PATH environment variable is not defined.");
|
||||
}
|
||||
console.log("Database path", path);
|
||||
const database = new Database(path);
|
||||
database.exec("PRAGMA journal_mode = WAL;");
|
||||
|
||||
const dialect = new SqliteDialect({ database });
|
||||
const db = new Kysely({ dialect });
|
||||
|
||||
// --- email client
|
||||
|
||||
const responseSchema = z.object({
|
||||
status: z.union([z.literal("success"), z.literal("error")]),
|
||||
data: z.record(z.any()),
|
||||
});
|
||||
|
||||
function convertObjectKeys(obj, fn) {
|
||||
const entries = Object.entries(obj).map(([key, val]) => [fn(key), val]);
|
||||
return Object.fromEntries(entries);
|
||||
}
|
||||
|
||||
class PostalError extends Error {
|
||||
constructor(code, message) {
|
||||
super(message);
|
||||
this.name = code;
|
||||
}
|
||||
}
|
||||
|
||||
export function createClient({ host, key }) {
|
||||
return {
|
||||
async send(config) {
|
||||
const url = join(host, "/api/v1/send/message");
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Server-API-Key": key,
|
||||
},
|
||||
body: JSON.stringify(convertObjectKeys(config, snakeCase)),
|
||||
});
|
||||
const data = await res.json();
|
||||
const parsed = responseSchema.parse(data);
|
||||
if (parsed.status === "error") {
|
||||
throw new PostalError(parsed.data.code, parsed.data.message);
|
||||
}
|
||||
return parsed.data;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const host = process.env.POSTAL_HOST ?? "https://resend.cz";
|
||||
const key = process.env.POSTAL_KEY;
|
||||
|
||||
if (!key) {
|
||||
throw new Error("Postal API key not defined");
|
||||
}
|
||||
|
||||
export const mailClient = createClient({ host, key });
|
||||
|
||||
// -----
|
||||
|
||||
const unconfirmedSubmissions = await db
|
||||
.selectFrom("letters")
|
||||
.select(["language", "email", "confirmationToken"])
|
||||
.where("confirmed", "=", 0)
|
||||
.execute();
|
||||
|
||||
console.log("Unconfirmed submissions", unconfirmedSubmissions.length);
|
||||
|
||||
for (const sub of unconfirmedSubmissions) {
|
||||
const title = "Last chance to verify email";
|
||||
const url = new URL(`${sub.language}/confirm/${sub.confirmationToken}`, process.env.PUBLIC_URL);
|
||||
const values = {
|
||||
subject: title,
|
||||
from: `Let's stop dirty money <vig@re-set.cz>`,
|
||||
to: sub.email,
|
||||
htmlBody: `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>${title}</title>
|
||||
<style>
|
||||
body {
|
||||
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";
|
||||
}
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 1em 2em;
|
||||
background-color: #a71122;
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
min-width: 150px;
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
font-weight: bold;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
p {
|
||||
font
|
||||
}
|
||||
.rule {
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
.separator {
|
||||
color: #ddd;
|
||||
margin-left: 0.25em;
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
.body {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="body">
|
||||
<h1>Last chance to verify e-mail</h1>
|
||||
<p>You have recently submitted message to Vienna Insurance Group, which is about to be sent to CEOs soon.</p>
|
||||
<p>We just need to confirm your e-mail to make sure it really exists.</p>
|
||||
<p>Please click following button to confirm your e-mail:</p>
|
||||
<p><a class="button" href="${url}">Confirm e-mail</a></p>
|
||||
</div>
|
||||
<div class="rule"></div>
|
||||
<p class="footer">
|
||||
<strong>Re-set</strong><span class="separator">·</span>
|
||||
<a href="https://re-set.cz/about-us" target="_blank">About</a><span class="separator">·</span>
|
||||
<a href="https://www.facebook.com/PlatformaReset" target="_blank">Facebook</a><span class="separator">·</span>
|
||||
<a href="https://www.instagram.com/reset_platforma/">Instagram</a><span class="separator">·</span>
|
||||
<a href="https://twitter.com/reset_platforma" target="_blank">X</a>
|
||||
</p>
|
||||
</body>
|
||||
</html>`,
|
||||
};
|
||||
console.log("values", values);
|
||||
// mailClient.send(values
|
||||
}
|
Reference in a new issue