├── test
├── test_helper.exs
├── fixtures
│ └── templates
│ │ ├── email
│ │ ├── welcome.text.eex
│ │ ├── welcome.html.eex
│ │ ├── welcome_assigns.text.eex
│ │ ├── welcome.custom.eex
│ │ ├── welcome_assigns.html.eex
│ │ ├── format_html.htm.eex
│ │ ├── format_html.xml.eex
│ │ ├── format_text.txt.eex
│ │ ├── welcome_assigns.custom.eex
│ │ ├── format_html.html.eex
│ │ ├── format_text.text.eex
│ │ ├── format_text.unknown.eex
│ │ ├── format_html.custom.eex
│ │ └── welcome_from.html.eex
│ │ ├── layout
│ │ ├── email.text.eex
│ │ ├── email.html.eex
│ │ └── email.custom.eex
│ │ ├── test_view_included_notifier
│ │ ├── welcome_assigns.text.eex
│ │ └── welcome_assigns.html.eex
│ │ └── test_view_included_notifier_formats
│ │ ├── welcome_assigns.text.eex
│ │ └── welcome_assigns.custom.eex
└── phoenix_swoosh_test.exs
├── .formatter.exs
├── .github
├── workflows
│ ├── elixir.yml
│ ├── publish-docs.yml
│ ├── publish.yml
│ └── release.yml
├── dependabot.yml
└── release-drafter.yml
├── CONTRIBUTING.md
├── .gitignore
├── LICENSE.md
├── mix.exs
├── CHANGELOG.md
├── README.md
├── lib
└── phoenix_swoosh.ex
└── mix.lock
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------
/test/fixtures/templates/email/welcome.text.eex:
--------------------------------------------------------------------------------
1 | Welcome, Avengers!
2 |
--------------------------------------------------------------------------------
/test/fixtures/templates/email/welcome.html.eex:
--------------------------------------------------------------------------------
1 |
Welcome, Avengers!
2 |
--------------------------------------------------------------------------------
/test/fixtures/templates/layout/email.text.eex:
--------------------------------------------------------------------------------
1 | TEXT: <%= @inner_content %>
2 |
--------------------------------------------------------------------------------
/test/fixtures/templates/email/welcome_assigns.text.eex:
--------------------------------------------------------------------------------
1 | Welcome, <%= @name %>!
2 |
--------------------------------------------------------------------------------
/test/fixtures/templates/layout/email.html.eex:
--------------------------------------------------------------------------------
1 | <%= @inner_content %>
2 |
--------------------------------------------------------------------------------
/test/fixtures/templates/email/welcome.custom.eex:
--------------------------------------------------------------------------------
1 | Welcome, Avengers!
2 |
--------------------------------------------------------------------------------
/test/fixtures/templates/email/welcome_assigns.html.eex:
--------------------------------------------------------------------------------
1 | Welcome, <%= @name %>!
2 |
--------------------------------------------------------------------------------
/test/fixtures/templates/layout/email.custom.eex:
--------------------------------------------------------------------------------
1 | <%= @inner_content %>
2 |
--------------------------------------------------------------------------------
/test/fixtures/templates/email/format_html.htm.eex:
--------------------------------------------------------------------------------
1 | This is an HTML template with the .htm extension.
2 |
--------------------------------------------------------------------------------
/test/fixtures/templates/email/format_html.xml.eex:
--------------------------------------------------------------------------------
1 | This is an HTML template with the .xml extension.
2 |
--------------------------------------------------------------------------------
/test/fixtures/templates/email/format_text.txt.eex:
--------------------------------------------------------------------------------
1 | This is a text template with the .txt extension.
2 |
--------------------------------------------------------------------------------
/test/fixtures/templates/email/welcome_assigns.custom.eex:
--------------------------------------------------------------------------------
1 | Welcome, <%= @name %>!
2 |
--------------------------------------------------------------------------------
/test/fixtures/templates/test_view_included_notifier/welcome_assigns.text.eex:
--------------------------------------------------------------------------------
1 | Welcome, <%= @name %>!
2 |
--------------------------------------------------------------------------------
/test/fixtures/templates/email/format_html.html.eex:
--------------------------------------------------------------------------------
1 | This is an HTML template with the .html extension.
2 |
--------------------------------------------------------------------------------
/test/fixtures/templates/email/format_text.text.eex:
--------------------------------------------------------------------------------
1 | This is a text template with the .text extension.
2 |
--------------------------------------------------------------------------------
/test/fixtures/templates/email/format_text.unknown.eex:
--------------------------------------------------------------------------------
1 | This is a text template with an unknown extension.
2 |
--------------------------------------------------------------------------------
/test/fixtures/templates/email/format_html.custom.eex:
--------------------------------------------------------------------------------
1 | This is an HTML template with the .custom extension.
2 |
--------------------------------------------------------------------------------
/test/fixtures/templates/test_view_included_notifier/welcome_assigns.html.eex:
--------------------------------------------------------------------------------
1 | Welcome, <%= @name %>!
2 |
--------------------------------------------------------------------------------
/test/fixtures/templates/test_view_included_notifier_formats/welcome_assigns.text.eex:
--------------------------------------------------------------------------------
1 | Welcome, <%= @name %>!
2 |
--------------------------------------------------------------------------------
/.formatter.exs:
--------------------------------------------------------------------------------
1 | # Used by "mix format"
2 | [
3 | inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"]
4 | ]
5 |
--------------------------------------------------------------------------------
/test/fixtures/templates/email/welcome_from.html.eex:
--------------------------------------------------------------------------------
1 | <% {_name, address} = @email.from %>Welcome from <%= address %>
2 |
--------------------------------------------------------------------------------
/test/fixtures/templates/test_view_included_notifier_formats/welcome_assigns.custom.eex:
--------------------------------------------------------------------------------
1 | Welcome, <%= @name %>!
2 |
--------------------------------------------------------------------------------
/.github/workflows/elixir.yml:
--------------------------------------------------------------------------------
1 | name: Elixir CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | uses: swoosh/actions/.github/workflows/test.yml@main
8 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: mix
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: '10:00'
8 | open-pull-requests-limit: 10
9 |
--------------------------------------------------------------------------------
/.github/workflows/publish-docs.yml:
--------------------------------------------------------------------------------
1 | name: Publish Docs
2 |
3 | on: workflow_dispatch
4 |
5 | jobs:
6 | publish:
7 | uses: swoosh/actions/.github/workflows/publish.yml@main
8 | with:
9 | mode: 'docs'
10 | secrets:
11 | HEX_API_KEY: ${{ secrets.HEX_API_KEY }}
12 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 | on:
3 | push:
4 | tags:
5 | - 'v*'
6 | workflow_dispatch:
7 |
8 | jobs:
9 | publish:
10 | uses: swoosh/actions/.github/workflows/publish.yml@main
11 | with:
12 | mode: 'package'
13 | secrets:
14 | HEX_API_KEY: ${{ secrets.HEX_API_KEY }}
15 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release Drafter
2 |
3 | on:
4 | push:
5 | # branches to consider in the event; optional, defaults to all
6 | branches:
7 | - main
8 |
9 | jobs:
10 | update_release_draft:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: release-drafter/release-drafter@v5
14 | env:
15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
16 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Running tests
4 |
5 | Clone the repo and fetch its dependencies:
6 |
7 | ```bash
8 | $ git clone https://github.com/swoosh/phoenix_swoosh.git
9 | $ cd phoenix_swoosh
10 | $ mix deps.get
11 | $ mix test
12 | ```
13 |
14 | ## Building docs
15 |
16 | Documentation is written into the library, you will find it in the source code,
17 | accessible from `iex` and of course, it all gets published to
18 | [hexdocs](http://hexdocs.pm/phoenix_swoosh).
19 |
20 | ```bash
21 | $ mix docs
22 | ```
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # The directory Mix will write compiled artifacts to.
2 | /_build/
3 |
4 | # If you run "mix test --cover", coverage assets end up here.
5 | /cover/
6 |
7 | # The directory Mix downloads your dependencies sources to.
8 | /deps/
9 |
10 | # Where third-party dependencies like ExDoc output generated docs.
11 | /doc/
12 |
13 | # Ignore .fetch files in case you like to edit your project deps locally.
14 | /.fetch
15 |
16 | # If the VM crashes, it generates a dump, let's ignore it too.
17 | erl_crash.dump
18 |
19 | # Also ignore archive artifacts (built via "mix archive.build").
20 | *.ez
21 |
22 | # Ignore package tarball (built via "mix hex.build").
23 | phoenix_swoosh-*.tar
24 |
25 | # Temporary files for e.g. tests.
26 | /tmp/
27 |
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name-template: 'v$RESOLVED_VERSION 🚀'
2 | tag-template: 'v$RESOLVED_VERSION'
3 | categories:
4 | - title: '✨ Features'
5 | labels:
6 | - 'feature'
7 | - 'enhancement'
8 | - title: '🐛 Bug Fixes'
9 | labels:
10 | - 'fix'
11 | - 'bugfix'
12 | - 'bug'
13 | - title: '🧰 Maintenance'
14 | label:
15 | - 'chore'
16 | - 'dependencies'
17 | - title: '📝 Documentation'
18 | label: 'docs'
19 | exclude-labels:
20 | - 'skip-changelog'
21 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
22 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
23 | version-resolver:
24 | major:
25 | labels:
26 | - 'major'
27 | minor:
28 | labels:
29 | - 'minor'
30 | patch:
31 | labels:
32 | - 'patch'
33 | default: patch
34 | template: |
35 | ## Changes
36 |
37 | $CHANGES
38 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Swoosh contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule PhoenixSwoosh.Mixfile do
2 | use Mix.Project
3 |
4 | @source_url "https://github.com/swoosh/phoenix_swoosh"
5 | @version "1.2.1"
6 |
7 | def project do
8 | [
9 | app: :phoenix_swoosh,
10 | version: @version,
11 | elixir: "~> 1.11",
12 | name: "Phoenix.Swoosh",
13 | build_embedded: Mix.env() == :prod,
14 | start_permanent: Mix.env() == :prod,
15 | deps: deps(),
16 | package: package(),
17 | docs: docs(),
18 | preferred_cli_env: [docs: :docs]
19 | ]
20 | end
21 |
22 | def application do
23 | [extra_applications: [:logger]]
24 | end
25 |
26 | defp deps do
27 | [
28 | {:swoosh, "~> 1.5"},
29 | {:phoenix_view, "~> 1.0 or ~> 2.0"},
30 | {:phoenix_html, "~> 3.0 or ~> 4.0", optional: true},
31 | {:hackney, "~> 1.10", optional: true},
32 | {:finch, "~> 0.8", optional: true},
33 | {:phoenix, "~> 1.8", optional: true},
34 | {:credo, "~> 1.0", only: [:dev, :test]},
35 | {:ex_doc, "~> 0.26", only: :docs, runtime: false}
36 | ]
37 | end
38 |
39 | defp package do
40 | [
41 | description: "Use Swoosh to easily send emails in your Phoenix project.",
42 | maintainers: ["Steve Domin", "Po Chen"],
43 | licenses: ["MIT"],
44 | links: %{
45 | "Changelog" => "https://hexdocs.pm/phoenix_swoosh/changelog.html",
46 | "GitHub" => @source_url
47 | }
48 | ]
49 | end
50 |
51 | defp docs do
52 | [
53 | extras: [
54 | {:"README.md", [title: "Overview"]},
55 | "CHANGELOG.md",
56 | "CONTRIBUTING.md",
57 | "LICENSE.md": [title: "License"]
58 | ],
59 | main: "readme",
60 | canonical: "http://hexdocs.pm/phoenix_swoosh",
61 | source_url: @source_url,
62 | source_ref: "v#{@version}",
63 | api_reference: false,
64 | formatters: ["html"]
65 | ]
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## 1.2.1 - 2024-01-08
9 |
10 | ### Maintenance
11 |
12 | - Relax `phoenix_html` dependency requirement, allowing `~> 4.0`
13 |
14 | ## 1.2.0 - 2023-02-19
15 |
16 | ### Feature
17 |
18 | - Support extension customization @jswanner (#290)
19 |
20 | See customization section in readme for more details.
21 |
22 | ## 1.1.0 - 2022-10-27
23 |
24 | ### Changes
25 |
26 | - Support `phoenix_view` 2.0 @dkln (#279)
27 |
28 | ## 1.0.1 - 2022-02-12
29 |
30 | ### Changes
31 |
32 | - Remove transitive compile-time deps in Phoenix.Swoosh macro @MamesPalmero (#241)
33 |
34 | ## 1.0.0 - 2021-09-25
35 |
36 | ### Added
37 |
38 | The setup within a `Phoenix` is now referred to as the
39 | [classic setup](https://github.com/swoosh/phoenix_swoosh#1-classic-setup).
40 |
41 | 1.0 adds the ability for the lib to be used outside `Phoenix` apps.
42 |
43 | A new setup that doesn't involve a sparate view module is added and is called the standalone setup.
44 | Both setups can work outsite `Phoenix` apps thanks to the recently extracted `Phoenix.View`.
45 |
46 | #### Standalone setup
47 |
48 | ```eex
49 | # path_to/templates/user_notifier/welcome.html.eex
50 |
51 |
Welcome to Sample, <%= @name %>!
52 |
53 | ```
54 |
55 | ```elixir
56 | # path_to/notifiers/user_notifier.ex
57 | defmodule Sample.UserNotifier do
58 | use Phoenix.Swoosh,
59 | template_root: "path_to/templates",
60 | template_path: "user_notifier"
61 |
62 | # ... same welcome ...
63 | end
64 | ```
65 |
66 | In this setup, the notifier module itself serves as the view module
67 |
68 | `template_root`, `template_path` and `template_namespace`
69 | will be passed to `Phoenix.View` as `root`, `path` and `namespace`.
70 |
71 | Layout can be setup the same way as
72 | [classic setup](https://github.com/swoosh/phoenix_swoosh#1-classic-setup).
73 |
74 | ---
75 |
76 | [Changelog prior to 1.0 can be found on 0.3.x branch](https://github.com/swoosh/phoenix_swoosh/blob/0.3.x/CHANGELOG.md)
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Phoenix.Swoosh
2 |
3 | [](https://github.com/swoosh/phoenix_swoosh/actions/workflows/elixir.yml)
4 | [](https://hex.pm/packages/phoenix_swoosh)
5 | [](https://hexdocs.pm/phoenix_swoosh/)
6 | [](https://hex.pm/packages/phoenix_swoosh)
7 | [](https://github.com/swoosh/phoenix_swoosh/blob/master/LICENSE)
8 | [](https://github.com/swoosh/phoenix_swoosh/commits/master)
9 |
10 | `Phoenix.View` + `Swoosh`. ([Discuss about the future post-Phoenix-1.7 here](https://github.com/swoosh/phoenix_swoosh/issues/287))
11 |
12 | This module provides the ability to set the HTML and/or text body of an email by rendering templates.
13 |
14 | ## Installation
15 |
16 | Add `:phoenix_swoosh` to your list of dependencies in `mix.exs`:
17 |
18 | ```elixir
19 | def deps do
20 | [
21 | {:phoenix_swoosh, "~> 1.0"},
22 |
23 | # without phoenix_html, phoenix_swoosh only works with plain text templates
24 | # if you want to use HTML templates
25 | # {:phoenix_html, "~> 3.0"},
26 | ]
27 | end
28 | ```
29 |
30 | You probably also want to install [`finch`](https://hex.pm/packages/finch),
31 | [`hackney`](https://hex.pm/packages/hackney) or an HTTP client of your own choice,
32 | if you are using a provider that `Swoosh` talks to via their HTTP API.
33 |
34 | Or [`:gen_smtp`](https://hex.pm/packages/gen_smtp) if you are working with a provider
35 | that only works through SMTP.
36 |
37 | See `Swoosh` for more details.
38 |
39 | ## Usage
40 |
41 | ### 1. Classic setup
42 |
43 | Setting up the templates:
44 |
45 | ```eex
46 | # path_to/templates/user_notifier/welcome.html.eex
47 |
48 |
Welcome to Sample, <%= @name %>!
49 |
50 | ```
51 |
52 | ```elixir
53 | # path_to/views/user_notifier_view.ex
54 | defmodule Sample.UserNotifierView do
55 | use Phoenix.View, root: "path_to/templates"
56 | end
57 | ```
58 |
59 | Passing values to templates:
60 |
61 | ```elixir
62 | # path_to/notifiers/user_notifier.ex
63 | defmodule Sample.UserNotifier do
64 | use Phoenix.Swoosh, view: Sample.UserNotifierView
65 |
66 | def welcome(user) do
67 | new()
68 | |> from("tony@stark.com")
69 | |> to(user.email)
70 | |> subject("Hello, Avengers!")
71 | |> render_body("welcome.html", %{name: name})
72 | end
73 | end
74 | ```
75 |
76 | Maybe with a layout:
77 |
78 | ```eex
79 | # path_to/templates/layout/email.html.eex
80 |
81 |
82 | <%= @email.subject %>
83 |
84 |
85 | <%= @inner_content %>
86 |
87 |
88 | ```
89 |
90 | ```elixir
91 | defmodule Sample.LayoutView do
92 | use Phoenix.View, root: "path_to/templates"
93 | end
94 | ```
95 |
96 | ```elixir
97 | # path_to/notifiers/user_notifier.ex
98 | defmodule Sample.UserNotifier do
99 | use Phoenix.Swoosh,
100 | view: Sample.NotifierView,
101 | layout: {Sample.LayoutView, :email}
102 |
103 | # ... same welcome ...
104 | end
105 | ```
106 |
107 | Layout can also be added/changed dynamically with `put_new_layout/2` and `put_layout/2`
108 |
109 | ### 2. Standalone setup
110 |
111 | ```eex
112 | # path_to/templates/user_notifier/welcome.html.eex
113 |
114 |
Welcome to Sample, <%= @name %>!
115 |
116 | ```
117 |
118 | ```elixir
119 | # path_to/notifiers/user_notifier.ex
120 | defmodule Sample.UserNotifier do
121 | use Phoenix.Swoosh,
122 | template_root: "path_to/templates",
123 | template_path: "user_notifier"
124 |
125 | # ... same welcome ...
126 | end
127 | ```
128 |
129 | In this setup, the notifier module itself serves as the view module
130 |
131 | `template_root`, `template_path` and `template_namespace`
132 | will be passed to `Phoenix.View` as `root`, `path` and `namespace`.
133 |
134 | Layout can be setup the same way as classic setup.
135 |
136 | ## Customization
137 |
138 | When using either the classic or standalone setup described above, the
139 | extensions of the templates used can be customized. For example, let's say
140 | MJML is the desired markup language to use for HTML emails, and there's a
141 | template such as:
142 |
143 | ```eex
144 | # path_to/templates/user_notifier/welcome.mjml.eex
145 |
146 |
147 |
148 | Welcome to Sample, <%= @name %>!
149 |
150 |
151 | ```
152 |
153 | Phoenix.Swoosh can be configured to use the "mjml" extension when rendering the
154 | HTML body of the email:
155 |
156 | ```elixir
157 | # path_to/notifiers/user_notifier.ex
158 | defmodule Sample.UserNotifier do
159 | use Phoenix.Swoosh,
160 | formats: %{"mjml" => :html_body, "text" => :text_body}
161 |
162 | # ... same welcome as above ...
163 | end
164 | ```
165 |
166 | This requires that Phoenix knows how to handle templates using the "mjml"
167 | format. This can be done by creating a module that exports
168 | `encode_to_iodata!/1`:
169 |
170 | ```elixir
171 | defmodule Sample.Mjml do
172 | def encode_to_iodata!(mjml) do
173 | with {:ok, html} <- Mjml.to_html(mjml) do
174 | html
175 | end
176 | end
177 | end
178 | ```
179 |
180 | And then configuring Phoenix to use it:
181 |
182 | ```elixir
183 | config :phoenix, format_encoders: [mjml: Sample.Mjml]
184 | ```
185 |
186 | The above example is using [`mjml`](https://hex.pm/packages/mjml), which would
187 | need to be installed as well.
188 |
189 | ## Copyright and License
190 |
191 | Copyright (c) 2021 Swoosh contributors
192 |
193 | Released under the MIT License, which can be found in [LICENSE.md](./LICENSE.md).
194 |
--------------------------------------------------------------------------------
/lib/phoenix_swoosh.ex:
--------------------------------------------------------------------------------
1 | defmodule Phoenix.Swoosh do
2 | @moduledoc """
3 | The main feature provided by this module is the ability to set the HTML and/or
4 | text body of an email by rendering templates.
5 |
6 | It utilizes `Phoenix.View` and can work very well both standalone
7 | and in apps using `Phoenix` framework.
8 |
9 |
10 | `use Phoenix.Swoosh` also accepts a list of options which configures where
11 | templates will be looked up:
12 |
13 | * `:view` - for "classic" setup, the view module to use for rendering
14 | * `:template_path` - for "standalone" setup, given to `Phoenix.View` as `path`
15 | * `:template_root` - for "standalone" setup, given to `Phoenix.View` as `root`
16 | * `:template_namespace` - for "standalone" setup, given to `Phoenix.View` as
17 | `namespace`
18 | * `:layout` - the layout to render the templates in. Must be a tuple,
19 | specifying the layout view and the layout name, or `false`
20 | * `:formats` - to customize the extensions of the templates. Must be a map,
21 | with key being the extension and the value being the body field to set
22 | """
23 |
24 | import Swoosh.Email
25 |
26 | defmacro __using__(opts) do
27 | view = Keyword.get(opts, :view)
28 | layout = Keyword.get(opts, :layout, false)
29 | template_root = Keyword.get(opts, :template_root)
30 | template_path = Keyword.get(opts, :template_path)
31 | template_namespace = Keyword.get(opts, :template_namespace)
32 | formats = Keyword.get(opts, :formats)
33 |
34 | unless view || template_root do
35 | raise ArgumentError,
36 | """
37 | no view or template_root was set, you can set one with
38 |
39 | use Phoenix.Swoosh, view: MyApp.EmailView
40 |
41 | or
42 |
43 | use Phoenix.Swoosh, template_root: "./templates"
44 | """
45 | end
46 |
47 | view_module = if template_root, do: quote(do: __MODULE__), else: view
48 |
49 | quote do
50 | import Swoosh.Email
51 | import Phoenix.Swoosh, except: [render_body: 3]
52 |
53 | if unquote(template_root) do
54 | use Phoenix.View,
55 | root: unquote(template_root),
56 | path: unquote(template_path),
57 | namespace: unquote(template_namespace)
58 | end
59 |
60 | def render_body(email, template, assigns \\ %{}) do
61 | email
62 | |> put_new_formats(unquote(formats))
63 | |> put_new_layout(unquote(layout))
64 | |> put_new_view(unquote(view_module))
65 | |> Phoenix.Swoosh.render_body(template, assigns)
66 | end
67 | end
68 | end
69 |
70 | @doc """
71 | Renders the given `template` and `assigns` based on the `email`.
72 |
73 | Once the template is rendered the resulting string is stored on the email
74 | fields `html_body` and `text_body` depending on the format of the template.
75 | `.html`, `.htm`, and `.xml` are stored in `html_body`; all other extensions,
76 | (e.g. `.txt` and `.text`), in `text_body`.
77 |
78 | ## Arguments
79 |
80 | * `email` - the `Swoosh.Email` struct.
81 |
82 | * `template` - may be an atom or a string. If an atom, like `:welcome`, it
83 | will render both the HTML and text template and stores them respectively on
84 | the email. If the template is a string it must contain the extension too,
85 | like `welcome.html`.
86 |
87 | * `assigns` - a dictionary with the assigns to be used in the view. Those
88 | assigns are merged and have higher order precedence than the email assigns.
89 | (`email.assigns`)
90 |
91 | ## Examples
92 |
93 | defmodule Sample.UserEmail do
94 | use Phoenix.Swoosh, view: Sample.EmailView
95 |
96 | def welcome(user) do
97 | new()
98 | |> from("tony@stark.com")
99 | |> to(user.email)
100 | |> subject("Hello, Avengers!")
101 | |> render_body("welcome.html", %{username: user.email})
102 | end
103 | end
104 |
105 | The example above renders a template `welcome.html` from `Sample.EmailView` and
106 | stores the resulting string onto the html_body field of the email.
107 | (`email.html_body`)
108 |
109 | In many cases you may want to set both the html and text body of an email. To
110 | do so you can pass the template name as an atom (without the extension):
111 |
112 | def welcome(user) do
113 | new()
114 | |> from("tony@stark.com")
115 | |> to(user.email)
116 | |> subject("Hello, Avengers!")
117 | |> render_body(:welcome, %{username: user.email})
118 | end
119 |
120 | ## Layouts
121 |
122 | Templates are often rendered inside layouts. If you wish to do so you will have
123 | to specify which layout you want to use when using the `Phoenix.Swoosh` module.
124 |
125 | defmodule Sample.UserEmail do
126 | use Phoenix.Swoosh, view: Sample.EmailView, layout: {Sample.LayoutView, :email}
127 |
128 | def welcome(user) do
129 | new()
130 | |> from("tony@stark.com")
131 | |> to(user.email)
132 | |> subject("Hello, Avengers!")
133 | |> render_body("welcome.html", %{username: user.email})
134 | end
135 | end
136 |
137 | The example above will render the `welcome.html` template inside an
138 | `email.html` template specified in `Sample.LayoutView`. `put_layout/2` can be
139 | used to change the layout, similar to how `put_view/2` can be used to change
140 | the view.
141 | """
142 | def render_body(email, template, assigns) when is_atom(template) do
143 | extensions(email)
144 | |> Enum.reduce(email, fn extension, email ->
145 | do_render_body(email, template_name(template, extension), extension, assigns)
146 | end)
147 | end
148 |
149 | def render_body(email, template, assigns) when is_binary(template) do
150 | case Path.extname(template) do
151 | "." <> extension ->
152 | do_render_body(email, template, extension, assigns)
153 |
154 | "" ->
155 | raise "cannot render template #{inspect(template)} without format. Use an atom if you " <>
156 | "want to set both the html and text body."
157 | end
158 | end
159 |
160 | defp do_render_body(email, template, extension, assigns) do
161 | assigns = Enum.into(assigns, %{})
162 |
163 | email =
164 | email
165 | |> put_private(:phoenix_template, template)
166 | |> prepare_assigns(assigns, extension)
167 |
168 | view =
169 | Map.get(email.private, :phoenix_view) ||
170 | raise "a view module was not specified, set one with put_view/2"
171 |
172 | content = Phoenix.View.render_to_string(view, template, Map.put(email.assigns, :email, email))
173 | Map.put(email, extension_to_body_key(email, extension), content)
174 | end
175 |
176 | @doc """
177 | Stores the formats for rendering if none was stored yet.
178 | """
179 | def put_new_formats(email, nil), do: email
180 |
181 | def put_new_formats(email, extension_format_map) do
182 | update_in(email.private, fn private ->
183 | private
184 | |> Map.put_new(:phoenix_extensions, Map.keys(extension_format_map))
185 | |> Map.put_new(:phoenix_extensions_to_body_key, extension_format_map)
186 | end)
187 | end
188 |
189 | @doc """
190 | Stores the layout for rendering.
191 |
192 | The layout must be a tuple, specifying the layout view and the layout
193 | name, or false. In case a previous layout is set, `put_layout` also
194 | accepts the layout name to be given as a string or as an atom. If a
195 | string, it must contain the format. Passing an atom means the layout
196 | format will be found at rendering time, similar to the template in
197 | `render_body/3`. It can also be set to `false`. In this case, no
198 | layout would be used.
199 |
200 | ## Examples
201 |
202 | iex> layout(email)
203 | false
204 |
205 | iex> email = put_layout email, {LayoutView, "email.html"}
206 | iex> layout(email)
207 | {LayoutView, "email.html"}
208 |
209 | iex> email = put_layout email, "email.html"
210 | iex> layout(email)
211 | {LayoutView, "email.html"}
212 |
213 | iex> email = put_layout email, :email
214 | iex> layout(email)
215 | {AppView, :email}
216 |
217 | """
218 | def put_layout(email, layout) do
219 | do_put_layout(email, layout)
220 | end
221 |
222 | defp do_put_layout(email, false) do
223 | put_private(email, :phoenix_layout, false)
224 | end
225 |
226 | defp do_put_layout(email, {mod, layout}) when is_atom(mod) do
227 | put_private(email, :phoenix_layout, {mod, layout})
228 | end
229 |
230 | defp do_put_layout(email, layout) when is_binary(layout) or is_atom(layout) do
231 | update_in(email.private, fn private ->
232 | case Map.get(private, :phoenix_layout, false) do
233 | {mod, _} ->
234 | Map.put(private, :phoenix_layout, {mod, layout})
235 |
236 | false ->
237 | raise "cannot use put_layout/2 with atom/binary when layout is false, use a tuple instead"
238 | end
239 | end)
240 | end
241 |
242 | @doc """
243 | Stores the layout for rendering if one was not stored yet.
244 | """
245 | def put_new_layout(email, layout)
246 | when (is_tuple(layout) and tuple_size(layout) == 2) or layout == false do
247 | update_in(email.private, &Map.put_new(&1, :phoenix_layout, layout))
248 | end
249 |
250 | @doc """
251 | Retrieves the current layout of an email.
252 | """
253 | def layout(email), do: Map.get(email.private, :phoenix_layout, false)
254 |
255 | @doc """
256 | Stores the view for rendering.
257 | """
258 | def put_view(email, module) do
259 | put_private(email, :phoenix_view, module)
260 | end
261 |
262 | @doc """
263 | Stores the view for rendering if one was not stored yet.
264 | """
265 | def put_new_view(email, module) do
266 | update_in(email.private, &Map.put_new(&1, :phoenix_view, module))
267 | end
268 |
269 | defp prepare_assigns(email, assigns, extension) do
270 | layout =
271 | case layout(email, assigns, extension) do
272 | {mod, layout} -> {mod, template_name(layout, extension)}
273 | false -> false
274 | end
275 |
276 | update_in(
277 | email.assigns,
278 | &(&1 |> Map.merge(assigns) |> Map.put(:layout, layout))
279 | )
280 | end
281 |
282 | @default_mapping %{
283 | "htm" => :html_body,
284 | "html" => :html_body,
285 | "text" => :text_body,
286 | "xml" => :html_body
287 | }
288 |
289 | defp extension_to_body_key(email, extension) do
290 | email.private
291 | |> Map.get(:phoenix_extensions_to_body_key, @default_mapping)
292 | |> Map.get(extension, :text_body)
293 | end
294 |
295 | defp extensions(email) do
296 | Map.get(email.private, :phoenix_extensions, ["html", "text"])
297 | end
298 |
299 | defp layout(email, assigns, extension) do
300 | if extension in extensions(email) do
301 | case Map.fetch(assigns, :layout) do
302 | {:ok, layout} -> layout
303 | :error -> layout(email)
304 | end
305 | else
306 | false
307 | end
308 | end
309 |
310 | defp template_name(name, extension) when is_atom(name),
311 | do: Atom.to_string(name) <> "." <> extension
312 |
313 | defp template_name(name, _ext) when is_binary(name), do: name
314 | end
315 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{
2 | "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
3 | "certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"},
4 | "credo": {:hex, :credo, "1.7.14", "c7e75216cea8d978ba8c60ed9dede4cc79a1c99a266c34b3600dd2c33b96bc92", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "12a97d6bb98c277e4fb1dff45aaf5c137287416009d214fb46e68147bd9e0203"},
5 | "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
6 | "ex_doc": {:hex, :ex_doc, "0.39.3", "519c6bc7e84a2918b737aec7ef48b96aa4698342927d080437f61395d361dcee", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "0590955cf7ad3b625780ee1c1ea627c28a78948c6c0a9b0322bd976a079996e1"},
7 | "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
8 | "finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"},
9 | "hackney": {:hex, :hackney, "1.25.0", "390e9b83f31e5b325b9f43b76e1a785cbdb69b5b6cd4e079aa67835ded046867", [:rebar3], [{:certifi, "~> 2.15.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.4", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "7209bfd75fd1f42467211ff8f59ea74d6f2a9e81cbcee95a56711ee79fd6b1d4"},
10 | "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
11 | "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
12 | "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
13 | "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
14 | "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
15 | "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"},
16 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
17 | "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
18 | "mimerl": {:hex, :mimerl, "1.4.0", "3882a5ca67fbbe7117ba8947f27643557adec38fa2307490c4c4207624cb213b", [:rebar3], [], "hexpm", "13af15f9f68c65884ecca3a3891d50a7b57d82152792f3e19d88650aa126b144"},
19 | "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"},
20 | "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
21 | "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
22 | "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
23 | "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
24 | "phoenix": {:hex, :phoenix, "1.8.3", "49ac5e485083cb1495a905e47eb554277bdd9c65ccb4fc5100306b350151aa95", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "36169f95cc2e155b78be93d9590acc3f462f1e5438db06e6248613f27c80caec"},
25 | "phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"},
26 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"},
27 | "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
28 | "phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"},
29 | "plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"},
30 | "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
31 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
32 | "swoosh": {:hex, :swoosh, "1.19.9", "4eb2c471b8cf06adbdcaa1d57a0ad53c0ed9348ce8586a06cc491f9f0dbcb553", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:idna, "~> 6.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "516898263a64925c31723c56bc7999a26e97b04e869707f681f4c9bca7ee1688"},
33 | "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
34 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"},
35 | "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
36 | "websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"},
37 | }
38 |
--------------------------------------------------------------------------------
/test/phoenix_swoosh_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Phoenix.SwooshTest do
2 | use ExUnit.Case, async: true
3 |
4 | alias Swoosh.Email
5 | import Swoosh.Email
6 | import Phoenix.Swoosh
7 |
8 | defmodule EmailView do
9 | use Phoenix.View, root: "test/fixtures/templates", namespace: Phoenix.SwooshTest
10 | end
11 |
12 | defmodule LayoutView do
13 | use Phoenix.View, root: "test/fixtures/templates", namespace: Phoenix.SwooshTest
14 | end
15 |
16 | defmodule TestEmail do
17 | use Phoenix.Swoosh, view: EmailView
18 |
19 | def welcome_html(), do: email() |> render_body("welcome.html", %{})
20 | def welcome_text(), do: email() |> render_body("welcome.text", %{})
21 |
22 | def welcome_html_assigns(),
23 | do: email() |> render_body("welcome_assigns.html", %{name: "Tony"})
24 |
25 | def welcome_text_assigns(),
26 | do: email() |> render_body("welcome_assigns.text", %{name: "Tony"})
27 |
28 | def welcome_html_without_assigns(), do: email() |> render_body("welcome.html")
29 | def welcome_text_without_assigns(), do: email() |> render_body("welcome.text")
30 |
31 | def welcome_html_layout() do
32 | email()
33 | |> put_layout({LayoutView, "email.html"})
34 | |> render_body("welcome.html", %{})
35 | end
36 |
37 | def welcome_text_layout() do
38 | email()
39 | |> put_layout({LayoutView, "email.text"})
40 | |> render_body("welcome.text", %{})
41 | end
42 |
43 | def welcome_html_layout_without_assigns() do
44 | email()
45 | |> put_layout({LayoutView, "email.html"})
46 | |> render_body("welcome.html")
47 | end
48 |
49 | def welcome_text_layout_without_assigns() do
50 | email()
51 | |> put_layout({LayoutView, "email.text"})
52 | |> render_body("welcome.text")
53 | end
54 |
55 | def welcome_html_layout_assigns() do
56 | email()
57 | |> put_layout({LayoutView, "email.html"})
58 | |> render_body("welcome_assigns.html", %{name: "Tony"})
59 | end
60 |
61 | def welcome_text_layout_assigns() do
62 | email()
63 | |> put_layout({LayoutView, "email.text"})
64 | |> render_body("welcome_assigns.text", %{name: "Tony"})
65 | end
66 |
67 | def welcome(), do: email() |> render_body(:welcome, %{})
68 |
69 | def welcome_assigns(), do: email() |> render_body(:welcome_assigns, %{name: "Tony"})
70 |
71 | def welcome_layout() do
72 | email()
73 | |> put_layout({LayoutView, :email})
74 | |> render_body(:welcome, %{})
75 | end
76 |
77 | def welcome_layout_assigns() do
78 | email()
79 | |> put_layout({LayoutView, :email})
80 | |> render_body(:welcome_assigns, %{name: "Tony"})
81 | end
82 |
83 | def email() do
84 | %Email{}
85 | |> from("tony@stark.com")
86 | |> to("steve@rogers.com")
87 | |> subject("Welcome, Avengers!")
88 | end
89 | end
90 |
91 | defmodule TestEmailLayout do
92 | use Phoenix.Swoosh, view: EmailView, layout: {LayoutView, :email}
93 |
94 | def welcome() do
95 | %Email{}
96 | |> from("tony@stark.com")
97 | |> to("steve@rogers.com")
98 | |> subject("Welcome, Avengers!")
99 | |> render_body(:welcome, %{})
100 | end
101 | end
102 |
103 | defmodule TestEmailLayoutFormats do
104 | use Phoenix.Swoosh,
105 | formats: %{"custom" => :html_body, "text" => :text_body},
106 | view: EmailView,
107 | layout: {LayoutView, :email}
108 |
109 | def welcome do
110 | %Email{}
111 | |> from("tony@stark.com")
112 | |> to("steve@rogers.com")
113 | |> subject("Welcome, Avengers!")
114 | |> render_body(:welcome, %{})
115 | end
116 | end
117 |
118 | defmodule TestViewIncludedNotifier do
119 | use Phoenix.Swoosh,
120 | template_root: "test/fixtures/templates",
121 | template_namespace: Phoenix.SwooshTest
122 |
123 | import Swoosh.Email
124 |
125 | def welcome_assigns do
126 | %Email{}
127 | |> from("tony@stark.com")
128 | |> to("steve@rogers.com")
129 | |> subject("Welcome, Avengers!")
130 | |> render_body(:welcome_assigns, %{name: "Tony"})
131 | end
132 |
133 | def welcome_layout do
134 | %Email{}
135 | |> from("tony@stark.com")
136 | |> to("steve@rogers.com")
137 | |> subject("Welcome, Avengers!")
138 | |> put_layout({LayoutView, :email})
139 | |> render_body(:welcome_assigns, %{name: "Avengers"})
140 | end
141 | end
142 |
143 | defmodule TestViewIncludedNotifierFormats do
144 | use Phoenix.Swoosh,
145 | formats: %{"custom" => :html_body, "text" => :text_body},
146 | template_root: "test/fixtures/templates",
147 | template_namespace: Phoenix.SwooshTest
148 |
149 | import Swoosh.Email
150 |
151 | def welcome_assigns do
152 | %Email{}
153 | |> from("tony@stark.com")
154 | |> to("steve@rogers.com")
155 | |> subject("Welcome, Avengers!")
156 | |> render_body(:welcome_assigns, %{name: "Tony"})
157 | end
158 |
159 | def welcome_layout do
160 | %Email{}
161 | |> from("tony@stark.com")
162 | |> to("steve@rogers.com")
163 | |> subject("Welcome, Avengers!")
164 | |> put_layout({LayoutView, :email})
165 | |> render_body(:welcome_assigns, %{name: "Avengers"})
166 | end
167 | end
168 |
169 | setup_all do
170 | email =
171 | %Email{}
172 | |> from("tony@stark.com")
173 | |> to("steve@rogers.com")
174 | |> subject("Welcome, Avengers!")
175 | |> put_view(EmailView)
176 |
177 | {:ok, email: email}
178 | end
179 |
180 | test "render html body", %{email: email} do
181 | assert %Email{html_body: "Welcome, Avengers!
\n"} =
182 | render_body(email, "welcome.html", %{})
183 | end
184 |
185 | test "render html body with layout", %{email: email} do
186 | email = email |> put_layout({LayoutView, "email.html"})
187 |
188 | assert %Email{html_body: "Welcome, Avengers!
\n\n"} =
189 | render_body(email, "welcome.html", %{})
190 | end
191 |
192 | test "render text body", %{email: email} do
193 | assert %Email{text_body: "Welcome, Avengers!\n"} = render_body(email, "welcome.text", %{})
194 | end
195 |
196 | test "render text body with layout", %{email: email} do
197 | email = email |> put_layout({LayoutView, "email.text"})
198 |
199 | assert %Email{text_body: "TEXT: Welcome, Avengers!\n\n"} =
200 | render_body(email, "welcome.text", %{})
201 | end
202 |
203 | test "render html body with assigns", %{email: email} do
204 | assert %Email{html_body: "Welcome, Tony!
\n"} =
205 | render_body(email, "welcome_assigns.html", %{name: "Tony"})
206 | end
207 |
208 | test "render text body with assigns", %{email: email} do
209 | assert %Email{text_body: "Welcome, Tony!\n"} =
210 | render_body(email, "welcome_assigns.text", %{name: "Tony"})
211 | end
212 |
213 | test "render html body with layout and assigns", %{email: email} do
214 | email = email |> put_layout({LayoutView, "email.html"})
215 |
216 | assert %Email{html_body: "Welcome, Tony!
\n\n"} =
217 | render_body(email, "welcome_assigns.html", %{name: "Tony"})
218 | end
219 |
220 | test "render text body with layout and assigns", %{email: email} do
221 | email = email |> put_layout({LayoutView, "email.text"})
222 |
223 | assert %Email{text_body: "TEXT: Welcome, Tony!\n\n"} =
224 | render_body(email, "welcome_assigns.text", %{name: "Tony"})
225 | end
226 |
227 | test "render both html and text body", %{email: email} do
228 | assert %Email{html_body: "Welcome, Avengers!
\n", text_body: "Welcome, Avengers!\n"} =
229 | render_body(email, :welcome, %{})
230 | end
231 |
232 | test "render both html and text body with layout", %{email: email} do
233 | email = email |> put_layout({LayoutView, :email})
234 |
235 | assert %Email{
236 | html_body: "Welcome, Avengers!
\n\n",
237 | text_body: "TEXT: Welcome, Avengers!\n\n"
238 | } = render_body(email, :welcome, %{})
239 | end
240 |
241 | test "render both html and text body with assigns", %{email: email} do
242 | assert %Email{html_body: "Welcome, Tony!
\n", text_body: "Welcome, Tony!\n"} =
243 | render_body(email, :welcome_assigns, %{name: "Tony"})
244 | end
245 |
246 | test "render both html and text body with layout and assigns", %{email: email} do
247 | email = email |> put_layout({LayoutView, :email})
248 |
249 | assert %Email{
250 | html_body: "Welcome, Tony!
\n\n",
251 | text_body: "TEXT: Welcome, Tony!\n\n"
252 | } = render_body(email, :welcome_assigns, %{name: "Tony"})
253 | end
254 |
255 | test "macro: render html body" do
256 | assert %Email{html_body: "Welcome, Avengers!
\n"} = TestEmail.welcome_html()
257 | end
258 |
259 | test "macro: render text body" do
260 | assert %Email{text_body: "Welcome, Avengers!\n"} = TestEmail.welcome_text()
261 | end
262 |
263 | test "macro: render html body with layout" do
264 | assert %Email{html_body: "Welcome, Avengers!
\n\n"} =
265 | TestEmail.welcome_html_layout()
266 | end
267 |
268 | test "macro: render text body with layout" do
269 | assert %Email{text_body: "TEXT: Welcome, Avengers!\n\n"} = TestEmail.welcome_text_layout()
270 | end
271 |
272 | test "macro: render html body with layout without assigns" do
273 | assert %Email{html_body: "Welcome, Avengers!
\n\n"} =
274 | TestEmail.welcome_html_layout_without_assigns()
275 | end
276 |
277 | test "macro: render text body with layout without assigns" do
278 | assert %Email{text_body: "TEXT: Welcome, Avengers!\n\n"} =
279 | TestEmail.welcome_text_layout_without_assigns()
280 | end
281 |
282 | test "macro: render html body without assigns" do
283 | assert %Email{html_body: "Welcome, Avengers!
\n"} =
284 | TestEmail.welcome_html_without_assigns()
285 | end
286 |
287 | test "macro: render text body without assigns" do
288 | assert %Email{text_body: "Welcome, Avengers!\n"} = TestEmail.welcome_text_without_assigns()
289 | end
290 |
291 | test "macro: render html body with assigns" do
292 | assert %Email{html_body: "Welcome, Tony!
\n"} = TestEmail.welcome_html_assigns()
293 | end
294 |
295 | test "macro: render text body with assigns" do
296 | assert %Email{text_body: "Welcome, Tony!\n"} = TestEmail.welcome_text_assigns()
297 | end
298 |
299 | test "macro: render html body with layout and assigns" do
300 | assert %Email{html_body: "Welcome, Tony!
\n\n"} =
301 | TestEmail.welcome_html_layout_assigns()
302 | end
303 |
304 | test "macro: render text body with layout and assigns" do
305 | assert %Email{text_body: "TEXT: Welcome, Tony!\n\n"} = TestEmail.welcome_text_layout_assigns()
306 | end
307 |
308 | test "macro: render both html and text body" do
309 | assert %Email{html_body: "Welcome, Avengers!
\n", text_body: "Welcome, Avengers!\n"} =
310 | TestEmail.welcome()
311 | end
312 |
313 | test "macro: render both html and text body with layout" do
314 | assert %Email{
315 | html_body: "Welcome, Avengers!
\n\n",
316 | text_body: "TEXT: Welcome, Avengers!\n\n"
317 | } = TestEmail.welcome_layout()
318 | end
319 |
320 | test "macro: render both html and text body with assigns" do
321 | assert %Email{html_body: "Welcome, Tony!
\n", text_body: "Welcome, Tony!\n"} =
322 | TestEmail.welcome_assigns()
323 | end
324 |
325 | test "macro: render both html and text body with layout and assigns" do
326 | assert %Email{
327 | html_body: "Welcome, Tony!
\n\n",
328 | text_body: "TEXT: Welcome, Tony!\n\n"
329 | } = TestEmail.welcome_layout_assigns()
330 | end
331 |
332 | test "macro: use layout when provided via `use` macro" do
333 | assert %Email{
334 | html_body: "Welcome, Avengers!
\n\n",
335 | text_body: "TEXT: Welcome, Avengers!\n\n"
336 | } = TestEmailLayout.welcome()
337 | end
338 |
339 | test "macro: use layout for custom format when provided via `use` macro" do
340 | assert %Email{
341 | html_body: "Welcome, Avengers!\n\n",
342 | text_body: "TEXT: Welcome, Avengers!\n\n"
343 | } = TestEmailLayoutFormats.welcome()
344 | end
345 |
346 | test "email is available in template", %{email: email} do
347 | assert %Email{html_body: "Welcome from tony@stark.com
\n"} =
348 | render_body(email, "welcome_from.html", %{})
349 | end
350 |
351 | test "put_layout/2", %{email: email} do
352 | email =
353 | email
354 | |> put_layout({LayoutView, :wrong})
355 | |> put_layout(:email)
356 | |> render_body("welcome.html", %{})
357 |
358 | assert %Email{html_body: "Welcome, Avengers!
\n\n"} = email
359 | end
360 |
361 | describe "view included" do
362 | test "render both html and text body with assigns" do
363 | assert %Email{html_body: "Welcome, Tony!
\n", text_body: "Welcome, Tony!\n"} =
364 | TestViewIncludedNotifier.welcome_assigns()
365 | end
366 |
367 | test "render both html and text body with layout" do
368 | assert %Email{
369 | html_body: "Welcome, Avengers!
\n\n",
370 | text_body: "TEXT: Welcome, Avengers!\n\n"
371 | } = TestViewIncludedNotifier.welcome_layout()
372 | end
373 | end
374 |
375 | describe "view included and formats defined" do
376 | test "render both html and text body with assigns" do
377 | assert %Email{
378 | html_body: "Welcome, Tony!\n",
379 | text_body: "Welcome, Tony!\n"
380 | } = TestViewIncludedNotifierFormats.welcome_assigns()
381 | end
382 |
383 | test "render both html and text body with layout" do
384 | assert %Email{
385 | html_body: "Welcome, Avengers!\n\n",
386 | text_body: "TEXT: Welcome, Avengers!\n\n"
387 | } = TestViewIncludedNotifierFormats.welcome_layout()
388 | end
389 | end
390 |
391 | test "should raise if no view is set" do
392 | assert_raise ArgumentError, fn ->
393 | defmodule ErrorEmail do
394 | use Phoenix.Swoosh
395 | end
396 | end
397 | end
398 |
399 | test "body formats are set according to template file extension", %{email: email} do
400 | assert email |> render_body("format_html.html", %{}) |> Map.fetch!(:html_body) =~
401 | "HTML template with the .html extension"
402 |
403 | assert email |> render_body("format_html.htm", %{}) |> Map.fetch!(:html_body) =~
404 | "HTML template with the .htm extension"
405 |
406 | assert email |> render_body("format_html.xml", %{}) |> Map.fetch!(:html_body) =~
407 | "HTML template with the .xml extension"
408 |
409 | assert email |> render_body("format_text.txt", %{}) |> Map.fetch!(:text_body) =~
410 | "text template with the .txt extension"
411 |
412 | assert email |> render_body("format_text.text", %{}) |> Map.fetch!(:text_body) =~
413 | "text template with the .text extension"
414 |
415 | assert email |> render_body("format_text.unknown", %{}) |> Map.fetch!(:text_body) =~
416 | "text template with an unknown extension"
417 | end
418 | end
419 |
--------------------------------------------------------------------------------