xLog/src/lib/mailgun.server.ts

176 lines
4.4 KiB
TypeScript

import Mailgun from "mailgun.js"
import FormData from "form-data"
import { singleton } from "./singleton.server"
import {
ENCRYPT_SECRET,
MAILGUN_APIKEY,
MAILGUN_DOMAIN,
} from "~/lib/env.server"
import type { MailgunMessageData } from "mailgun.js/interfaces/Messages"
import { IS_PROD } from "./constants"
import { APP_NAME, OUR_DOMAIN, SITE_URL } from "./env"
import { SubscribeFormData } from "./types"
import { getSite } from "~/models/site.model"
import { type Site } from "~/lib/db.server"
import Iron from "@hapi/iron"
import mjml from "mjml"
import { getSiteLink } from "./helpers"
const enableMailgun = Boolean(MAILGUN_APIKEY && MAILGUN_DOMAIN)
const getClient = () =>
singleton("mailgun", () => {
const mg = new Mailgun(FormData)
const client = mg.client({
username: "api",
key: MAILGUN_APIKEY,
timeout: 60000,
})
return client
})
const sendEmail = async (message: MailgunMessageData) => {
console.log(message)
if (!enableMailgun) {
console.error(
"not sending email because no mailgun apikey or domain configured"
)
return
}
const client = getClient()
await client.messages.create(MAILGUN_DOMAIN, message).then(console.log)
}
export const sendLoginEmail = async (payload: {
token: string
email: string
url: string
subscribeForm?: SubscribeFormData
}) => {
const { protocol, host, pathname } = new URL(payload.url)
const query = new URLSearchParams([
["token", payload.token],
["next", `${protocol}//${host}${pathname}`],
])
if (payload.subscribeForm) {
query.append("subscribe", JSON.stringify(payload.subscribeForm))
}
const loginLink = `${
IS_PROD ? "https" : "http"
}://${OUR_DOMAIN}/api/login?${query.toString()}`
let subject = `Sign in to ${APP_NAME}`
let html = `
<p>Hello,</p>
<p>We received a request to sign in to ${APP_NAME} using this email address, please click the link below to sign in:</p>
<a href="${loginLink}">Sign in to ${APP_NAME}</a>
<p>This link will expire in 10 minutes. If you did not request this link, you can safely ignore this email.</p>
<p>Thanks,</p>
<p>Your Proselog Team</p>
`
if (payload.subscribeForm) {
const site = await getSite(payload.subscribeForm.siteId)
subject = `Confirm your subscription to ${site.name}`
html = `
<p>Please confirm your subscription to ${site.name} by clicking the link below:</p>
<a href="${loginLink}">confirm</a>
<p>This link will expire in 10 minutes.</p>`
}
const message: MailgunMessageData = {
from: `${APP_NAME} <hi@proselog.com>`,
to: payload.email,
subject,
html,
}
await sendEmail(message)
}
export const sendEmailForNewPost = async (payload: {
post: { slug: string; title: string; rendered: { contentHTML: string } }
site: Site
subscribers: { id: string; email: string }[]
}) => {
try {
const from = `${payload.site.name} <updates@${payload.site.subdomain}.proselog.com>`
const subject = `${payload.post.title} - ${payload.site.name}`
const siteLink = getSiteLink({ subdomain: payload.site.subdomain })
const html = `
<a href="${siteLink}/${
payload.post.slug
}" style="display:block;background:#eee;padding:5px 8px;border-radius:4px;text-decoration:none;">
View this post in browser
</a>
<h1 style="font-size:2.4em">${payload.post.title}</h1>
<div>
${payload.post.rendered.contentHTML}
</div>
<p style="margin-top:3em;">
<a
href="${`${SITE_URL}/api/unsubscribe?token=%recipient.unsubscribeToken%`}"
style="text-decoration:none;">
Unsubscribe
</a>
</p>
<p>
<a
href="${SITE_URL}"
style="text-decoration:none;">
Published on Proselog
</a>
</p>
`
const recipientVariables: {
[email in string]: { unsubscribeToken: string }
} = {}
await Promise.allSettled(
payload.subscribers.map(async (sub) => {
const unsubscribeToken = await Iron.seal(
{
userId: sub.id,
siteId: payload.site.id,
},
ENCRYPT_SECRET,
Iron.defaults
)
recipientVariables[sub.email] = {
unsubscribeToken,
}
})
)
const message: MailgunMessageData = {
from,
subject,
html,
to: Object.keys(recipientVariables),
"recipient-variables": JSON.stringify(recipientVariables),
}
await sendEmail(message)
} catch (error) {
console.error(error)
}
}