Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ArgumentError: 1st argument: not an iodata term #303

Open
TheArrowsmith opened this issue Apr 13, 2023 · 6 comments
Open

ArgumentError: 1st argument: not an iodata term #303

TheArrowsmith opened this issue Apr 13, 2023 · 6 comments

Comments

@TheArrowsmith
Copy link

TheArrowsmith commented Apr 13, 2023

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:

  1. Generate a new Phoenix 1.6 app and add phoenix_swoosh as a dependency.
  2. 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("someone@example.com")
      |> from({"Someone Else", "someoneelse@example.com"})
      |> 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
  1. 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>                                                                                                                                                                                                                                             
  1. 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:

image

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&lt;1.35421111/1 in EmailDemoWeb.NotifierView."email.text"/1&gt;, 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&lt;1.35421111/1 in EmailDemoWeb.NotifierView."email.text"/1&gt;, 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&gt;      render_to_iodata(module, template, assign) |&gt; IO.iodata_to_binary()
    565     end
    566   
    567     defp encode(content, template) do
    568       "." &lt;&gt; 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&gt;      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         |&gt; to("someone@example.com")
    11         |&gt; from({"Someone Else", "someoneelse@example.com"})
    12         |&gt; subject("Subject")
    13&gt;        |&gt; render_body(:email)
    14   
    15       with {:ok, _metadata} &lt;- 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&gt;      EmailDemo.Notifier.deliver
    6       render(conn, "index.html")
    7     end
    8   end
    
`lib/email_demo_web/controllers/page_controller.ex`

    1&gt;  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&gt;  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 -&gt;
    353           try do
    354&gt;            plug.call(piped_conn, plug.init(opts))
    355           else
    356             conn -&gt;
    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&gt;  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&gt;  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} -&gt;
    52             %{adapter: {@connection, req}} =
    53               conn
    54&gt;              |&gt; handler.call(opts)
    55               |&gt; 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) -&gt; {ok, Req, Env}
    35   	when Req::cowboy_req:req(), Env::cowboy_middleware:env().
    36   execute(Req, Env=#{handler := Handler, handler_opts := HandlerOpts}) -&gt;
    37&gt;  	try Handler:init(Req, HandlerOpts) of
    38   		{ok, Req2, State} -&gt;
    39   			Result = terminate(normal, Req2, State, Handler),
    40   			{ok, Req2, Env#{result =&gt; Result}};
    41   		{Mod, Req2, State} -&gt;
    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(_, _, []) -&gt;
    304   	ok;
    305   execute(Req, Env, [Middleware|Tail]) -&gt;
    306&gt;  	case Middleware:execute(Req, Env) of
    307   		{ok, Req2, Env2} -&gt;
    308   			execute(Req2, Env2, Tail);
    309   		{suspend, Module, Function, Args} -&gt;
    310   			proc_lib:hibernate(?MODULE, resume, [Env, Tail, Module, Function, Args]);
    311   		{stop, _Req2} -&gt;
    
`/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()]) -&gt; ok.
    293   request_process(Req, Env, Middlewares) -&gt;
    294   	try
    295&gt;  		execute(Req, Env, Middlewares)
    296   	catch
    297   		exit:Reason={shutdown, _}:Stacktrace -&gt;
    298   			erlang:raise(exit, Reason, Stacktrace);
    299   		exit:Reason:Stacktrace when Reason =/= normal, Reason =/= shutdown -&gt;
    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:%22dev.client@gtintel.io%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" =&gt; "lPkeFDviFNkVfyB9YOuNTiAh"}
@princemaple
Copy link
Member

Looks like you are using the classic setup. You are most likely missing the root. Please check the first usage option in readme.

@TheArrowsmith
Copy link
Author

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.

@princemaple
Copy link
Member

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.

@princemaple
Copy link
Member

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.

@TheArrowsmith
Copy link
Author

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.

@princemaple
Copy link
Member

Ah okay. 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants