├── .gitignore ├── src ├── templates │ ├── css │ │ ├── media-queries.css │ │ └── main.css │ ├── partials │ │ ├── preheader.html │ │ └── footer.html │ └── basic-template.html └── panel │ └── index.html ├── config-sample.json ├── package.json ├── panel ├── js │ └── main.js └── css │ └── panel.css ├── gulpfile.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | kontist-basic-template.html 4 | config.json 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /src/templates/css/media-queries.css: -------------------------------------------------------------------------------- 1 | @media screen and (min-width: 600px) { 2 | .preheaderContent { 3 | padding: 40px 0 30px !important; 4 | } 5 | .bodyContent { 6 | padding: 40px 40px 0!important; 7 | } 8 | .button a{ 9 | margin-top: 40px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /config-sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "testing": { 3 | "from": "from-email", 4 | "to": "recepit-email", 5 | "subject": "your-subject-line" 6 | }, 7 | 8 | "auth": { 9 | "mailgun": { 10 | "apikey": "your-api-key", 11 | "sandbox": "your-sandbox-url" 12 | }, 13 | "mandrill": { 14 | "apikey": "your-api-key" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/templates/partials/preheader.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 |
4 | 5 | 6 | 9 | 12 | 13 |
7 | 8 | 10 | Your brand claim 11 |
14 |
17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "email-framework", 3 | "version": "1.0.0", 4 | "description": "A simple framework for working with responsive email", 5 | "main": "index.html", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/joshuasoehn/email-framework.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/joshuasoehn/email-framework/issues" 17 | }, 18 | "homepage": "https://github.com/joshuasoehn/email-framework#readme", 19 | "devDependencies": { 20 | "browser-sync": "^2.17.5", 21 | "glob": "^7.1.1", 22 | "gulp": "^4.0.2", 23 | "gulp-file-include": "^1.0.0", 24 | "gulp-filenames": "^4.0.1", 25 | "gulp-inline-css": "^3.1.0", 26 | "gulp-mailgun": "0.0.7", 27 | "gulp-template": "^4.0.0", 28 | "gulp-util": "^3.0.7" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/templates/partials/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 24 | 25 |
6 | 7 | 8 | 12 | 13 |
14 | 15 | 16 | 21 | 22 |
17 | 20 |
23 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 39 | 40 |
34 |
35 | This email template was prepared for you by your friends at Kontist.
36 | Logo and image from Mailchimp. 37 |
38 |
41 | -------------------------------------------------------------------------------- /src/panel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Email Testing Panel 11 | 12 | 13 | 23 |
24 |
25 | 26 | 27 | 28 | 29 |
30 |
31 |
32 | 33 |
34 |
35 | 36 |
37 |
38 |
39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /panel/js/main.js: -------------------------------------------------------------------------------- 1 | var activeTemplate; 2 | var config; 3 | 4 | $(function() { 5 | 6 | $.getJSON( "/config.json", function( data ) { 7 | config = data; 8 | }); 9 | 10 | activeTemplate = window.location.hash.split('#')[1]; 11 | $('iframe').attr("src", activeTemplate); 12 | 13 | $('.template-list li a[href="' + activeTemplate + '"]').addClass('active'); 14 | 15 | $('.template-list li a').click(function(e){ 16 | e.preventDefault(); 17 | activeTemplate = $(this).attr('href'); 18 | $('.template-list li a').removeClass('active') 19 | $(this).addClass('active'); 20 | console.log(activeTemplate); 21 | $('iframe').attr("src", activeTemplate); 22 | window.location.hash = activeTemplate; 23 | }); 24 | 25 | $('.send-button').click(function(){ 26 | $.ajax({ 27 | type: 'GET', 28 | url: activeTemplate 29 | }).done(function(templateContent) { 30 | console.log(templateContent); 31 | $.ajax({ 32 | type: 'POST', 33 | url: 'https://mandrillapp.com/api/1.0/messages/send.json', 34 | data: { 35 | 'key': config.auth.mandrill.apikey, 36 | 'message': { 37 | 'from_email': config.testing.from, 38 | 'to': [ 39 | { 40 | 'email': config.testing.to, 41 | 'type': 'to' 42 | } 43 | ], 44 | 'autotext': 'true', 45 | 'subject': config.testing.subject, 46 | 'html': templateContent 47 | } 48 | } 49 | }).done(function(response) { 50 | console.log(response); // if you're into that sorta thing 51 | }); 52 | }); 53 | 54 | }); 55 | 56 | }); 57 | -------------------------------------------------------------------------------- /panel/css/panel.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | border: 0; 6 | outline: 0; 7 | } 8 | 9 | html, body { 10 | height: 100%; 11 | min-height: 100%; 12 | } 13 | 14 | body { 15 | font-family: -apple-system, BlinkMacSystemFont, "Roboto", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; 16 | font-size: 16px; 17 | background: #E0E0E0; 18 | display: flex; 19 | } 20 | 21 | a { 22 | color: #111; 23 | text-decoration: none; 24 | transition: all .3s ease; 25 | } 26 | 27 | a:hover, 28 | a.active { 29 | color: #0097A7; 30 | } 31 | 32 | h3 { 33 | margin-bottom: 40px; 34 | } 35 | 36 | ul { 37 | list-style: none; 38 | } 39 | 40 | li { 41 | margin-bottom: 20px; 42 | } 43 | 44 | aside { 45 | background: #fff; 46 | padding: 40px; 47 | width: 300px; 48 | box-shadow: 0 0 2px rgba(0,0,0,.4); 49 | overflow-y: scroll; 50 | } 51 | 52 | .wrapper { 53 | width: 100%; 54 | display: flex; 55 | height: 100vh; 56 | justify-content: center; 57 | align-items: center; 58 | } 59 | 60 | .email-previews { 61 | display: flex; 62 | align-items: flex-end; 63 | padding: 0 100px; 64 | } 65 | 66 | iframe { 67 | width: 100%; 68 | height: 100%; 69 | } 70 | 71 | .send-button { 72 | display: flex; 73 | align-items: center; 74 | justify-content: center; 75 | width: 60px; 76 | height: 60px; 77 | background: #0097A7; 78 | border-radius: 50%; 79 | position: absolute; 80 | top: 40px; 81 | right: 40px; 82 | cursor: pointer; 83 | transition: all .3s ease; 84 | box-shadow: 0 0 3px rgba(0,0,0,0.12),0 3px 3px rgba(0,0,0,0.24),0 0 6px rgba(0,0,0,0.12),0 6px 6px rgba(0,0,0,0.24) 85 | } 86 | 87 | .send-button svg { 88 | fill: #fff; 89 | } 90 | 91 | .send-button:hover { 92 | box-shadow: 0 0 6px rgba(0,0,0,0.12),0 6px 6px rgba(0,0,0,0.24),0 0 12px rgba(0,0,0,0.12),0 12px 12px rgba(0,0,0,0.24) 93 | } 94 | 95 | .preview { 96 | border-radius: 2px; 97 | box-shadow: 0 10px 20px rgba(0,0,0,.2); 98 | } 99 | 100 | .preview-desktop { 101 | width: 1000px; 102 | height: 800px; 103 | margin-right: 60px; 104 | } 105 | 106 | .preview-mobile { 107 | width: 375px; 108 | height: 600px; 109 | } 110 | 111 | @media (max-width: 1600px) { 112 | .preview-desktop { 113 | width: 800px; 114 | height: 600px; 115 | margin-right: -200px; 116 | } 117 | .preview-mobile { 118 | height: 500px; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var browserSync = require('browser-sync').create(); 3 | var reload = browserSync.reload; 4 | var inlineCss = require('gulp-inline-css'); 5 | var fileinclude = require('gulp-file-include'); 6 | var sendmail = require('gulp-mailgun'); 7 | var template = require('gulp-template'); 8 | var glob = require("glob") 9 | 10 | 11 | var config = require('./config.json'); 12 | 13 | 14 | // Static server 15 | gulp.task('browser-sync', function() { 16 | browserSync.init({ 17 | server: { 18 | baseDir: "./" 19 | }, 20 | startPath: "/panel/" 21 | }); 22 | }); 23 | 24 | 25 | // Build Panel 26 | gulp.task('build-panel', function() { 27 | glob("./src/templates/*.html", {}, function(er, files) { 28 | var templates = files.map(function(file) { 29 | var pathArray = file.split("/"); 30 | var fileName = pathArray[pathArray.length - 1]; 31 | var templateName = fileName.split(".")[0]; 32 | return { 33 | name: templateName, 34 | path: "/build/" + fileName 35 | }; 36 | }); 37 | return gulp.src('./src/panel/index.html') 38 | .pipe(template({ 39 | templates: templates 40 | })) 41 | .pipe(gulp.dest('./panel')); 42 | }); 43 | }); 44 | 45 | // Build Templates 46 | gulp.task('build-templates', function() { 47 | return gulp.src('./src/templates/*.html') 48 | .pipe(fileinclude({ 49 | prefix: '@@', 50 | basepath: '@file' 51 | })) 52 | .pipe(inlineCss({ 53 | preserveMediaQueries: true 54 | })) 55 | .pipe(gulp.dest('./build/')); 56 | }); 57 | 58 | // Watch Files For Changes And Reload 59 | gulp.task('watch', function() { 60 | gulp.watch('./src/panel/**/*.html', ['build-panel', reload]); 61 | gulp.watch('./src/templates/**/*.html', ['build-templates', reload]); 62 | gulp.watch('./src/**/css/*.css', ['build-templates', reload]); 63 | gulp.watch('./**/*.html', reload); 64 | gulp.watch('./**/css/*.css', reload); 65 | }); 66 | 67 | // Add ability to send test emails 68 | gulp.task('send', function() { 69 | gulp.src('./build/basic-template.html') 70 | .pipe(sendmail({ 71 | key: config.auth.mailgun.apikey, 72 | sender: config.testing.from, 73 | recipient: config.testing.to, 74 | subject: config.testing.subject 75 | })); 76 | }); 77 | 78 | gulp.task('default', ['build-panel', 'browser-sync', 'build-templates', 'watch']); -------------------------------------------------------------------------------- /src/templates/basic-template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Email Title 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 62 | 63 |
23 | 24 | @@include('./partials/preheader.html') 25 | 26 | 27 | 28 | 29 | 41 | 42 | 43 | 44 | 53 | 54 | 55 | @@include('./partials/footer.html') 56 | 57 | 58 | 59 |
30 | 31 | 32 | 38 | 39 |
33 |

Heading 1

34 |

Getting started: Customize your template by clicking on the style editor tabs up above. Set your fonts, colors, and styles. After setting your styling is all done you can click here in this area, delete the text, and start adding your own awesome content! 35 | 36 | After you enter your content, highlight the text you want to style and select the options you set in the style editor in the "styles" drop down box. Want to get rid of styling on a bit of text, but having trouble doing it? Just use the "clear styles" button to strip the text of any formatting and reset your style.

37 |
40 |
45 | 46 | 47 | 50 | 51 |
48 | Read More Stories On Our Blog 49 |
52 |
60 | 61 |
64 | 65 | 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gulp Email Framework 2 | 3 | A simple, gulp powered framework to develop and test responsive emails. Inspired by [Gulp Email Creator](https://github.com/darylldoyle/Gulp-Email-Creator) and the [Grunt Email Workflow](https://github.com/leemunroe/grunt-email-workflow). 4 | 5 | ![email template screenshot](https://cloud.githubusercontent.com/assets/4403029/19772299/0ea6066e-9c66-11e6-97f3-c413eadbefa6.png) 6 | 7 | 8 | ## Installation 9 | 10 | Clone this repository to a local folder and run: 11 | ``` 12 | npm install 13 | ``` 14 | 15 | ## Features 16 | 17 | ### A basic starter template 18 | This framework comes with a ready to use sample start template, which can be modified how you like it. All the used example assets are ©Mailchimp. 19 | 20 | ### Automatic CSS Inline and build system 21 | The build systems inlines all the css autmaticly, but keeps your media queries in the head tag, so the build email is ready to be used in any service. 22 | 23 | ### No more reloading thanks to browser sync 24 | Browser sync is keeping all the adjustments you make to the email templates in sync and previews changes live, without manually reloading. 25 | 26 | ### Smart partials 27 | We are using gulp-file-include for includes, giving you the option to pass down props to partials like so 28 | ``` 29 | @@include('./var.html', { 30 | "name": "haoxin", 31 | "age": 12345, 32 | "socials": { 33 | "fb": "facebook.com/include", 34 | "tw": "twitter.com/include" 35 | }) 36 | ``` 37 | 38 | ### Sending test emails via Mailgun and Mandrill 39 | Use Mailgun to send preview emails to yourself using `gulp send` (please adjust the setting in the gulp file to the template you want to send). Or if you have an Mandrill you can send the email you are previewing right from the UI. 40 | 41 | ### A simple UI for designing your emails 42 | Preview and manage all your templates right from the UI and preview your email at two devices sizes at the same time, browser sync will keep all the iFrames in sync for you. 43 | 44 | ## How to use 45 | Rename the `config-sample.json` file to `config.json`. Add you api keys for mailgun and mandrill, if you want to use either of the service for sending test emails to yourself. Setting up an mailgun account for that is free, but it doesn't support sending emails from the UI. 46 | 47 | ``` 48 | { 49 | "testing": { 50 | "from": "from-email", 51 | "to": "recepit-email", 52 | "subject": "your-subject-line" 53 | }, 54 | 55 | "auth": { 56 | "mailgun": { 57 | "apikey": "your-api-key", 58 | "sandbox": "your-sandbox-url" 59 | }, 60 | "mandrill": { 61 | "apikey": "your-api-key" 62 | } 63 | } 64 | } 65 | 66 | ``` 67 | 68 | Run the framework by using 69 | ``` 70 | gulp 71 | ``` 72 | Now edit the template in 73 | ``` 74 | src/templates/basic-template.html 75 | ``` 76 | or create your own from scratch. As long as the gulp task is running it will automaticly build the template on every save and reload the browser. If you create a new template make sure to include it in the panel sidebar. 77 | ``` 78 |
79 | 83 |
84 | ``` 85 | in `panel/index.html`. 86 | 87 | Pull request and feedback welcome, as this is a very first version. 88 | Thanks a lot [Filip](https://github.com/peritus) for helping my with lots of the JS part. 89 | -------------------------------------------------------------------------------- /src/templates/css/main.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | body { 7 | font-family: -apple-system, BlinkMacSystemFont, "Roboto", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; 8 | background-color: #f6f6f6; 9 | -webkit-text-size-adjust: none; 10 | } 11 | 12 | .body-wrap { 13 | background-color: #f6f6f6; 14 | margin: 0; 15 | width: 100%; 16 | } 17 | 18 | table, 19 | td, 20 | tr { 21 | border-spacing: 0; 22 | padding: 0; 23 | } 24 | 25 | table { 26 | width: 100%; 27 | } 28 | 29 | img { 30 | display: block; 31 | border: none; 32 | height: auto; 33 | outline: none; 34 | max-width: 100%; 35 | width: 100%; 36 | max-width: 600px; 37 | } 38 | 39 | .wrapper { 40 | margin: 0 auto; 41 | padding: 15px; 42 | max-width: 600px !important; 43 | width: 100%; 44 | } 45 | 46 | .templateContainer { 47 | background: #fff; 48 | border-radius: 4px; 49 | overflow: hidden; 50 | border: 1px solid #eee; 51 | } 52 | 53 | h1,.h1{ 54 | color: #111; 55 | display: block; 56 | font-size: 34px; 57 | font-weight: bold; 58 | line-height: 100%; 59 | margin-bottom: 20px; 60 | text-align: left; 61 | } 62 | 63 | h2,.h2{ 64 | display: block; 65 | color: #111; 66 | font-size: 30px; 67 | font-weight: bold; 68 | line-height: 100%; 69 | margin-bottom: 10px; 70 | text-align: left; 71 | } 72 | 73 | h3,.h3{ 74 | font-size: 26px; 75 | display: block; 76 | color: #111; 77 | font-weight: bold; 78 | line-height: 100%; 79 | margin-bottom: 10px; 80 | text-align: left; 81 | } 82 | 83 | h4,.h4{ 84 | color: #111111; 85 | display: block; 86 | font-size: 22px; 87 | font-weight: bold; 88 | line-height: 100%; 89 | margin-bottom: 10px; 90 | text-align: left; 91 | } 92 | 93 | p { 94 | display: block; 95 | margin: 0; 96 | } 97 | 98 | .button a { 99 | display: inline-block; 100 | text-decoration: none; 101 | font-size: 14px; 102 | background: #348eda; 103 | color: #fff; 104 | padding: 15px 40px; 105 | border-radius: 4px; 106 | margin-top: 20px; 107 | font-weight: 600; 108 | } 109 | 110 | .preheaderContent { 111 | padding: 30px 0px; 112 | } 113 | 114 | .preheaderClaim { 115 | text-align: right; 116 | color: #666; 117 | font-size: 14px; 118 | } 119 | 120 | .headerContent img { 121 | width: 100%; 122 | display: block; 123 | } 124 | 125 | .preheaderContent .logo { 126 | height: auto; 127 | width: 60px; 128 | } 129 | 130 | .bodyContent { 131 | padding: 20px 20px 0; 132 | color: #333333; 133 | font-size: 16px; 134 | line-height: 150%; 135 | text-align: left; 136 | } 137 | 138 | 139 | .bodyContent a:link, 140 | .bodyContent a:visited { 141 | color: #348eda; 142 | font-weight: normal; 143 | text-decoration: underline; 144 | } 145 | 146 | .bodyContent img { 147 | display: block; 148 | width: 100%; 149 | margin: 20px 0; 150 | } 151 | 152 | .templateFullWidthImage img { 153 | margin-top: 40px; 154 | display: block; 155 | width: 100%; 156 | } 157 | 158 | .templateFooter{ 159 | margin-top: 20px; 160 | } 161 | 162 | .footerContent { 163 | color: #707070; 164 | font-size: 12px; 165 | line-height: 125%; 166 | } 167 | 168 | .footerContent img { 169 | display: inline; 170 | } 171 | 172 | .social { 173 | border-top: 1px solid #eee; 174 | border-bottom: 1px solid #eee; 175 | font-size: 14px; 176 | text-align: center; 177 | padding: 25px 0; 178 | } 179 | 180 | .social a { 181 | color: #348eda; 182 | text-decoration: none; 183 | line-height: 1.5 184 | font-size: 14px; 185 | } 186 | 187 | .templateAddress { 188 | color: #999; 189 | font-size: 12px; 190 | line-height: 125%; 191 | text-align: center; 192 | padding: 20px; 193 | } 194 | 195 | .templateAddress a { 196 | color: #999; 197 | font-size: 12px; 198 | text-decoration: underline; 199 | } 200 | 201 | .utility { 202 | color: #999; 203 | font-size: 12px; 204 | text-align: center; 205 | padding: 20px 0; 206 | } 207 | 208 | .utility a { 209 | color: #999; 210 | font-size: 12px; 211 | text-decoration: none; 212 | } 213 | --------------------------------------------------------------------------------