convert plain text newsletter email content to html using markdown-to-html and save it if html is not available

This commit is contained in:
Hongbo Wu
2023-09-25 21:16:43 +08:00
parent 02291e4e55
commit 9021854be7
4 changed files with 56 additions and 5 deletions

View File

@ -24,6 +24,7 @@
"@types/json-bigint": "^1.0.1",
"@types/node": "^14.11.2",
"@types/rfc2047": "^2.0.1",
"@types/showdown": "^2.0.1",
"chai": "^4.3.6",
"eslint-plugin-prettier": "^4.0.0",
"mocha": "^10.0.0"
@ -39,6 +40,7 @@
"jsonwebtoken": "^8.5.1",
"parse-headers": "^2.0.4",
"parse-multipart-data": "^1.2.1",
"rfc2047": "^4.0.1"
"rfc2047": "^4.0.1",
"showdown": "^2.1.0"
}
}

View File

@ -12,6 +12,7 @@ import * as jwt from 'jsonwebtoken'
import parseHeaders from 'parse-headers'
import * as multipart from 'parse-multipart-data'
import rfc2047 from 'rfc2047'
import { Converter } from 'showdown'
import { promisify } from 'util'
import { Attachment, handleAttachments, isAttachment } from './attachment'
import {
@ -36,6 +37,11 @@ const signToken = promisify(jwt.sign)
const NEWSLETTER_EMAIL_RECEIVED_TOPIC = 'newsletterEmailReceived'
const NON_NEWSLETTER_EMAIL_TOPIC = 'nonNewsletterEmailReceived'
const pubsub = new PubSub()
const converter = new Converter()
export const plainTextToHtml = (text: string): string => {
return converter.makeHtml(text)
}
export const publishMessage = async (
topic: string,
@ -163,19 +169,24 @@ export const inboundEmailHandler = Sentry.GCPFunction.wrapHttpFunction(
await handleAttachments(to, subject, attachments, receivedEmailId)
return res.send('ok')
}
// convert text to html if html is not available
const content = html || plainTextToHtml(text)
// all other emails are considered newsletters
const newsletterMessage = await handleNewsletter({
from,
to,
subject,
html,
html: content,
headers,
})
// queue newsletter emails
await pubsub.topic(NEWSLETTER_EMAIL_RECEIVED_TOPIC).publishMessage({
json: {
email: to,
content: html || text, // html is preferred
content,
url: generateUniqueUrl(),
title: subject,
author: parseAuthor(from),

View File

@ -2,7 +2,7 @@ import { expect } from 'chai'
import 'mocha'
import parseHeaders from 'parse-headers'
import rfc2047 from 'rfc2047'
import { parsedTo } from '../src'
import { parsedTo, plainTextToHtml } from '../src'
import {
getConfirmationCode,
isGoogleConfirmationEmail,
@ -138,3 +138,29 @@ describe('decode and parse headers', () => {
})
})
})
describe('plainTextToHtml', () => {
it('converts text to html', () => {
const text =
'DEVOPS WEEKLY\r\n' +
'ISSUE #665 - 24th September 2023\r\n' +
'\r\n' +
'A few posts on CI tooling this week, along with a good introduction to developer portals/platforms and other topics.\r\n' +
'\r\n' +
'StackHawk sponsors Devops Weekly\r\n' +
'============================\r\n' +
'\r\n' +
'Experience automated security testing without the hassle of connecting your own app or configuring an environment! Follow the Tutorial to try out StackHawk and explore a world where security becomes an accelerator, not a blocker\r\n' +
'\r\n' +
'https://sthwk.com/tutorial\r\n' +
'\r\n'
expect(plainTextToHtml(text)).to.eql(
`<p>DEVOPS WEEKLY
ISSUE #665 - 24th September 2023</p>
<p>A few posts on CI tooling this week, along with a good introduction to developer portals/platforms and other topics.</p>
<h1 id="stackhawksponsorsdevopsweekly">StackHawk sponsors Devops Weekly</h1>
<p>Experience automated security testing without the hassle of connecting your own app or configuring an environment! Follow the Tutorial to try out StackHawk and explore a world where security becomes an accelerator, not a blocker</p>
<p>https://sthwk.com/tutorial</p>`
)
})
})

View File

@ -7985,6 +7985,11 @@
"@types/mime" "^1"
"@types/node" "*"
"@types/showdown@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/showdown/-/showdown-2.0.1.tgz#24134738ba3107237d6a783e054a54773e739f81"
integrity sha512-xdnAw2nFqomkaL0QdtEk0t7yz26UkaVPl4v1pYJvtE1T0fmfQEH3JaxErEhGByEAl3zUZrkNBlneuJp0WJGqEA==
"@types/sinon-chai@^3.2.8":
version "3.2.8"
resolved "https://registry.yarnpkg.com/@types/sinon-chai/-/sinon-chai-3.2.8.tgz#5871d09ab50d671d8e6dd72e9073f8e738ac61dc"
@ -11214,7 +11219,7 @@ commander@^8.3.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
commander@^9.1.0:
commander@^9.0.0, commander@^9.1.0:
version "9.5.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30"
integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==
@ -24526,6 +24531,13 @@ shimmer@^1.2.1:
resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337"
integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==
showdown@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/showdown/-/showdown-2.1.0.tgz#1251f5ed8f773f0c0c7bfc8e6fd23581f9e545c5"
integrity sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==
dependencies:
commander "^9.0.0"
side-channel@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"