phoenix_swoosh
phoenix_swoosh copied to clipboard
ArgumentError: 1st argument: not an iodata term
I'm getting an error when trying to send an email with both an HTML and text version using render_body.
I made a minimal app here that replicates the problem. Steps:
- Generate a new Phoenix 1.6 app and add
phoenix_swooshas a dependency. - Add a notifier class with a corresponding view:
# lib/email_demo/notifier.ex
defmodule EmailDemo.Notifier do
import Swoosh.Email
use Phoenix.Swoosh, view: EmailDemoWeb.NotifierView
alias EmailDemo.Mailer
def deliver do
email =
new()
|> to("[email protected]")
|> from({"Someone Else", "[email protected]"})
|> subject("Subject")
|> render_body(:email)
with {:ok, _metadata} <- Mailer.deliver(email) do
{:ok, email}
end
end
end
# lib/email_demo_web/views/notifier_view.ex
defmodule EmailDemoWeb.NotifierView do
use EmailDemoWeb, :view
end
- Add HTML and text templates:
lib/email_demo_web/templates/notifier/email.text.heex:
This is a plaintext email
lib/email_demo_web/templates/notifier/email.html.heex:
<p>This is an HTML email</p>
- Set things up to deliver an email when you visit the home page:
defmodule EmailDemoWeb.PageController do
use EmailDemoWeb, :controller
def index(conn, _params) do
EmailDemo.Notifier.deliver
render(conn, "index.html")
end
end
Now visit the homepage aaaaand... kaboom:
Is this a bug, or am I doing something wrong?
To make this searchable I'm going to c&p the whole error and stacktrace:
# ArgumentError at GET /
Exception:
** (ArgumentError) errors were found at the given arguments:
* 1st argument: not an iodata term
:erlang.iolist_to_binary(%Phoenix.LiveView.Rendered{static: ["This is a plaintext email\n"], dynamic: #Function<1.35421111/1 in EmailDemoWeb.NotifierView."email.text"/1>, fingerprint: 61109027427193190218149404380223381180, root: false})
(phoenix_view 2.0.2) lib/phoenix_view.ex:564: Phoenix.View.render_to_string/3
(phoenix_swoosh 1.2.0) lib/phoenix_swoosh.ex:172: Phoenix.Swoosh.do_render_body/4
(elixir 1.14.3) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
(email_demo 0.1.0) lib/email_demo/notifier.ex:13: EmailDemo.Notifier.deliver/0
(email_demo 0.1.0) lib/email_demo_web/controllers/page_controller.ex:5: EmailDemoWeb.PageController.index/2
(email_demo 0.1.0) lib/email_demo_web/controllers/page_controller.ex:1: EmailDemoWeb.PageController.action/2
(email_demo 0.1.0) lib/email_demo_web/controllers/page_controller.ex:1: EmailDemoWeb.PageController.phoenix_controller_pipeline/2
(phoenix 1.6.16) lib/phoenix/router.ex:354: Phoenix.Router.__call__/2
(email_demo 0.1.0) lib/email_demo_web/endpoint.ex:1: EmailDemoWeb.Endpoint.plug_builder_call/2
(email_demo 0.1.0) lib/plug/debugger.ex:136: EmailDemoWeb.Endpoint."call (overridable 3)"/2
(email_demo 0.1.0) lib/email_demo_web/endpoint.ex:1: EmailDemoWeb.Endpoint.call/2
(phoenix 1.6.16) lib/phoenix/endpoint/cowboy2_handler.ex:54: Phoenix.Endpoint.Cowboy2Handler.init/4
(cowboy 2.9.0) /Users/george/arrowsmith/por/email_demo/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
(cowboy 2.9.0) /Users/george/arrowsmith/por/email_demo/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
(cowboy 2.9.0) /Users/george/arrowsmith/por/email_demo/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3
(stdlib 4.3) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Code:
`nofile`
No code available.
Called with 1 arguments
* `%Phoenix.LiveView.Rendered{static: ["This is a plaintext email\n"], dynamic: #Function<1.35421111/1 in EmailDemoWeb.NotifierView."email.text"/1>, fingerprint: 61109027427193190218149404380223381180, root: false}`
`lib/phoenix_view.ex`
559
560 @doc """
561 Renders the template and returns a string.
562 """
563 def render_to_string(module, template, assign) do
564> render_to_iodata(module, template, assign) |> IO.iodata_to_binary()
565 end
566
567 defp encode(content, template) do
568 "." <> format = Path.extname(template)
569
`lib/phoenix_swoosh.ex`
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.
`lib/enum.ex`
No code available.
`lib/email_demo/notifier.ex`
8 email =
9 new()
10 |> to("[email protected]")
11 |> from({"Someone Else", "[email protected]"})
12 |> subject("Subject")
13> |> render_body(:email)
14
15 with {:ok, _metadata} <- Mailer.deliver(email) do
16 {:ok, email}
17 end
18 end
`lib/email_demo_web/controllers/page_controller.ex`
1 defmodule EmailDemoWeb.PageController do
2 use EmailDemoWeb, :controller
3
4 def index(conn, _params) do
5> EmailDemo.Notifier.deliver
6 render(conn, "index.html")
7 end
8 end
`lib/email_demo_web/controllers/page_controller.ex`
1> defmodule EmailDemoWeb.PageController do
2 use EmailDemoWeb, :controller
3
4 def index(conn, _params) do
5 EmailDemo.Notifier.deliver
6 render(conn, "index.html")
`lib/email_demo_web/controllers/page_controller.ex`
1> defmodule EmailDemoWeb.PageController do
2 use EmailDemoWeb, :controller
3
4 def index(conn, _params) do
5 EmailDemo.Notifier.deliver
6 render(conn, "index.html")
`lib/phoenix/router.ex`
349 metadata = %{metadata | conn: halted_conn}
350 :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)
351 halted_conn
352 %Plug.Conn{} = piped_conn ->
353 try do
354> plug.call(piped_conn, plug.init(opts))
355 else
356 conn ->
357 measurements = %{duration: System.monotonic_time() - start}
358 metadata = %{metadata | conn: conn}
359 :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)
`lib/email_demo_web/endpoint.ex`
1> defmodule EmailDemoWeb.Endpoint do
2 use Phoenix.Endpoint, otp_app: :email_demo
3
4 # The session will be stored in the cookie and signed,
5 # this means its contents can be read but not tampered with.
6 # Set :encryption_salt if you would also like to encrypt it.
`lib/plug/debugger.ex`
No code available.
`lib/email_demo_web/endpoint.ex`
1> defmodule EmailDemoWeb.Endpoint do
2 use Phoenix.Endpoint, otp_app: :email_demo
3
4 # The session will be stored in the cookie and signed,
5 # this means its contents can be read but not tampered with.
6 # Set :encryption_salt if you would also like to encrypt it.
`lib/phoenix/endpoint/cowboy2_handler.ex`
49 end
50
51 {:plug, conn, handler, opts} ->
52 %{adapter: {@connection, req}} =
53 conn
54> |> handler.call(opts)
55 |> maybe_send(handler)
56
57 {:ok, req, {handler, opts}}
58 end
59 catch
`/Users/george/arrowsmith/por/email_demo/deps/cowboy/src/cowboy_handler.erl`
32 -optional_callbacks([terminate/3]).
33
34 -spec execute(Req, Env) -> {ok, Req, Env}
35 when Req::cowboy_req:req(), Env::cowboy_middleware:env().
36 execute(Req, Env=#{handler := Handler, handler_opts := HandlerOpts}) ->
37> try Handler:init(Req, HandlerOpts) of
38 {ok, Req2, State} ->
39 Result = terminate(normal, Req2, State, Handler),
40 {ok, Req2, Env#{result => Result}};
41 {Mod, Req2, State} ->
42 Mod:upgrade(Req2, Env, Handler, State);
`/Users/george/arrowsmith/por/email_demo/deps/cowboy/src/cowboy_stream_h.erl`
301 end.
302
303 execute(_, _, []) ->
304 ok;
305 execute(Req, Env, [Middleware|Tail]) ->
306> case Middleware:execute(Req, Env) of
307 {ok, Req2, Env2} ->
308 execute(Req2, Env2, Tail);
309 {suspend, Module, Function, Args} ->
310 proc_lib:hibernate(?MODULE, resume, [Env, Tail, Module, Function, Args]);
311 {stop, _Req2} ->
`/Users/george/arrowsmith/por/email_demo/deps/cowboy/src/cowboy_stream_h.erl`
290 %% to simplify the debugging of errors. The proc_lib library
291 %% already adds the stacktrace to other types of exceptions.
292 -spec request_process(cowboy_req:req(), cowboy_middleware:env(), [module()]) -> ok.
293 request_process(Req, Env, Middlewares) ->
294 try
295> execute(Req, Env, Middlewares)
296 catch
297 exit:Reason={shutdown, _}:Stacktrace ->
298 erlang:raise(exit, Reason, Stacktrace);
299 exit:Reason:Stacktrace when Reason =/= normal, Reason =/= shutdown ->
300 erlang:raise(exit, {Reason, Stacktrace}, Stacktrace)
`proc_lib.erl`
No code available.
## Connection details
### Params
%{}
### Request info
* URI: http://localhost:4000/
* Query string:
### Headers
* accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8
* accept-encoding: gzip, deflate, br
* accept-language: en-GB,en-US;q=0.9,en;q=0.8
* connection: keep-alive
* cookie: _lr_uf_-wly4ri=c1297a67-a292-4fe8-952e-1ca73a54510e; _twittex_key=SFMyNTY.g3QAAAADbQAAAAtfY3NyZl90b2tlbm0AAAAYWGxOV1o4bGptRzkwbkgzNHNFUi1pZHl0bQAAAA5saXZlX3NvY2tldF9pZG0AAAA7dXNlcnNfc2Vzc2lvbnM6Z3Qta1B1U3lhOUxqWjdGV0hITEZjWkpjaGRTcEhnMlRpeElneGdfY1M3RT1tAAAACnVzZXJfdG9rZW5tAAAAIILfpD7ksmvS42exVhxyxXGSXIXUqR4Nk4sSIMYP3Eux.CHLFN4kkQh9nXtGic0M8f9rSWzgc1zqMH63X-ly26Wc; csrftoken=wWhMDQYEifcfCTKhpo7GaE6k9lbvQbA1podKQ6mlls5EiY4AwVr447DJHhRNdPao; _fly_builder_key=SFMyNTY.g3QAAAADbQAAAAtfY3NyZl90b2tlbm0AAAAYa0MyWWpXeDBOQlViLWFwUEQ2VUVpamgwbQAAAA5saXZlX3NvY2tldF9pZG0AAAA7dXNlcnNfc2Vzc2lvbnM6cGdxTFcxLXRDQkJaY282bVFjanR3MFpjNE1GbGpHNm5ual83dVpDMkJGZz1tAAAACnVzZXJfdG9rZW5tAAAAIKYKi1tfrQgQWXKOpkHI7cNGXODBZYxup54_-7mQtgRY.ObIY2uH80F17Wt_LJwOy-cZTGoP1YPG0-x75CLNMzEM; _pensieve_key=SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYZTVIT3gwRlFPN0J6ZElNbHRmVjFQMExF.R2vohSltVMsk459cA21yTfqKUHEiFGmGs6CPbbGwkCk; _blade_key=SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYMFJDMmRnMm5ya1BoNHJPX0JnbXhTS0pZ.GY0zn1OgK5rxMhsxYAGH6JEWNUYy8QgPzwBjVVfQRGM; token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjgxNTY4ODE5LCJpYXQiOjE2ODEzMDk2MTksImp0aSI6ImJlYWNiMjdlMjcwYTQyMjNhMTcyY2RiMmYzODg0YTFjIiwidXNlcl9pZCI6M30.secxRldpkgs9UkgcbPCQne75k7u-KwWtLMZgeUutPd0; refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTY4MTM5NjAxOSwiaWF0IjoxNjgxMzA5NjE5LCJqdGkiOiI2Yzc3MzkzNWZiMmE0YjUyODVkMTFjYzQzYjIzMGEzOCIsInVzZXJfaWQiOjN9.fBO1z9ShpQ9gAqfaFX8rA82eDeRoTjoGfnScLoahgvg; client_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjgxNTY4ODE5LCJpYXQiOjE2ODEzMDk2MTksImp0aSI6ImJlYWNiMjdlMjcwYTQyMjNhMTcyY2RiMmYzODg0YTFjIiwidXNlcl9pZCI6M30.secxRldpkgs9UkgcbPCQne75k7u-KwWtLMZgeUutPd0; client_refresh=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTY4MTM5NjAxOSwiaWF0IjoxNjgxMzA5NjE5LCJqdGkiOiI2Yzc3MzkzNWZiMmE0YjUyODVkMTFjYzQzYjIzMGEzOCIsInVzZXJfaWQiOjN9.fBO1z9ShpQ9gAqfaFX8rA82eDeRoTjoGfnScLoahgvg; partner_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjgxNTY4ODE5LCJpYXQiOjE2ODEzMDk2MTksImp0aSI6ImJlYWNiMjdlMjcwYTQyMjNhMTcyY2RiMmYzODg0YTFjIiwidXNlcl9pZCI6M30.secxRldpkgs9UkgcbPCQne75k7u-KwWtLMZgeUutPd0; partner_refresh=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTY4MTM5NjAxOSwiaWF0IjoxNjgxMzA5NjE5LCJqdGkiOiI2Yzc3MzkzNWZiMmE0YjUyODVkMTFjYzQzYjIzMGEzOCIsInVzZXJfaWQiOjN9.fBO1z9ShpQ9gAqfaFX8rA82eDeRoTjoGfnScLoahgvg; infor_user={%22id%22:3%2C%22url%22:%22http://localhost:8000/api/users/3/%22%2C%22first_name%22:%22Test%22%2C%22last_name%22:%22Client%22%2C%22email%22:%[email protected]%22%2C%22phone%22:%22%22%2C%22alter_phone%22:null%2C%22authy_id%22:%22%22%2C%22company%22:%22Ground%20Truth%20Intelligence%22%2C%22street_address%22:%2227%20Dev%20Test%20Rd%22%2C%22apartment%22:%22%22%2C%22biography%22:%22%22%2C%22tags%22:[]%2C%22tag_groups%22:[]%2C%22avatar%22:null%2C%22is_client%22:true%2C%22is_administrator%22:false%2C%22is_network_partner%22:false%2C%22accept_batch%22:false%2C%22timezone%22:%22America/New_York%22%2C%22dual_account%22:null%2C%22is_active%22:true%2C%22full_name%22:%22Test%20Client%22%2C%22edit_role%22:false%2C%22is_auto_tag%22:true%2C%22is_deleted%22:false%2C%22notes%22:%22%22%2C%22team%22:{%22id%22:1%2C%22name%22:%22Test%20team%22}%2C%22role%22:%221%22%2C%22id_client%22:3}; type_view=Client%20Mode; tw=eyJhbGciOiJIUzI1NiIsImN0eSI6InR3aWxpby1mcGE7dj0xIiwidHlwIjoiSldUIn0.eyJqdGkiOiJTSzYzYWY2MjcwNWU0Yjk3ZTYyYWY1NTZiODRmNDM1NmQ3LTE2ODEzMTAxNjgiLCJncmFudHMiOnsiZGF0YV9zeW5jIjp7InNlcnZpY2Vfc2lkIjoiSVM2NmE3MzNmYWQ2YzIzNWRjNzM4MzVlMWE1N2ZkNGM2OCJ9LCJjaGF0Ijp7InNlcnZpY2Vfc2lkIjoiSVNjMGQwYTQ0YmQ5YzQ0YmQ1ODY3ZTZmMTRmMzQ4NjM5OCJ9LCJpZGVudGl0eSI6IjMifSwiaXNzIjoiU0s2M2FmNjI3MDVlNGI5N2U2MmFmNTU2Yjg0ZjQzNTZkNyIsImV4cCI6MTY4MTMxMzc2OCwibmJmIjoxNjgxMzEwMTY4LCJzdWIiOiJBQ2M1NzQwZGE5Y2JmYzQyZWZiYTc3ZTYyOTA0NWIxYWJmIn0.2qr8rLSVtfDRzP5QLMbLH9V94Y0lBQ89Yf3f14_XtWo; _trip_key=SFMyNTY.g3QAAAADbQAAAAtfY3NyZl90b2tlbm0AAAAYSmtjUHFMUTRudEJlbEQzNDdHU3BKQ3ZobQAAAA5saXZlX3NvY2tldF9pZG0AAAA7dXNlcnNfc2Vzc2lvbnM6MEMxUHRKdFdqeXZpMmk0b0RsaXlQOEJvS3h6dkRYNmc1N2N1WVZOMTVUQT1tAAAACnVzZXJfdG9rZW5tAAAAINAtT7SbVo8r4touKA5Ysj_AaCsc7w1-oOe3LmFTdeUw.A2Yyb3XvDcjrpAAkLWkG6SfcteZg724v6xeRNnuTMxM; _email_demo_key=SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYbFBrZUZEdmlGTmtWZnlCOVlPdU5UaUFo.4KfqbHoZnjCfirkBczxRssAvay5q1A6B7-Cirm3xMzw
* host: localhost:4000
* sec-ch-ua: "Brave";v="111", "Not(A:Brand";v="8", "Chromium";v="111"
* sec-ch-ua-mobile: ?0
* sec-ch-ua-platform: "macOS"
* sec-fetch-dest: document
* sec-fetch-mode: navigate
* sec-fetch-site: none
* sec-fetch-user: ?1
* sec-gpc: 1
* upgrade-insecure-requests: 1
* user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36
### Session
%{"_csrf_token" => "lPkeFDviFNkVfyB9YOuNTiAh"}
Looks like you are using the classic setup. You are most likely missing the root. Please check the first usage option in readme.
I'm not sure what you mean by missing the root. The :root option is provided by use EmailDemoWeb, :view in the normal Phoenix way:
defmodule EmailDemoWeb do
# …
def view do
quote do
use Phoenix.View,
root: "lib/email_demo_web/templates",
namespace: EmailDemoWeb
If you're suggesting that Swoosh is unable to find the templates at all, that's obviously not the case - just look in the screenshot. You can see that the contents of my text template ("This is a plaintext email") has been loaded within the %Phoenix.LiveView.Rendered{} struct.
The proximate cause of the problem is that the %Phoenix.LiveView.Rendered{} is being passed to :erlang.io_list_binary(), which doesn't know how to handle this type of argument. But I don't understand why this is happening.
You are right. My apologies. I'm currently travelling overseas and on the phone most of the time, and won't have the opportunity to pull down your code to try it. Might be a good idea to ask on the forum or in slack.
That being said, I do think you should start generating phoenix projects with 1.7 and take a look at the 1.7 issue in this repo.
Well yes, but this is for an existing 1.6 app, not a greenfield project, and I can't upgrade to 1.7 just yet.
Ah okay. 😅