• Stars
    star
    641
  • Rank 70,212 (Top 2 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created almost 11 years ago
  • Updated almost 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

💌 Compose responsive email templates easily, fill them with models, and send them out.

campaign.png

help me on gittip flattr.png Support

Compose responsive email templates easily, fill them with models, and send them out.

This is the stuff responsible for sending beautiful emails in Pony Foo. I've isolated that code, and turned it into a reusable package, called campaign. It comes with a dead simple API, and a beautiful responsive layout, originally written by MailChimp, adapted by me. It's also easily configurable, and comes with nice conventions over configuration, so you don't need to do a lot to get going.

Being highly configurable is important to campaign, and for that reason it ships with several plugins to popular view engines: campaign-mustache, campaign-jade, and campaign-jadum. You can use any of these to manage your email templates. Typically, you'll want to use the same templating engine you use in your front-end views, for consistency across your codebase.

Campaign can send emails through a variety of services using different plugins as well. You could also create your own email service provider plugin.

Reference

Quick links for reference.

Features

  • Extensible. Pick a template engine and an email-sending service or SMTP and roll with it
  • Takes care of boring stuff: CSS inlining, @media queries, JSON-LD, plain-text versions of your HTML
  • Takes care of important stuff: batching requests, providing a sane API, view layouts, etc.
  • Provides debugging facilities for sending test emails and capturing output in a terminal session

Getting Started

Install using npm.

npm i --save campaign

Set it up.

Construct a client.

var client = require('campaign')({
  templateEngine: require('campaign-jade'),
  provider: require('campaign-mailgun')({
    apiKey: 'key-12rvasxx'
  })
});

Send emails!

client.send(template, options, done);
client.sendString('<p>{{something}}</p>', options, done);

(detailed information below)

Screenshot

Here is a screenshot of an email sent using this library, as seen on Pony Foo subscriptions, in production. This email is using the default layout provided by campaign.

sample.png

Client Options

There's a few configurable options, here's an overview of the default values.

{
  "from": null,
  "provider": null,
  "templateEngine": null,
  "layout": null,
  "formatting": null,
  "headerImage": null,
  "trap": false
}

from

The from address for our emails. The provider is responsible for trying to make it look like that's the send address. Not necessarily used for authentication.

provider

You can pick any supported email providers, creating your own or choosing one that comes with campaign. To implement a provider yourself, you'll need to create a custom provider object. The provider object should have a send function, which takes a model, and a done callback. You can read more about custom providers below.

Available providers listed below.

templateEngine

You can use other template engines, creating your own. You'll need to create a custom engine object with both render and renderString methods. Note that template engines govern the default layouts. If you implement your own engine, you'll have to provide a default layout, as well.

Available engines listed below.

layout

The layout used in your emails. Templates for email sending are meant to have the bare minimum needed to fill out an email. Since you want a consistent UX, the same layout should be used for every email your product sends.

A default layout template is provided by supporting template engines. You can provide a different one, just set layout to the absolute path of a template file that's supported by your template engine. For information about the model passed to the layout, see the Templates section.

formatting

When you want to customize HTML before submission, but after your template engine and layout have been rendered into a single piece of HTML, you can use the formatting option. Useful for tweaking CSS or markup in a global manner for all emails without having to touch the models every time.

function formatting (html) {
  return change(html);
}

headerImage

You may provide the full path to an image. This image will be encoded in base64 and embedded into the email as a heading. Embedding helps people view your emails offline.

This image should have a 3:1ish ratio. For instance, I use 600x180 in my blog's emails.

trap

If true, then emails won't be sent to any recipients at all. You could also set trap to [email protected], and all emails would be sent to me instead of the intended recipients. Great for spamming me, and also great for testing.

When you trap recipients, the email will get a nifty JSON at the end detailing the actual recipients that would've gotten it.

Email Sending Options

Once you've created a client, you can start sending emails. Here are the default options, and what you need to fill out. The from and trap fields are inherited from the configuration object passed to campaign, and they can be overridden on an email-by-email basis.

{
  "subject": "<not provided>",
  "teaser": "<options.subject>",
  "from": "<campaign.from>",
  "trap": "<campaign.trap>",
  "to": "<not provided>",
  "when": "YYYY/MM/DD HH:mm, UTC Z",
  "images": "<empty>",
  "attachments": "<empty>",
  "social": {
    "twitter": "<not provided>",
    "landing": "<not provided>",
    "name": "<not provided>"
  },
  "provider": {
    "tags": "<not provided>",
    "merge": "<not provided>"
  },
  "styles": "<defaults>"
}

.send vs .sendString

The only difference between .send and .sendString is that .send takes the path to a file, rather than the template itself. .send compiles the template and keeps it in a cache, while .sendString compiles the template every time.

You can also use .render or .renderString as the equivalents to both of these methods that will only render the emails as HTML. This is useful for debugging and to render emails identically to what your customers see, but handle the rendering logic yourself.

subject

The email subject.

teaser

This is the line that most email clients show as a teaser for the email message. It defaults to the subject line. Changing it is extremely encouraged.

to

These are the recipients of the email you're sending. Simply provide a single recipient's email address, or an array of email addresses.

when

Here you can pass a moment format string. Eg. '[um] HH:mm [am] DD.MM.YYYY'. The default format passed to moment is 'YYYY/MM/DD HH:mm, UTC Z'.

attachments

A list of files you'd like to attach to your emails.

[
  { name: 'invoice', file: path.join(__dirname, 'invoice.png') }
]

images

If you want to provide the template with embedded images (other than the optional email header when creating the campaign client) you can set images to a list of file paths and names (to later reference them in your templates), as shown below.

[
  { name: 'housing', file: path.join(__dirname, 'housing.png') }
]

Instead of a file you can provide a data value with the base64 encoded data, and avoid the overhead of creating a temporary file. If you choose this approach you must set the mime property as well.

[
  { name: 'housing', mime: 'image/png', data: buff.toString('base64') }
]

social

Social metadata used when sending an email can help build your brand. You can provide a twitter handle, a name for your brand, and a landing page.

The name is used as the name of the send address, as well as in the "Visit " link.

provider

Configuration specifically used by the email-sending provider.

Many email providers allow you to add dynamic content to your templates. For instance, the feature is supported by both the campaign-mailgun and the campaign-mandrill providers, out the box. Read more about merge variables in Mandrill.

provider.merge

Providers have wildly different merge API in terms of how they want you to give them these recipient-specific variables and how you can reference them in your templates. Campaign helps by providing a reasonable API and then deals with obscure provider data formats under the hood, so you don't have to.

The following example shows merge variables for a couple emails and defaults that are used when a particular recipient doesn't have a value for a given variable.

{
  "[email protected]": {
    "something": "is a merge variable for the guy with that email"
  },
  "[email protected]": {
    "here": "is a merge variable for another peep"
  },
  "*": {
    "whatever": "is a merge variable for everyone, useful for defaults"
  }
}

In your email templates, you can reference these variables simply using {{something}} for values you wish to encode before embedding, and {{{here}}} for embedding raw HTML. Note that this syntax is consistent regardless of whether you're using campaign-mailgun, campaign-mandrill, or something else.

provider.tags

Mailgun, Mandrill and others let you tag your emails so that you can find different campaigns later on. Read more about tagging. By default, emails will be tagged with the template name.

styles

Read about styles below.

Templates

There are two types of templates: the layout, and the email's body template. A default layout is provided, so let's talk about the email templates first, and then the layout.

Email body Templates

The body template determines what goes in the message body. The options we used to configure our email are also used as the model for the body template, as sometimes it might be useful to include some of that metadata in the model itself.

The API expects an absolute path to the body template.

client.send(body, options, done);

Other than the options listed above, you can provide any values you want, and then reference those in the template.

The layout Template

The layout has one fundamental requirement in order to be mildly functional, it should have a {{{body}}} in it, so that the actual email's content can be rendered. Luckily the default layout is good enough that you shouldn't need to touch it. If you're building a custom layout, {{{body}}} should be whatever expression is needed to render the unescaped <body> HTML.

Purposely, the layout template isn't passed the full model, but only a subset, containing:

{
  "_header": "<options._header>",
  "subject": "<options.subject>",
  "preview": "<options.preview>",
  "generated": "<when>",
  "body": "<html>",
  "trapped": "<options.trapped>",
  "social": "<options.social>",
  "styles": "<options.style>",
  "linkedData": "<options.linkedData>"
}

In this case, the _header variable would contain whether a header image was provided. Then, generated contains the moment the email was rendered, passing the 'YYYY/MM/DD HH:mm, UTC Z' format string to moment. Lastly, trapped contains the metadata extracted from the model when trap is set to a truthy value, in the client options.

Styling the layout

These are the default styles, and you can override them in the options passed to client.send.

{
  "styles": {
    "bodyBackgroundColor": "#eaeadf",
    "bodyTextColor": "#505050",
    "codeFontFamily": "Consolas, Menlo, Monaco, 'Lucida Console', 'Liberation Mono', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New', monospace, serif",
    "fontFamily": "Helvetica",
    "footerBackgroundColor": "#f4f4f4",
    "headerColor": "#412917",
    "horizontalBorderColor": "#dedede",
    "layoutBackgroundColor": "#f3f4eb",
    "layoutTextColor": "#808080",
    "linkColor": "#e92c6c",
    "quoteBorderColor": "#cbc5c0"
  }
}

Custom layouts should either abide by these style rule names, or provide entirely new ones.

Unsubscribe Facilities

Here's a perfect use case for merge variables, which were described above in the send options. While many email service providers offer a way to unsubscribe readers, their implementations don't quite align to one another, so we favor using merge variables instead.

The default layout supports an optional unsubscribe_html merge variable, which can be filled out like below. This is rendered in the footer of every email campaign sends out.

{
  "merge": {
    "[email protected]": {
      "unsubscribe_html": "<a href='http://sth.ng/unsubscribe/hash_for_someone'>unsubscribe</a>"
    },
    "[email protected]": {
      "unsubscribe_html": "<a href='http://sth.ng/unsubscribe/hash_for_someone_else'>unsubscribe</a>"
    }
  }
}

Remember, those are supported by Mandrill, Mailgun, and SparkPost, but not every provider supports merge variables. Merge variables are processed after you make a request to their API, with the provider replacing them with the values assigned to each recipient. For more detail on merge variables for each provider you can read these docs:

Debugging

To help you debug, an alternative provider is available. Set it up like this:

var campaign = require('campaign');
var client = campaign({
  provider: require('campaign-terminal')
});

// build and send mails as usual

Rather than actually sending emails, you will get a bit of JSON output in your terminal, and the Markdown representation of your email's body HTML. Super useful during development!

terminal.png

Providers

There are a few different providers you can use. The recommended provider is to send emails through campaign-mailgun. There is also a campaign-terminal logging provider, explained above, and a nodemailer provider, detailed below.

Creating custom providers

If the existing providers don't satisfy your needs, you may provide your own. The provider option just needs to be an object with a send method.

To create your own email-sending provider, you'll need to create a module that implements the interface methods found below. See campaign-mailgun for an example on how you could implement your own email-sending provider.

{
  name: 'my-custom-provider', // mostly debugging purposes
  send: function (model, done) {
    // use the data in the model to send your email messages
  },
  tweakPlaceholder: function (property, raw) {
    // used to explain how merge variables should be rendered in the template, e.g:
    if (raw) {
      return '${HTML:' + property + '}';
    }
    return '${' + property + '}';
  }
}

If you decide to go for your own provider, campaign will still prove useful thanks to its templating features, which you can also extend!

Template Engines

The default provider included with campaign allows us to render layouts and views using mustache, but this behavior can be altered to use a custom templating engine.

To create your own template engine, you'll need to create a module that implements the interface methods found below. See campaign-jadum for an example on how you could implement your own template engine.

{
  render: function (file, model, done) {
  },
  renderString: function (template, model, done) {
  },
  defaultLayout: '/path/to/default/layout'
}

The done callback takes an error as the first argument, and the resulting HTML as the second argument.

Contributing

You're welcome to contribute to the development of campaign! Additional template engines and providers would be nice, and I'd encourage creating packages that solely contain that engine or email provider. For instance, you could create campaign-ejs, or campaign-postmark.

Hmmm, yeah. That'd be great!

Lovely Internet meme

License

MIT

More Repositories

1

dragula

👌 Drag and drop so simple it hurts
JavaScript
21,936
star
2

es6

🌟 ES6 Overview in 350 Bullet Points
4,328
star
3

rome

📆 Customizable date (and time) picker. Opt-in UI, no jQuery!
JavaScript
2,913
star
4

js

🎨 A JavaScript Quality Guide
2,874
star
5

fuzzysearch

🔮 Tiny and blazing-fast fuzzy search in JavaScript
JavaScript
2,711
star
6

woofmark

🐕 Barking up the DOM tree. A modular, progressive, and beautiful Markdown and HTML editor
JavaScript
1,624
star
7

promisees

📨 Promise visualization playground for the adventurous
JavaScript
1,199
star
8

horsey

🐴 Progressive and customizable autocomplete component
JavaScript
1,167
star
9

css

🎨 CSS: The Good Parts
992
star
10

react-dragula

👌 Drag and drop so simple it hurts
JavaScript
992
star
11

contra

🏄 Asynchronous flow control with a functional taste to it
JavaScript
771
star
12

shots

🔫 pull down the entire Internet into a single animated gif.
JavaScript
728
star
13

insignia

🔖 Customizable tag input. Progressive. No non-sense!
JavaScript
674
star
14

perfschool

🌊 Navigate the #perfmatters salt marsh waters in this NodeSchool workshopper
CSS
630
star
15

local-storage

🛅 A simplified localStorage API that just works
JavaScript
523
star
16

angularjs-dragula

👌 Drag and drop so simple it hurts
HTML
509
star
17

reads

📚 A list of physical books I own and read
486
star
18

insane

😾 Lean and configurable whitelist-oriented HTML sanitizer
JavaScript
449
star
19

hget

👏 Render websites in plain text from your terminal
HTML
334
star
20

hit-that

✊ Render beautiful pixel perfect representations of websites in your terminal
JavaScript
332
star
21

hash-sum

🎊 Blazing fast unique hash generator
JavaScript
301
star
22

swivel

Message passing between ServiceWorker and pages made simple
JavaScript
294
star
23

dominus

💉 Lean DOM Manipulation
JavaScript
277
star
24

trunc-html

📐 truncate html by text length
JavaScript
220
star
25

grunt-ec2

📦 Create, deploy to, and shutdown Amazon EC2 instances
JavaScript
190
star
26

beautify-text

✒️ Automated typographic quotation and punctuation marks
JavaScript
186
star
27

sixflix

🎬 Detects whether a host environment supports ES6. Algorithm by Netflix.
JavaScript
175
star
28

twitter-for-github

🐥 Twitter handles for GitHub
JavaScript
146
star
29

awesome-badges

🏆 Awesome, badges!
JavaScript
124
star
30

prop-tc39

Scraping microservice for TC39 proposals 😸
JavaScript
108
star
31

megamark

😻 Markdown with easy tokenization, a fast highlighter, and a lean HTML sanitizer
JavaScript
104
star
32

diferente

User-friendly virtual DOM diffing
JavaScript
95
star
33

domador

😼 Dependency-free and lean DOM parser that outputs Markdown
JavaScript
86
star
34

proposal-undefined-coalescing-operator

Undefined Coalescing Operator proposal for ECMAScript
77
star
35

dotfiles

💠 Yay! @bevacqua does dotfiles \o/
Shell
75
star
36

assignment

😿 Assign property objects onto other objects, recursively
JavaScript
73
star
37

sektor

📍 A slim alternative to jQuery's Sizzle
JavaScript
65
star
38

map-tag

🏷 Map template literal expression interpolations with ease.
JavaScript
65
star
39

unbox

Unbox a node application with a well-designed build-oriented approach in minutes
JavaScript
61
star
40

hint

Awesome tooltips at your fingertips
JavaScript
60
star
41

but

🛰 But expands your functional horizons to the edge of the universe
JavaScript
59
star
42

kanye

Smash your keyboards with ease
JavaScript
55
star
43

correcthorse

See XKCD for reference
JavaScript
52
star
44

easymap

🗺 simplified use of Google Maps API to render a bunch of markers.
JavaScript
52
star
45

flickr-cats

A demo page using the Flickr API, ServiceWorker, and plain JavaScript
HTML
49
star
46

hubby

👨 Hubby is a lowly attempt to describe public GitHub activity in natural language
JavaScript
46
star
47

ruta3

Route matcher devised for shared rendering JavaScript applications
JavaScript
45
star
48

poser

📯 Create clean arrays, or anything else, which you can safely extend
JavaScript
45
star
49

baal

🐳 Automated, autoscaled, zero-downtime, immutable deployments using plain old bash, Packer, nginx, Node.js, and AWS. Made easy.
Shell
44
star
50

lipstick

💄 sticky sessions for Node.js clustering done responsibly
JavaScript
43
star
51

crossvent

🌏 Cross-platform browser event handling
JavaScript
41
star
52

lazyjs

The minimalist JavaScript loader
JavaScript
39
star
53

spritesmith-cli

😳 Adds a CLI to the spritesmith module
JavaScript
38
star
54

gulp-jsfuck

Fuck JavaScript and obfuscate it using only 6 characters ()+[]!
JavaScript
37
star
55

measly

A measly wrapper around XHR to help you contain your requests
JavaScript
36
star
56

keynote-extractor

🎁 Extract Keynote presentations to JSON and Markdown using a simple script.
AppleScript
35
star
57

hyperterm-working-directory

🖥👷📂 Adds a default working directory setting. Opens new tabs using that working directory.
JavaScript
34
star
58

gitcanvas

🏛 Use your GitHub account's commit history as a canvas. Express the artist in you!
JavaScript
34
star
59

cave

Remove critical CSS from your stylesheet after inlining it in your pages
JavaScript
33
star
60

scrape-metadata

📜 HTML metadata scraper
JavaScript
31
star
61

feeds

🍎 RSS feeds I follow and maintain
31
star
62

suchjs

Provides essential jQuery-like methods for your evergreen browser, in under 200 lines of code. Such small.
JavaScript
30
star
63

ponyedit

An interface between contentEditable and your UI
JavaScript
29
star
64

grunt-grunt

Spawn Grunt tasks in other Gruntfiles easily from a Grunt task
JavaScript
29
star
65

ultramarked

Marked with built-in syntax highlighting and input sanitizing that doesn't encode all HTML.
JavaScript
28
star
66

sell

💰 Cross-browser text input selection made simple
JavaScript
28
star
67

icons

Free icon sets gathered around the open web
27
star
68

insert-rule

Insert rules into a stylesheet programatically with a simple API
JavaScript
26
star
69

hose

Redirect any domain to localhost for convenience or productivity!
JavaScript
26
star
70

ponymark

Next-generation PageDown fork
JavaScript
25
star
71

omnibox

Fast url parsing with a tiny footprint and extensive browser support
JavaScript
25
star
72

estimate

Calculate remaining reading time estimates in real-time
JavaScript
24
star
73

node-emoji-random

Creates a random emoji string. This is as useless as it gets.
JavaScript
24
star
74

sluggish

🐍 Sluggish slug generator that works universally
JavaScript
24
star
75

seleccion

💵 A getSelection polyfill and a setSelection ranch dressing
JavaScript
24
star
76

bullseye

🎯 Attach elements onto their target
JavaScript
23
star
77

vectorcam

🎥 Record gifs out of <svg> elements painlessly
JavaScript
22
star
78

paqui

Dead simple, packager-agnostic package management solution for front-end component developers
JavaScript
22
star
79

grunt-ngdoc

Grunt task for generating documentation using AngularJS' @ngdoc comments
JavaScript
20
star
80

ftco

⚡ Browser extension that unshortens t.co links in TweetDeck and Twitter
JavaScript
19
star
81

jadum

💍 A lean Jade compiler that understands Browserify and reuses partials
JavaScript
19
star
82

trunc-text

📏 truncate text by length, doesn't cut words
JavaScript
16
star
83

flexarea

Pretty flexible areas!
JavaScript
16
star
84

music-manager

📻 Manages a list of favorite artists and opens playlists on youtube.
JavaScript
16
star
85

grunt-integration

Run Integration Tests using Selenium, Mocha, a Server, and a Browser
JavaScript
15
star
86

apartment

🏡 Remove undesirable properties from a piece of css
JavaScript
14
star
87

mongotape

Run integration tests using mongoose and tape
JavaScript
14
star
88

rehearsal

Persist standard input to a file, then simulate real-time program execution.
JavaScript
13
star
89

queso

Turn a plain object into a query string
JavaScript
13
star
90

bitfin

🏦 Finance utility for Bitstamp
JavaScript
13
star
91

pandora-box

🐼 What will it be?
JavaScript
12
star
92

grunt-spriting-example

An example on how to seamlessly use spritesheets with Grunt.
12
star
93

artists

🎤 Big list of artists pulled from Wikipedia.
JavaScript
11
star
94

twitter-leads

🐦 Pull list of leads from a Twitter Ads Lead Generation Card
JavaScript
11
star
95

reaver

Minimal asset hashing CLI and API
JavaScript
11
star
96

atoa

Creates a true array based on `arraylike`, starting at `startIndex`.
JavaScript
10
star
97

BridgeStack

.NET StackExchange API v2.0 client library wrapper
C#
10
star
98

ama

📖 A repository to ask @bevacqua anything.
10
star
99

virtual-host

Create virtual, self-contained `connect` or `express` applications using a very simple API.
JavaScript
10
star
100

banksy

🌇 Street art between woofmark and horsey
JavaScript
10
star