feat(chore): add support of custom mail templates
This commit is contained in:
parent
4f061c00b9
commit
befff95eb5
90
cli/send.ts
90
cli/send.ts
|
@ -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) ?? [],
|
||||
|
|
34
deno.lock
34
deno.lock
|
@ -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",
|
||||
|
|
|
@ -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 })),
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue