Skip to content

Commit 5da0930

Browse files
committed
Format emails WIP
1 parent e668da8 commit 5da0930

File tree

11 files changed

+185
-11
lines changed

11 files changed

+185
-11
lines changed

.env.sample

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# port on which the service will run
22
PORT=3005
33

4+
# name of the app that sends the emails - featured in the message subject or body
5+
APP_NAME=sleepy.bike
6+
47
# server base url, e.g. to construct correct email verification link
58
# this is the base url that end users see
69
BASE_URL=http://localhost:3005

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
"version": "0.0.1",
44
"main": "dist/index.js",
55
"scripts": {
6-
"start": "tsc && node dist/index.js",
6+
"start": "yarn build && node dist/index.js",
7+
"build": "rm -rf dist && tsc && yarn copy-hbs",
8+
"copy-hbs": "cp src/templates/*.hbs dist/templates && cp src/templates/*.css dist/templates",
79
"format": "prettier 'src/**/*.ts' '**/*.{md,yml,yaml,json}' --write",
810
"lint": "eslint . --ext .ts",
911
"test": "mocha",
@@ -64,7 +66,9 @@
6466
"css-authn": "^0.0.14",
6567
"dotenv": "^16.0.0",
6668
"fs-extra": "^11.2.0",
69+
"handlebars": "^4.7.8",
6770
"jsonwebtoken": "^9.0.2",
71+
"juice": "^10.0.0",
6872
"koa": "^2.14.2",
6973
"koa-helmet": "^7.0.2",
7074
"koa-static": "^5.0.0",

src/config/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import SMTPTransport from 'nodemailer/lib/smtp-transport'
77
// server base url, e.g. to construct correct email verification links
88
export const baseUrl = process.env.BASE_URL ?? 'http://localhost:3005'
99

10+
export const appName = process.env.APP_NAME ?? 'sleepy.bike'
11+
1012
// identity under which the mailer is operating
1113
export const mailerCredentials = {
1214
email: process.env.MAILER_IDENTITY_EMAIL ?? 'bot@example',
@@ -39,7 +41,8 @@ export const smtpTransportOptions: SMTPTransport.Options = {
3941
}
4042

4143
// email address which will be the sender of the notifications and email verification messages
42-
export const emailSender = process.env.EMAIL_SENDER
44+
export const emailSender =
45+
process.env.EMAIL_SENDER ?? '[email protected]'
4346

4447
export const port: number = +(process.env.PORT ?? 3005)
4548

src/controllers/integration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export const initializeIntegration: Middleware = async ctx => {
3333
await sendMail({
3434
from: config.emailSender,
3535
to: email,
36-
subject: 'Verify your email for sleepy.bike notifications',
36+
subject: `Verify your email for ${config.appName} notifications`,
3737
html: `Please verify your email <a href="${emailVerificationLink}">click here</a>`,
3838
text: `Please verify your email ${emailVerificationLink}`,
3939
})

src/controllers/notification.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { DefaultContext, Middleware } from 'koa'
2-
import { emailSender } from '../config'
2+
import { appName, emailSender } from '../config'
33
import { sendMail } from '../services/mailerService'
4+
import { generateHtmlMessage } from '../templates/generateMessage'
45
import { getVerifiedEmails } from './status'
56

67
export type GoodBody = {
@@ -31,10 +32,18 @@ export const notification: Middleware<
3132

3233
for (const email of emails) {
3334
await sendMail({
34-
from: emailSender,
35-
to: email,
36-
subject: 'You have a new message from sleepy.bike!', // TODO generalize
37-
html: body.object.content,
35+
from: {
36+
name: body.actor.name
37+
? `${body.actor.name} (via ${appName})`
38+
: `${appName} notifications`,
39+
address: emailSender,
40+
},
41+
to: {
42+
name: body.target.name ?? '',
43+
address: email,
44+
},
45+
subject: `${body.actor.name || 'Someone'} wrote you from ${appName}`,
46+
html: await generateHtmlMessage('message', body),
3847
text: body.object.content,
3948
})
4049
}

src/templates/generateMessage.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as fs from 'fs-extra'
2+
import Handlebars from 'handlebars'
3+
import juice from 'juice'
4+
import path from 'path'
5+
6+
export const generateHtmlMessage = async <T>(type: string, data: T) => {
7+
const layout = await fs.readFile(path.join(__dirname, 'layout.hbs'), 'utf8')
8+
const layoutTemplate = Handlebars.compile(layout)
9+
const content = await fs.readFile(path.join(__dirname, `${type}.hbs`), 'utf8')
10+
const contentTemplate = Handlebars.compile<T>(content)
11+
12+
const compiledContent = contentTemplate(data)
13+
const emailHtml = layoutTemplate({ title: 'asdf', body: compiledContent })
14+
const emailHtmlInlineCss = juice(emailHtml)
15+
16+
return emailHtmlInlineCss
17+
}

src/templates/layout.hbs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<html>
2+
<head>
3+
<meta charset='utf-8' />
4+
<title>{{title}}</title>
5+
<style>
6+
/* Email styles go here */ body { font-family: Arial, sans-serif; }
7+
</style>
8+
</head>
9+
<body>
10+
{{{body}}}
11+
<footer>
12+
<p>Contact us at
13+
<a href='mailto:[email protected]'>[email protected]</a></p>
14+
</footer>
15+
</body>
16+
</html>

src/templates/message.hbs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<p>
2+
Hello
3+
{{target.name}}!</p>
4+
<a href={{actor.id}}>{{actor.name}}</a>
5+
sent you a message from
6+
{{appName}}.
7+
8+
<p>{{object.content}}</p>
9+
10+
<a href='https://sleepy.bike/messages/{{actor.id}}'>Reply on {{appName}}</a>

src/templates/styles.css

Whitespace-only changes.

src/test/notification.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ describe('send notification via /notification', () => {
7676
const emailNotification = sendMailSpy.firstCall.firstArg
7777

7878
expect(emailNotification).to.exist
79-
expect(emailNotification.to).to.equal(email)
79+
expect(emailNotification.to).to.haveOwnProperty('address', email)
8080

8181
// TODO
8282
})

0 commit comments

Comments
 (0)