Skip to content

Commit fc4d682

Browse files
committed
mail prep
1 parent 938364e commit fc4d682

File tree

8 files changed

+180
-7
lines changed

8 files changed

+180
-7
lines changed

Dockerfile

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,4 @@ WORKDIR /usr/app
55
COPY package.json .
66

77
RUN npm install -g typescript typeorm gulp-cli
8-
RUN npm install --verbose
9-
10-
COPY . .
8+
RUN npm install --verbose

api/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
import { startServer } from './server'
2+
23
startServer()

api/services/mailer.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import * as nodemailer from 'nodemailer'
2+
import EmailTemplate from '../views/email'
3+
import * as React from 'react'
4+
import * as ReactDomServer from 'react-dom/server'
5+
import * as juice from 'juice'
6+
7+
export const sendMail = async () => {
8+
9+
nodemailer.createTestAccount((err, account) => {
10+
11+
const transporter = nodemailer.createTransport({
12+
host: 'smtp.ethereal.email',
13+
port: 587,
14+
secure: false,
15+
auth: {
16+
user: account.user,
17+
pass: account.pass
18+
}
19+
})
20+
const html = ReactDomServer.renderToString(EmailTemplate({
21+
type: 'success',
22+
heading: 'This is the header',
23+
body: 'This is getting rendered booyah!',
24+
linkTitle: "Test link",
25+
linkUrl: "http://tojam.ca"
26+
}))
27+
const finalHtml = juice(html)
28+
const mailOptions = {
29+
to: 'tester1@gmail.com',
30+
from: 'tester2@gmail.com',
31+
text: 'plain text here',
32+
subject: 'Hello ✔',
33+
html: finalHtml
34+
}
35+
transporter.sendMail(mailOptions, (error, info) => {
36+
if (error) {
37+
return console.log(error);
38+
}
39+
console.log('Message sent: %s', info.messageId);
40+
console.log('Preview URL: %s', nodemailer.getTestMessageUrl(info));
41+
});
42+
})
43+
}

api/views/email.tsx

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import * as React from 'react'
2+
import { getStylesString } from './emailStyles'
3+
4+
interface EmailProps {
5+
type: 'default' | 'primary' | 'success' | 'info' | 'warning' | 'danger'
6+
7+
heading: string
8+
body: string
9+
10+
image?: string
11+
linkUrl?: string
12+
linkTitle?: string
13+
}
14+
15+
function ImageTag(props: EmailProps) {
16+
if (!props.image) { return null }
17+
return <img className="image_fix" src={props.image} />
18+
}
19+
20+
function LinkTag(props: EmailProps) {
21+
if (!props.linkUrl) { return null }
22+
return <a href={props.linkUrl} target="_blank" title="Styling Links">
23+
{props.linkTitle || props.linkUrl}
24+
</a>
25+
}
26+
27+
export default function EmailTemplate(props: EmailProps) {
28+
return (
29+
<html>
30+
<head>
31+
<meta httpEquiv="Content-Type" content="text/html; charset=utf-8" />
32+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
33+
<title>{props.heading}</title>
34+
<style type="text/css">{getStylesString()}</style>
35+
</head>
36+
<body>
37+
<table cellPadding="0" cellSpacing="0" id="backgroundTable">
38+
<tr>
39+
<td>
40+
<table cellPadding="0" cellSpacing="0">
41+
<tr>
42+
<td>
43+
<h2>{props.heading}</h2>
44+
</td>
45+
</tr>
46+
<tr>
47+
<td>
48+
<p>{props.body}</p>
49+
</td>
50+
</tr>
51+
<tr>
52+
<td>
53+
<LinkTag {...props} />
54+
<ImageTag {...props} />
55+
</td>
56+
</tr>
57+
</table>
58+
</td>
59+
</tr>
60+
</table>
61+
</body>
62+
</html>
63+
)
64+
}

api/views/emailStyles.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
2+
export function getStylesString(): string {
3+
return `
4+
#outlook a {padding:0;} /* Force Outlook to provide a "view in browser" menu link. */
5+
body{width:100% !important; -webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; margin:0; padding:0;}
6+
img {outline:none; text-decoration:none; -ms-interpolation-mode: bicubic;}
7+
a img {border:none;}
8+
.image_fix {display:block;}
9+
p {margin: 1em 0;}
10+
h1, h2, h3, h4, h5, h6 {color: black !important;}
11+
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {color: blue !important;}
12+
h1 a:active, h2 a:active, h3 a:active, h4 a:active, h5 a:active, h6 a:active {
13+
color: red !important; /* Preferably not the same color as the normal header link color. There is limited support for psuedo classes in email clients, this was added just for good measure. */
14+
}
15+
h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visited {
16+
color: purple !important; /* Preferably not the same color as the normal header link color. There is limited support for psuedo classes in email clients, this was added just for good measure. */
17+
}
18+
table td {border-collapse: collapse;}
19+
table { border-collapse:collapse; mso-table-lspace:0pt; mso-table-rspace:0pt; width: 100%;}
20+
a {color: orange;}
21+
a:link { color: orange; }
22+
a:visited { color: blue; }
23+
a:hover { color: green; }
24+
@media only screen and (max-device-width: 480px) {
25+
a[href^="tel"], a[href^="sms"] {
26+
text-decoration: none;
27+
color: black; /* or whatever your want */
28+
pointer-events: none;
29+
cursor: default;
30+
}
31+
.mobile_link a[href^="tel"], .mobile_link a[href^="sms"] {
32+
text-decoration: default;
33+
color: orange !important; /* or whatever your want */
34+
pointer-events: auto;
35+
cursor: default;
36+
}
37+
}
38+
/* More Specific Targeting */
39+
@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) {
40+
/* You guessed it, ipad (tablets, smaller screens, etc) */
41+
/* Step 1a: Repeating for the iPad */
42+
a[href^="tel"], a[href^="sms"] {
43+
text-decoration: none;
44+
color: blue; /* or whatever your want */
45+
pointer-events: none;
46+
cursor: default;
47+
}
48+
.mobile_link a[href^="tel"], .mobile_link a[href^="sms"] {
49+
text-decoration: default;
50+
color: orange !important;
51+
pointer-events: auto;
52+
cursor: default;
53+
}
54+
}
55+
`
56+
}

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,19 @@
2121
"@types/multer": "^1.3.6",
2222
"@types/node": "^9.6.1",
2323
"@types/nodemailer": "^4.6.0",
24+
"@types/react": "^16.3.6",
25+
"@types/react-dom": "^16.0.4",
2426
"@types/uuid": "^3.4.3",
2527
"bcrypt": "^1.0.3",
2628
"body-parser": "^1.18.2",
2729
"express": "^4.16.3",
30+
"juice": "^4.2.3",
2831
"multer": "^1.3.0",
2932
"nodemailer": "^4.6.4",
3033
"pg": "^7.4.1",
3134
"pg-hstore": "^2.3.2",
35+
"react": "^16.3.1",
36+
"react-dom": "^16.3.1",
3237
"reflect-metadata": "^0.1.12",
3338
"routing-controllers": "^0.7.7",
3439
"typeorm": "^0.1.20",

readme.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@ Work in progress template for cloning
77
1. Get Docker
88
2. `$ docker-compose up`
99

10-
## To-dos
10+
## Capabilities out of the box
11+
12+
- Express server powered using TypeScript friendly `routing-controllers` and `type-orm`
13+
- All the boring user management boilerplate
14+
- E2E tests for all boilerplated API
15+
- Server side rendering of React TSX files, including email
1116

12-
- Node Mailer
17+
## To-dos
1318

1419
- User CRUD
1520
- Password email reset
@@ -28,4 +33,4 @@ Work in progress template for cloning
2833
- Google OAuth Signup
2934
- Facebook OAuth Signup
3035
- Twitter OAuth Signup
31-
- Redis Cache Layer
36+
- Redis Cache Layer

tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"node_modules/*",
1717
"api/types/*"
1818
]
19-
}
19+
},
20+
"jsx": "react"
2021
},
2122
"include": [
2223
"api/**/*",

0 commit comments

Comments
 (0)