GithubHelp home page GithubHelp logo

jfrolich / smoothie Goto Github PK

View Code? Open in Web Editor NEW
45.0 3.0 5.0 69 KB

Beautiful emails for your elixir application

License: MIT License

Elixir 30.02% HTML 69.45% CSS 0.53%
elixir-lang elixir email css stylesheets smoothie

smoothie's Introduction

Smoothie

Stylesheet inlining and plain text template generation for your email templates.

Follow the installation instructions to set up Smoothie. After that we can use it in the following way in our project:

Let's suppose we are using the excellent Mailgun library to send our emails. Then we set up a Mailer module in the following location: web/mailers/mailer.ex, with the following content:

defmodule MyApp.Mailer do
  # your mailgun config here
  @config %{...}
  use Mailgun.Client, @config

  def welcome_email(user) do
    send_email to: user.email_address,
               from: "[email protected]",
               subject: "Welcome!",
               text: "Welcome #{user.name}"
    :ok
  end
end

Pretty boring right. So lets add smoothie. First we need a layout, lets try this one (save as: web/mailers/templates/layouts/main.html.eex):

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <style type="text/css">
    @media screen and (min-width: 581px) {
      .container {
        width: 580px !important;
      }
    }
  </style>
</head>
<body>
  <table class="body-wrap">
    <tr>
      <td class="container">
        <table>
          <tr>
            <td align="center" class="masthead">
              <h1><%= @title %></h1>
            </td>
          </tr>
          <tr>
            <td class="content">
              {content}
            </td>
          </tr>
        </table>
      </td>
    </tr>
    <tr>
      <td class="container">
        <table>
          <tr>
            <td class="content footer" align="center">
              <p>Sent by <a href="http://www.acme.com">Acme</a></p>
              <p><a href="mailto:[email protected]">[email protected]</a></p>
            </td>
          </tr>
        </table>
      </td>
    </tr>
  </table>
</body>
</html>

Also, lets add a stylesheet. We can use sass for this! save in web/mailers/templates/layout/style.scss. If you just want to use css you can save it as style.css.

$action-color: #50E3C2;

@media only screen and (min-device-width: 581px) {
  .container {
    width: 580px !important;
  }
}


// Global
* {
  margin: 0;
  padding: 0;
  font-size: 100%;
  font-family: 'Avenir Next', "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
  line-height: 1.65;
}

img {
  max-width: 100%;
  margin: 0 auto;
  display: block;
}

body,
.body-wrap {
  width: 100% !important;
  height: 100%;
  background: #efefef;
  -webkit-font-smoothing:antialiased;
  -webkit-text-size-adjust:none;
}

a {
  color: $action-color;
  text-decoration: none;
}

.text-center {
  text-align: center;
}

.text-right {
  text-align: right;
}

.text-left {
  text-align: left;
}

// Button
.button {
  display: inline-block;
  color: white;
  background: $action-color;
  border: solid $action-color;
  border-width: 10px 20px 8px;
  font-weight: bold;
  border-radius: 4px;
}

// Typography
h1, h2, h3, h4, h5, h6 {
  margin-bottom: 20px;
  line-height: 1.25;
}
h1 {
  font-size: 32px;
}
h2 {
  font-size: 28px;
}
h3 {
  font-size: 24px;
}
h4 {
  font-size: 20px;
}
h5 {
  font-size: 16px;
}

p, ul, ol {
  font-size: 16px;
  font-weight: normal;
  margin-bottom: 20px;
}

// layout
.container {
  display: block !important;
  clear: both !important;
  margin: 0 auto !important;
  max-width: 580px !important;

  table {
    width: 100% !important;
    border-collapse: collapse;
  }

  .masthead {
    padding: 80px 0;
    background: #50E3C2;
    color: white;

    h1 {
      margin: 0 auto !important;
      max-width: 90%;
      text-transform: uppercase;
    }
  }

  .content {
    background: white;
    padding: 30px 35px;

    &.footer {
      background: none;

      p {
        margin-bottom: 0;
        color: #888;
        text-align: center;
        font-size: 14px;
      }

      a {
        color: #888;
        text-decoration: none;
        font-weight: bold;
      }
    }
  }
}

Now create the template for our email, we can save this in web/mailers/templates/welcome.html.eex

Optionally adding additional css styling specific for this template partial is possible using <style> </style> tags.

<style>
    .inner-template{
        font-size: 120%;
        color: lightgreen;
    }
</style>

<h2>Hi <%= @name %>,</h2>

<p class="inner-template">Welcome!</p>

<p>Cheers,</p>

<p><em>β€”The Acme</em></p>

Alright we're all set up, lets make sure this template works in Smoothie:

defmodule MyApp.Mailer do
  # your mailgun config here
  @config %{...}
  use Mailgun.Client, @config
  use Smoothie,
    template_dir: "templates",
    layout_file: Path.join("layouts", "main.html.eex")



  def welcome_email(user) do
    template_params = [
      title: "Big Welcome!",
      name: user.name,
    ]

    send_email to: user.email_address,
               from: "[email protected]",
               subject: "Welcome!",
               text: welcome_text(template_params),
               html: welcome_html(template_params)
    :ok
  end
end

Additionally to enable smoothie compilation on this module we need to add the module to the configuration (config/config.exs):

config :smoothie, modules: [MyApp.Mailer]

The last thing to do is to compile the templates, we need to do this every time when we change the templates or the layout:

> mix smoothie.compile
Preparing to compile the following template files:
- welcome.html.eex
Created welcome.html.eex
Created welcome.txt.eex
Done πŸ™

Done! Now we are able to send very beautifully styled templates with styles inlined so it works in every email client, and for we also have a nice plain text version of the email!

Text templates

Smoothie automatically generates txt.eex templates from your html.eex files. If you don't like them, you can provide your own txt.eex templates by saving them to the same directory as your html.eex files.

/build
/layout
welcome.html.eex
welcome.txt.eex

To skip generating text templates altogether, set :html_only in your config,

config :smoothie, html_only: true

or run mix smoothie.compile --html-only.

Installation

Smoothie can be installed as:

  1. Add smoothie to your list of dependencies in mix.exs:
def deps do
  [{:smoothie, "~> 3.0"}]
end
  1. Ensure smoothie is started before your application:
def application do
  [applications: [:smoothie]]
end
  1. The only thing left is install the npm package that smoothie relies on in your project, we can do this with the following command:
  > mix smoothie.init

Compile with layout

  > mix smoothie.compile

Smoothie needs to install a npm library to do the css inlining, so make sure you have npm initialized in your project (a package.json file in your project's root)

Tests

yarn install && mix test

TODO

  • Create example usage repository (and link to README)

smoothie's People

Contributors

aaronjensen avatar eirenauts-infra avatar jayjun avatar jfrolich avatar stephenmoloney avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

smoothie's Issues

Smoothie Escaping Function `->`

Hi there,

Not sure if I'm doing something incorrectly, but trying to repeat some HTML blocks in a template that is to be processed by smoothie:

                        <%= Enum.map child.activities, fn(activity) -> %>
                          <tr>
                            <td><%= activity.details %></td>
                            <td><%= activity.display_time %></td>
                          </tr>
                        <% end %>

It would appear that smoothie is escaping the -> at the end of the function literal:

                        <%= Enum.map child.activities, fn(activity) -&gt; %>
                          <tr>
                            <td style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt;"><%= activity.details %></td>
                            <td style="-webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt;"><%= activity.display_time %></td>
                          </tr>
                        <% end %>

Note it has compiled to a -&gt;.

Is there a way I'm supposed to be doing this differently?

Unused variable warnings

Using <%= results in unused variable warnings. 2.0.5 fixed issues with characters being escaped improperly, but currently are only good for passing through template params.

I would like to have absolute urls for my images and I would do that like this: <%= MyApp.Router.Helpers.static_url(MyApp.Endpoint, ~s(/images/img.png)) %>

This works as expected, but I can't do this because it emits a warning that this is an unused variable.

A possible fix for this would be, instead of pulling out everything between <%= as a variable only extracting @.... This is how eex templates work in the first place.

allow for multiple smoothie configurations

problem:

Project might have more than one smoothie layout for different email purposes

potential solution

Allow smoothie to be configured for multiple template_directories and multiple layouts.

_Note:_
I've made some progress towards this ... it has required work on elixir-smoothie too, I've a bit more to go on this but I think it would be an enhancement.

Let me know what you think...

Smoothie Reference branch
Elixir Smoothie Reference branch

Edit:

See PR.. #4
and companion PR https://github.com/jfrolich/elixir-smoothie

mix smoothie.init: ** (ArgumentError) all arguments for System.cmd/3 must be binaries

I am trying to use smoothie with "phoenix 1.5.4". After mix deps.get, I get an error on "mix smoothie.init"

% mix smoothie.init ** (ArgumentError) all arguments for System.cmd/3 must be binaries (elixir 1.10.4) lib/system.ex:786: System.cmd/3 (mix 1.10.4) lib/mix/task.ex:330: Mix.Task.run_task/3 (mix 1.10.4) lib/mix/cli.ex:82: Mix.CLI.run_task/2 (elixir 1.10.4) lib/code.ex:926: Code.require_file/2
I assume this is from init.ex:
System.cmd "npm", ["i", @package_version, "--save-dev", @new_opts]

Has anyone else seen this? Or is there something weird about my config?

Code available here: https://github.com/TheSwanFactory/igwet/tree/smoothie

Smoothie should use @external_resource to declare its dependency on built templates

Right now, if a built template changes w/o running smoothie.compile (as it may if you were to check out a different branch that had a new template), the templates module will not be rebuilt and you may get compile errors.

I believe the proper thing for smoothie to do is to declare its dependency upon external files like the built templates.

Right now I'm working around this like this in my module that has use Smoothie

template_files_glob = Path.join(__DIR__, "templates/build/*")
template_files_glob
|> Path.wildcard
|> Enum.filter_map(&File.regular?/1, fn file ->
  @external_resource file
end)

[Proposal] Let the user enter the full path

@jfrolich
Just getting back to smoothie now. Have been busy.

Wonder what you thought about the idea that we let the user enter the full path to the template
directory and other directories - rather than inferring them inside Smootie..
In other words remove logic such as:

@template_path Path.join(@smoothie_path, @template_dir)
@layout_path if @layout_file, do: Path.join(@smoothie_path, @layout_file)

and let the user enter the paths?

Html only option doesn't seem to work

I just upgraded to smoothie 3.1.1, and I've trie both the config option (config :smoothie, html_only: true) and the command line flag (mix smoothie.compile --html-only), but in all cases I still get text templates being generated.

Allow disabling text template generation

The text template generation leaves quite a bit to be desired so we tend to write our own text templates manually.

For some reason, templates generated by smoothie are super slow with dialyzer.

It'd be great if we could disable text template generation so we could reduce the size of the template module and hopefully reduce the time it takes dialyzer to process the template module.

Is inlining still necessary?

Most clients now support the <style> tag, so is inlining still necessary. Perhaps it makes sense to deprecate smoothie or make the scope smaller and deprecate the style inlining?

compilation deadlock issue

@jfrolich
I think I'm getting a compilation dealock issue but I can't be sure since I'm finding it difficult to reproduce...

Have you tried running mix do clean, deps.clean smoothie, deps.get smoothie, compile && mix smoothie.compile in your project without any issue?

Hopefully I can figure this out soon. Sorry, I can't be more specific for now.

<template_dir>/build: no such file or directory

When adding smoothie to my project I added the following config:

config :smoothie, template_dir: Path.join(["web", "templates", "email"])

But when I try to run mix deps.compile smoothie I get a compilation error on lib/smoothie.ex:15. It seems to be appending build to the template_dirpath but I couldn't find anything about this in the documentation.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.