Bamboo Rendering Error: Expects template to be a string, got: {:safe, [bunch_of_html]}

In attempting to render an email, Bamboo is throwing me errors:

[error] #PID<0.1751.0> running UnaffiliatedStudiosWeb.Endpoint (connection #PID<0.1741.0>, stream id 3) terminated
Server: localhost:4000 (http)
Request: POST /contact
** (exit) an exception was raised:
    ** (ArgumentError) render/2 expects template to be a string, got: {:safe, ["<tr>\n    <td style=\"padding:30px;background-color:#ffffff;\">\n        <h1 style=\"margin-top:0;margin-bottom:16px;font-size:26px;line-height:32px;font-weight:bold;letter-spacing:-0.02em;\">Welcome to our responsive email!</h1>\n        <p style=\"margin:0;\">Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tempus adipiscing felis, sit amet blandit ipsum volutpat sed. Morbi porttitor, <a href=\"http://www.example.com/\" style=\"color:#e50d70;text-decoration:underline;\">eget accumsan dictum</a>, nisi libero ultricies ipsum, in posuere mauris neque at erat.</p>\n    </td>\n</tr>\n<tr>\n    <td style=\"padding:0;font-size:24px;line-height:28px;font-weight:bold;\">\n        <a href=\"http://www.example.com/\" style=\"text-decoration:none;\"><img src=\"images/1200x800-2.png\" width=\"600\" alt=\"\" style=\"width:100%;height:auto;display:block;border:none;text-decoration:none;color:#363636;\"></a>\n    </td>\n</tr>\n<tr>\n    <td style=\"padding:35px 30px 11px 30px;font-size:0;background-color:#ffffff;border-bottom:1px solid #f0f0f5;border-color:rgba(201,201,207,.35);\">\n        <!--[if mso]>\n        <table role=\"presentation\" width=\"100%\">\n        <tr>\n        <td style=\"width:145px;\" align=\"left\" valign=\"top\">\n        <![endif]-->\n            <div class=\"col-sml\" style=\"display:inline-block;width:100%;max-width:145px;vertical-align:top;text-align:left;font-family:Arial,sans-serif;font-size:14px;color:#363636;\">\n                <img src=\"images/icon.png\" width=\"115\" alt=\"\" style=\"width:80%;max-width:115px;margin-bottom:20px;\">\n            </div>\n        <!--[if mso]>\n        </td>\n        <td style=\"width:395px;padding-bottom:20px;\" valign=\"top\">\n        <![endif]-->\n            <div class=\"col-lge\" style=\"display:inline-block;width:100%;max-width:395px;vertical-align:top;padding-bottom:20px;font-family:Arial,sans-serif;font-size:16px;line-height:22px;color:#363636;\">\n                <p style=\"margin-top:0;margin-bottom:12px;\">Nullam mollis sapien vel cursus fermentum. Integer porttitor augue id ligula facilisis pharetra. In eu ex et elit ultricies ornare nec ac ex. Mauris sapien massa, placerat non venenatis et, tincidunt eget leo.</p>\n                <p style=\"margin-top:0;margin-bottom:18px;\">Nam non ante risus. Vestibulum vitae eleifend nisl, quis vehicula justo. Integer viverra efficitur pharetra. Nullam eget erat nibh.</p>\n                <p style=\"margin:0;\"><a href=\"https://example.com/\" style=\"background: #ff3884; text-decoration: none; padding: 10px 25px; color: #ffffff; border-radius: 4px; display:inline-block; mso-padding-alt:0;text-underline-color:#ff3884\"><!--[if mso]><i style=\"letter-spacing: 25px;mso-font-width:-100%;mso-text-raise:20pt\">&nbsp;</i><![endif]--><span style=\"mso-text-raise:10pt;font-weight:bold;\">Claim yours now</span><!--[if mso]><i style=\"letter-spacing: 25px;mso-font-width:-100%\">&nbsp;</i><![endif]--></a></p>\n            </div>\n        <!--[if mso]>\n        </td>\n        </tr>\n        </table>\n        <![endif]-->\n    </td>\n</tr>\n<tr>\n    <td style=\"padding:30px;font-size:24px;line-height:28px;font-weight:bold;background-color:#ffffff;border-bottom:1px solid #f0f0f5;border-color:rgba(201,201,207,.35);\">\n        <a href=\"http://www.example.com/\" style=\"text-decoration:none;\"><img src=\"images/1200x800-1.png\" width=\"540\" alt=\"\" style=\"width:100%;height:auto;border:none;text-decoration:none;color:#363636;\"></a>\n    </td>\n</tr>\n<tr>\n    <td style=\"padding:30px;background-color:#ffffff;\">\n        <p style=\"margin:0;\">Duis sit amet accumsan nibh, varius tincidunt lectus. Quisque commodo, nulla ac feugiat cursus, arcu orci condimentum tellus, vel placerat libero sapien et libero. Suspendisse auctor vel orci nec finibus.</p>\n    </td>\n</tr>\n"]}
        (unaffiliated_studios 0.5.1) lib/unaffiliated_studios_web/views/layout_view.ex:2: UnaffiliatedStudiosWeb.LayoutView.render/2
        (unaffiliated_studios 0.5.1) lib/unaffiliated_studios_web/templates/layout/email.html.eex:68: UnaffiliatedStudiosWeb.LayoutView."email.html"/1
        (phoenix 1.5.9) lib/phoenix/view.ex:472: Phoenix.View.render_to_iodata/3
        (phoenix 1.5.9) lib/phoenix/view.ex:479: Phoenix.View.render_to_string/3
        (bamboo_phoenix 1.0.0) lib/bamboo_phoenix.ex:291: Bamboo.Phoenix.render_text_or_html_email/1
        (unaffiliated_studios 0.5.1) lib/unaffiliated_studios_web/controllers/contact_controller.ex:12: UnaffiliatedStudiosWeb.ContactController.send/2
        (unaffiliated_studios 0.5.1) lib/unaffiliated_studios_web/controllers/contact_controller.ex:1: UnaffiliatedStudiosWeb.ContactController.action/2
        (unaffiliated_studios 0.5.1) lib/unaffiliated_studios_web/controllers/contact_controller.ex:1: UnaffiliatedStudiosWeb.ContactController.phoenix_controller_pipeline/2

Looking at this specific line:
(bamboo_phoenix 1.0.0) lib/bamboo_phoenix.ex:291: Bamboo.Phoenix.render_text_or_html_email/1
I decided to trace that back to its source:

cond do
      String.ends_with?(template, ".html") ->
        email |> Map.put(:html_body, render_html(email, template))

      String.ends_with?(template, ".text") ->
        email |> Map.put(:text_body, render_text(email, template))

      true ->
        raise ArgumentError, """
        Template name must end in either ".html" or ".text". Template name was #{
          inspect(template)
        }
        If you would like to render both and html and text template,
        use an atom without an extension instead.
        """
    end

The string, near as I can tell, ends with “html” as far as Elixir/Phoenix is concerned, as it’s named contact-email.html.eex (or the other one I have so far, response-email.html.eex). I’m not trying to use both html and text [yet], so no atom is being sent. The function(s) calling it look like this:

On the controller

def send(conn, %{"email_params" => email_params} = params) do
    case validate_changeset(email_params) do
      {:ok, contact_form} ->
        contact_form
        |> EmailBuilder.response_email()
        |> Mailer.deliver_later()

        conn
        |> put_flash(:info, "Email sent")
        |> redirect(to: Routes.index_path(conn, :index))

      {:error, changeset} ->
        conn
        |> put_status(:unprocessable_entity)
        |> put_flash(:error, "Please fix the errors below.")
        |> redirect(to: Routes.page_path(conn, :contact, changeset: changeset))
    end

In my email_builder.ex:

def response_email(contact_form) do
    base_email()
    |> to(contact_form.email)
    |> subject("We've Received Your Email")
    |> render("response-email.html")
  end

  def contact_email(contact_form) do
    base_email()
    |> to({"Info", "[REDACTED]"})
    |> assign(:contact_info, contact_form)
    |> subject("[CONTACT] A New Contact Has Arrived!")
    |> render("contact-email.html")
  end

  defp base_email do
    new_email()
    |> from({"Unaffiliated Studios", "[REDACTED]"})
    |> put_html_layout({UnaffiliatedStudiosWeb.LayoutView, "email.html"})
  end

I cannot see where the error is coming from. I acknowledge that it’s likely right in my face and inexperience is causing some blindness there.

1 Like

Hey, guess what happens when you really get into the thing? Turns out, this:

<%= render @inner_content %> should be <%= @inner_content %>. As such, I was rendering allllll the whole tuple rather than just the HTML bits.

2 Likes