-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Adding code + ESLint + Prettier * Make cli executable * Add examples * Add yargs for CLI tool * Refactor with async/await * Don't apply Prettier to markdown files * Use keeptags option to preserve MailChimp tags * Remove comment * Fix arguments + display edit url
- Loading branch information
Showing
13 changed files
with
3,870 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"env": { | ||
"commonjs": true, | ||
"es6": true, | ||
"node": true | ||
}, | ||
"extends": ["airbnb-base", "prettier"], | ||
"globals": {}, | ||
"parserOptions": { | ||
"ecmaVersion": 2018 | ||
}, | ||
"rules": { | ||
"indent": ["error", 4], | ||
"quotes": ["error"] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"trailingComma": "es5", | ||
"tabWidth": 4, | ||
"semi": false, | ||
"singleQuote": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,11 @@ | ||
# Markdown To Mailchimp | ||
|
||
> Test project to create Mailchimp newsletters using Markdown | ||
> Create Mailchimp newsletters using Markdown | ||
## Install | ||
|
||
## Usage | ||
|
||
### CLI | ||
|
||
### Library |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
#!/usr/bin/env node | ||
const yargs = require('yargs') | ||
const chalk = require('chalk') | ||
const markdownToHtmlEmail = require('../src/markdownToHtmlEmail') | ||
const createMailchimpCampaign = require('../src/createMailchimpCampaign') | ||
|
||
const logError = error => console.error(chalk.bold.red(`❌ ${error}`)) | ||
const logSuccess = message => console.log(chalk.green(`✅ ${message}`)) | ||
|
||
const { argv } = yargs | ||
.usage('Usage: $0 [options]') | ||
.example('$0 --md ./test.md --t ./template.mjml --o ./test.html') | ||
.option('m', { | ||
alias: 'markdown', | ||
demandOption: true, | ||
describe: 'File containing email content in markdown format', | ||
type: 'string', | ||
}) | ||
.option('t', { | ||
alias: 'template', | ||
demandOption: true, | ||
describe: 'File containing email template in MJML format', | ||
type: 'string', | ||
}) | ||
.option('o', { | ||
alias: 'output', | ||
describe: | ||
'Optional directory to write HTML email to. Filename matches markdown name.', | ||
type: 'string', | ||
}) | ||
.option('a', { | ||
alias: 'apikey', | ||
default: process.env.MAILCHIMP_API_KEY, | ||
describe: 'Mailchimp API key', | ||
type: 'string', | ||
}) | ||
.option('l', { | ||
alias: 'listid', | ||
default: process.env.MAILCHIMP_LIST_ID, | ||
describe: 'Mailchimp list identifier', | ||
type: 'string', | ||
}) | ||
.option('k', { | ||
alias: 'keeptags', | ||
default: true, | ||
describe: 'Keep Mailchimp merge tags', | ||
type: 'boolean', | ||
}) | ||
.help('h') | ||
.alias('h', 'help') | ||
|
||
const convertAndCreateCampaign = async args => { | ||
try { | ||
const emailData = await markdownToHtmlEmail(args) | ||
|
||
const { apikey: apiKey, listid: listId } = args | ||
const options = { | ||
apiKey, | ||
listId, | ||
...emailData, | ||
} | ||
|
||
logSuccess('Created email data') | ||
|
||
const campaignData = await createMailchimpCampaign(options) | ||
if (!campaignData) { | ||
logError('No Mailchimp campaign created') | ||
} else { | ||
const { web_id: id } = campaignData | ||
const editUrl = 'https://admin.mailchimp.com/campaigns/edit' | ||
|
||
logSuccess(`Mailchimp campaign created - ${editUrl}?id=${id}`) | ||
} | ||
} catch (error) { | ||
console.error(error.toString()) | ||
} | ||
} | ||
|
||
convertAndCreateCampaign(argv) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
--- | ||
title: 'Title of the email campaign in Mailchimp' | ||
subject: 'Email subject line' | ||
preview: 'Preview text for the email' | ||
fromName: 'Test User' | ||
replyTo: '[email protected]' | ||
--- | ||
|
||
Hi *|FNAME|*, | ||
|
||
Thanks for subscribing to my mailing list. | ||
|
||
This is an email which has been converted from markdown to HTML. | ||
|
||
Thanks, | ||
Marc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<mjml> | ||
<mj-head> | ||
<mj-attributes> | ||
<mj-all font-family="Helvetica" font-size="16px" color="#222" /> | ||
</mj-attributes> | ||
<mj-preview>{{frontmatter.preview}}</mj-preview> | ||
</mj-head> | ||
|
||
<!-- Main body --> | ||
<mj-body background-color="#fff"> | ||
|
||
<!-- Main content --> | ||
<mj-section> | ||
<mj-column> | ||
<mj-text>{{content}}</mj-text> | ||
<mj-divider border-width="1px" border-style="dotted" border-color="#111" /> | ||
</mj-column> | ||
</mj-section> | ||
|
||
<!-- Footer with Mailchimp specifics --> | ||
<mj-section> | ||
<mj-column> | ||
<mj-text font-style="italic" font-size="12px">This email was sent to *|EMAIL|*.</mj-text> | ||
<mj-text font-style="italic" font-size="12px">If you would like to stop getting these emails, click <a href="*|UNSUB|*" target="_blank">this link to unsubscribe</a> from this mailing list.</mj-text> | ||
|
||
<mj-text font-style="italic" font-size="12px">Copyright © *|CURRENT_YEAR|* *|LIST:COMPANY|*, All rights reserved.</mj-text> | ||
<mj-text font-size="12px">*|IFNOT:ARCHIVE_PAGE|* *|LIST:DESCRIPTION|*</mj-text> | ||
<mj-text font-weight="bold" font-size="12px">My mailing address is:</mj-text> | ||
<mj-text font-size="12px">*|HTML:LIST_ADDRESS_HTML|* *|END:IF|*</mj-text> | ||
</mj-column> | ||
</mj-section> | ||
</mj-body> | ||
</mjml> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
{ | ||
"name": "markdown-to-mailchimp", | ||
"version": "0.1.0", | ||
"main": "index.js", | ||
"bin": { | ||
"md2mc": "./bin/cli.js" | ||
}, | ||
"repository": "[email protected]:MarcL/markdown-to-mailchimp.git", | ||
"author": "Marc Littlemore <[email protected]>", | ||
"license": "MIT", | ||
"dependencies": { | ||
"chalk": "^2.4.2", | ||
"frontmatter": "^0.0.3", | ||
"handlebars": "^4.5.1", | ||
"mailchimp-api-v3": "^1.13.1", | ||
"marked": "^0.7.0", | ||
"mjml": "^4.5.1", | ||
"yargs": "^14.2.0" | ||
}, | ||
"devDependencies": { | ||
"eslint": "^6.6.0", | ||
"eslint-config-airbnb-base": "^14.0.0", | ||
"eslint-config-prettier": "^6.5.0", | ||
"eslint-plugin-import": "^2.18.2", | ||
"husky": ">=1", | ||
"lint-staged": ">=8", | ||
"prettier": "1.18.2" | ||
}, | ||
"scripts": { | ||
"lint": "eslint ." | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "lint-staged" | ||
} | ||
}, | ||
"lint-staged": { | ||
"*.{js,css,json}": [ | ||
"prettier --write", | ||
"git add" | ||
], | ||
"*.js": [ | ||
"eslint --fix", | ||
"git add" | ||
] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
const mailchimp = require('./mailchimp') | ||
|
||
const createMailchimpCampaign = async options => { | ||
const { apiKey, listId, frontmatter, html } = options | ||
|
||
if (!apiKey || !listId) { | ||
return false | ||
} | ||
|
||
const campaignOptions = { | ||
apiKey, | ||
listId, | ||
...frontmatter, | ||
html, | ||
} | ||
|
||
let campaign = await mailchimp.findCampaignIdByMetaData(campaignOptions) | ||
|
||
if (!campaign) { | ||
campaign = await mailchimp.createCampaign(campaignOptions) | ||
} else { | ||
await mailchimp.updateCampaignHtml({ | ||
...campaignOptions, | ||
id: campaign.id, | ||
}) | ||
} | ||
|
||
return campaign | ||
} | ||
|
||
module.exports = createMailchimpCampaign |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
const Mailchimp = require('mailchimp-api-v3') | ||
|
||
const createCampaign = ({ | ||
apiKey, | ||
listId, | ||
subject, | ||
preview, | ||
title, | ||
fromName, | ||
replyTo, | ||
type = 'regular', | ||
}) => { | ||
const mailchimp = new Mailchimp(apiKey) | ||
return mailchimp.post('/campaigns', { | ||
type, | ||
recipients: { | ||
list_id: listId, | ||
}, | ||
settings: { | ||
subject_line: subject, | ||
preview_text: preview, | ||
title, | ||
from_name: fromName, | ||
reply_to: replyTo, | ||
to_name: '*|FNAME|*', | ||
}, | ||
}) | ||
} | ||
|
||
const updateCampaignHtml = ({ apiKey, id, html }) => { | ||
const mailchimp = new Mailchimp(apiKey) | ||
return mailchimp.put(`/campaigns/${id}/content`, { | ||
html, | ||
}) | ||
} | ||
|
||
const findCampaignIdByMetaData = ({ apiKey, listId, title, subject }) => { | ||
const mailchimp = new Mailchimp(apiKey) | ||
return mailchimp.get('/campaigns', { count: 1000 }).then(response => { | ||
return response.campaigns.find(campaign => { | ||
return ( | ||
campaign.recipients.list_id === listId && | ||
campaign.settings.subject_line === subject && | ||
campaign.settings.title === title | ||
) | ||
}) | ||
}) | ||
} | ||
|
||
module.exports = { | ||
createCampaign, | ||
findCampaignIdByMetaData, | ||
updateCampaignHtml, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
const fs = require('fs').promises | ||
const frontmatter = require('frontmatter') | ||
const marked = require('marked') | ||
|
||
// https://mailchimp.com/help/all-the-merge-tags-cheat-sheet/ | ||
const isMailChimpTag = text => /\|(.+?)\|/.test(text) | ||
|
||
const createHtmlFromMarkdown = (content, keepMailChimpTags = true) => { | ||
const newRenderer = new marked.Renderer() | ||
newRenderer.em = text => | ||
keepMailChimpTags && isMailChimpTag(text) | ||
? `*${text}*` | ||
: `<em>${text}</em>` | ||
|
||
return marked(content, { | ||
renderer: newRenderer, | ||
}) | ||
} | ||
|
||
const parseMarkdownFile = async ( | ||
markdownFilename, | ||
keepMailChimpTags = true | ||
) => { | ||
const fileContent = await fs.readFile(markdownFilename, 'utf8') | ||
|
||
const { content, data } = frontmatter(fileContent) | ||
const html = createHtmlFromMarkdown(content, keepMailChimpTags) | ||
|
||
return { | ||
html, | ||
markdown: content, | ||
frontmatter: data, | ||
} | ||
} | ||
|
||
module.exports = parseMarkdownFile |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
const fs = require('fs').promises | ||
const mjml = require('mjml') | ||
|
||
const parseMarkdownFile = require('./markdown') | ||
const renderHandlebars = require('./renderHandlebars') | ||
|
||
const createHtmlEmailFromTemplate = mjmlContent => { | ||
return mjml(mjmlContent, { | ||
minify: true, | ||
}) | ||
} | ||
|
||
const getFilenameWithoutExtension = filename => { | ||
const fileParts = filename.split('/') | ||
return fileParts[fileParts.length - 1].split('.md')[0] | ||
} | ||
|
||
const markdownToHtmlEmail = async options => { | ||
const { | ||
markdown: markdownFilename, | ||
template: mjmlTemplateFilename, | ||
output: outputDirectory, | ||
keeptags: keepMailChimpTags, | ||
} = options | ||
|
||
const fileData = await parseMarkdownFile( | ||
markdownFilename, | ||
keepMailChimpTags | ||
) | ||
|
||
const mjmlRenderedTemplate = await renderHandlebars({ | ||
filename: mjmlTemplateFilename, | ||
context: { | ||
frontmatter: fileData.frontmatter, | ||
content: fileData.html, | ||
}, | ||
}) | ||
|
||
const htmlEmail = createHtmlEmailFromTemplate(mjmlRenderedTemplate) | ||
|
||
if (outputDirectory) { | ||
const filename = getFilenameWithoutExtension(markdownFilename) | ||
const outputFilename = `${outputDirectory}/${filename}.html` | ||
|
||
await fs.writeFile(outputFilename, htmlEmail.html, 'utf8') | ||
} | ||
|
||
return { | ||
...fileData, | ||
...htmlEmail, | ||
mjml: mjmlRenderedTemplate, | ||
} | ||
} | ||
|
||
module.exports = markdownToHtmlEmail |
Oops, something went wrong.