feat(chore): add support of custom mail templates

This commit is contained in:
Julien Oculi 2024-04-02 15:47:06 +02:00
parent 4f061c00b9
commit befff95eb5
3 changed files with 121 additions and 12 deletions

View file

@ -1,25 +1,60 @@
import { Command } from '@cliffy/command/mod.ts'
import { Command, EnumType } from '@cliffy/command/mod.ts'
import { Input } from '@cliffy/prompt/mod.ts'
import { Contact } from '../src/contact.ts'
import { send } from '../src/send.ts'
import type { Mail } from '../types.ts'
import type { Mail, Template } from '../types.ts'
import type { JSX } from 'preact'
const templates: Map<
string,
Template<
(props: Record<string, unknown>) => JSX.Element,
Record<string, unknown>
>
> = new Map()
const templatesDir = import.meta.resolve('../templates').slice('file://'.length)
//Load templates dynamicaly
for await (
const template of Deno.readDir(templatesDir)
) {
if (template.isFile && template.name.endsWith('.tsx')) {
const modPath = new URL(
template.name,
`${import.meta.resolve('../templates')}/`,
)
const mod = await import(modPath.href)
templates.set(mod.default.name, mod.default)
}
}
const templateType = new EnumType([...templates.keys()])
export const cmd = new Command()
.name('send')
.description('Send a mail.')
.type('template', templateType)
.option(
'-f, --from <account:string>',
'From mail account or short name from config.',
{ default: '!me' },
)
.option('-r, --recipient <recipient:string>', 'Recipient (to) of the mail.', {
required: true,
collect: true,
})
.option('--cc <recipient:string>', 'Copy carbon.', { collect: true })
.option('--cci <recipient:string>', 'Copy carbon invisible.', {
collect: true,
})
.option('-a, --attachments <file:file>', 'Attachments.', { collect: true })
//.option('-t, --template <name:template>', 'HTML template from config', { default: 'message' })
.arguments('<to:string> <subject:string> <body:string>')
.option('-t, --template <name:template>', 'HTML template from config', {
default: 'message',
})
.arguments('<subject:string>')
.action(
async ({ from, cc, cci, attachments }, to, subject, body) => {
async ({ from, recipient, cc, cci, attachments, template }, subject) => {
const fromContact: Contact = await (async () => {
if (from === '!me') {
const whoami = new Deno.Command('whoami', {
@ -29,10 +64,10 @@ export const cmd = new Command()
const rawName = new TextDecoder().decode(stdout).trim()
const name = encodeURIComponent(rawName)
return {
return new Contact({
name: `${name} de Cohabit`,
address: `_${name}_@cohabit.fr`,
}
})
}
if (from.startsWith('!')) {
//@ts-ignore try expand
@ -41,11 +76,48 @@ export const cmd = new Command()
return Contact.fromString(from)
})()
const selectedTemplate = templates.get(template)!
type Props = typeof selectedTemplate
const props: Partial<Props> = {}
for (const prop of selectedTemplate.props) {
if (prop.multiline) {
console.log(
`%c?%c ${prop.description} %c[end input with "!EOL" on a new line] %c`,
'color: yellow;',
'font-weight: bold',
'color: white',
'color: blue',
)
const decoder = new TextDecoder()
for await (const chunk of Deno.stdin.readable) {
const text = decoder.decode(chunk)
if (text.startsWith('!EOF\n')) {
break
}
//@ts-ignore TODO fix type inference
props[prop.tag] += text
}
//@ts-ignore TODO fix type inference
if (props[prop.tag].startsWith('undefined')) {
//@ts-ignore TODO fix type inference
props[prop.tag] = props[prop.tag].slice('undefined'.length)
}
} else {
const value = await Input.prompt({
message: prop.description,
})
//@ts-ignore TODO fix type inference
props[prop.tag] = value
}
}
const mail: Mail = {
from: fromContact,
to: [Contact.fromString(to)],
to: recipient.map((to) => Contact.fromString(to)),
subject,
body,
body: selectedTemplate.builder(props)!,
options: {
cc: cc?.map(Contact.fromString) ?? [],
cci: cci?.map(Contact.fromString) ?? [],

View file

@ -3862,15 +3862,30 @@
"https://esm.sh/@types/nodemailer": "https://esm.sh/v135/@types/nodemailer@6.4.14/index.d.ts"
},
"remote": {
"https://deno.land/std@0.196.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3",
"https://deno.land/std@0.196.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee",
"https://deno.land/std@0.196.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56",
"https://deno.land/std@0.196.0/console/_data.json": "cf2cc9d039a192b3adbfe64627167c7e6212704c888c25c769fc8f1709e1e1b8",
"https://deno.land/std@0.196.0/console/_rle.ts": "56668d5c44f964f1b4ff93f21c9896df42d6ee4394e814db52d6d13f5bb247c7",
"https://deno.land/std@0.196.0/console/unicode_width.ts": "10661c0f2eeab802d16b8b85ed8825bbc573991bbfb6affed32dc1ff994f54f9",
"https://deno.land/std@0.196.0/encoding/base64.ts": "144ae6234c1fbe5b68666c711dc15b1e9ee2aef6d42b3b4345bf9a6c91d70d0d",
"https://deno.land/std@0.196.0/fmt/colors.ts": "a7eecffdf3d1d54db890723b303847b6e0a1ab4b528ba6958b8f2e754cf1b3bc",
"https://deno.land/std@0.196.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0",
"https://deno.land/std@0.196.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b",
"https://deno.land/std@0.196.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0",
"https://deno.land/std@0.196.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000",
"https://deno.land/std@0.196.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1",
"https://deno.land/std@0.196.0/path/mod.ts": "f065032a7189404fdac3ad1a1551a9ac84751d2f25c431e101787846c86c79ef",
"https://deno.land/std@0.196.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d",
"https://deno.land/std@0.196.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1",
"https://deno.land/std@0.196.0/path/win32.ts": "4fca292f8d116fd6d62f243b8a61bd3d6835a9f0ede762ba5c01afe7c3c0aa12",
"https://deno.land/std@0.203.0/dotenv/load.ts": "0636983549b98f29ab75c9a22a42d9723f0a389ece5498fe971e7bb2556a12e2",
"https://deno.land/std@0.203.0/dotenv/mod.ts": "1da8c6d0e7f7d8a5c2b19400b763bc11739df24acec235dda7ea2cfd3d300057",
"https://deno.land/x/cliffy@v1.0.0-rc.3/_utils/distance.ts": "02af166952c7c358ac83beae397aa2fbca4ad630aecfcd38d92edb1ea429f004",
"https://deno.land/x/cliffy@v1.0.0-rc.3/ansi/ansi_escapes.ts": "193b3c3a4e520274bd8322ca4cab1c3ce38070bed1898cb2ade12a585dddd7c9",
"https://deno.land/x/cliffy@v1.0.0-rc.3/ansi/cursor_position.ts": "caa008d29f7a904908bda514f9839bfbb7a93f2d5f5580501675b646d26a87ff",
"https://deno.land/x/cliffy@v1.0.0-rc.3/ansi/deps.ts": "f48ae5d066684793f4a203524db2a9fd61f514527934b458006f3e57363c0215",
"https://deno.land/x/cliffy@v1.0.0-rc.3/ansi/tty.ts": "155aacdcb7dc00f3f95352616a2415c622ffb88db51c5934e5d2e8341eab010b",
"https://deno.land/x/cliffy@v1.0.0-rc.3/command/_argument_types.ts": "ab269dacea2030f865a07c2a1e953ec437a64419a05bad1f1ddaab3f99752ead",
"https://deno.land/x/cliffy@v1.0.0-rc.3/command/_errors.ts": "12d513ff401020287a344e0830e1297ce1c80c077ecb91e0ac5db44d04a6019c",
"https://deno.land/x/cliffy@v1.0.0-rc.3/command/_spread.ts": "0cc6eb70a6df97b5d7d26008822d39f3e8a1232ee0a27f395aa19e68de738245",
@ -3920,6 +3935,25 @@
"https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types/integer.ts": "b60d4d590f309ddddf066782d43e4dc3799f0e7d08e5ede7dc62a5ee94b9a6d9",
"https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types/number.ts": "610936e2d29de7c8c304b65489a75ebae17b005c6122c24e791fbed12444d51e",
"https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types/string.ts": "e89b6a5ce322f65a894edecdc48b44956ec246a1d881f03e97bbda90dd8638c5",
"https://deno.land/x/cliffy@v1.0.0-rc.3/keycode/_key_codes.ts": "917f0a2da0dbace08cf29bcfdaaa2257da9fe7e705fff8867d86ed69dfb08cfe",
"https://deno.land/x/cliffy@v1.0.0-rc.3/keycode/key_code.ts": "730fa675ca12fc2a99ba718aa8dbebb1f2c89afd47484e30ef3cb705ddfca367",
"https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/_figures.ts": "e22413ddd51bb271b6b861a058742e83aaa3f62c14e8162cb73ae6f047062f51",
"https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/_generic_input.ts": "870dad97077582439cee26cb19aec123b4850376331338abdc64a91224733cdc",
"https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/_generic_list.ts": "8b0bea4521b1e2f62c564e0d3764a63264043694f4228bb0bc0b63ce129ef33b",
"https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/_generic_prompt.ts": "4c9d9cdeda749620a3f5332524df13d083e2d59b1ed90a003f43cd0991a75a10",
"https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/_generic_suggestions.ts": "5e6ee1190b4dd5af261ae2ff0196dec7f1988ea9c41c6288cfaece293703002c",
"https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/_utils.ts": "498ae639d7666599d612b615ee85de9103b3c3a913d5196f6b265072674258c7",
"https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/checkbox.ts": "9cfd71f1e278d0ef76054be103d956b66995593902c149380d01b1a1647025f3",
"https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/confirm.ts": "ff892331f6de281079421fe2f57f1d56acb38f28bc48678f87a3fc11ef4a5f7c",
"https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/deps.ts": "2560142f070bb2668e2e8a74683c799461648b9aad01bbf36b3cad3851d712e6",
"https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/input.ts": "81821244f895cc4db32c2511c17e21fb48fd7606e300605aeb2a231ab1680544",
"https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/list.ts": "e5d3e1a6d931b9736d03eec2425fb7b4d2b8d1461a84e210b4787edda414dca4",
"https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/mod.ts": "f8789193742daf3aba93b543a2ea099383284d60fcccc03567102e28c0d61927",
"https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/number.ts": "5421bf1b6411a6f02c44da4e867f19e02315450769e0feacab3c1c88cc1b06d6",
"https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/prompt.ts": "f10e1c8a0c2ca093a485f7f1156342210b27a8cffc96fe0b4cff60007cabab30",
"https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/secret.ts": "cece271c7ce01e12b249c31c2f9cea9e53b6e6be7621a478dac902bd8f288b61",
"https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/select.ts": "c10902aeaca02a55d9b846934958dd166ee39c741faebdaa9800689e402186cf",
"https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/toggle.ts": "028f80de31750e7b5479727a64b4878f090ecd783fe3bb0d286e2e1c29f0eee3",
"https://deno.land/x/cliffy@v1.0.0-rc.3/table/_layout.ts": "e4a518da28333de95ad791208b9930025987c8b93d5f8b7f30b377b3e26b24e1",
"https://deno.land/x/cliffy@v1.0.0-rc.3/table/_utils.ts": "fd48d1a524a42e72aa3ad2eec858a92f5a00728d306c7e8436fba6c34314fee6",
"https://deno.land/x/cliffy@v1.0.0-rc.3/table/border.ts": "5c6e9ef5078c6930169aacb668b274bdbb498461c724a7693ac9270fe9d3f5d5",

View file

@ -1,15 +1,18 @@
import type { Mail } from '../types.ts'
import { renderTemplate } from './template.tsx'
import { transporter } from './transporter.ts'
export function send(mail: Mail) {
export async function send(mail: Mail) {
const { html, text } = await renderTemplate(mail.body)
return transporter.sendMail({
from: mail.from.toString(),
to: mail.to.map((contact) => contact.toString()),
cc: mail.options.cc.map((cc) => cc.toString()),
bcc: mail.options.cci.map((cci) => cci.toString()),
subject: mail.subject,
text: 'Text only',
html: mail.body.toString(),
text,
html,
attachments: mail.options.attachments.map((path) => ({ path })),
})
}