Repository: swoosh/swoosh
Pull request #1032 (2025-04-10 20:10:56)

Redirect to latest message in mailbox if one exists
This would be super useful for `phx.gen.auth` magic links where we direct folks to `/dev/mailbox` in dev and they can hop to the most recent email for a more convenient login flow in dev

princemaple: Hi Chris, can I invite one of Phoenix members to be a maintainer on swoosh? They don't have to do anything, they can even mute notifications on swoosh. Due to my personal situation, sometimes I can't watch swoosh closely. I'm currently the only person who maintains swoosh, and I just want to bump the bus factor up and not block Phoenix in the future.
chrismccord: @princemaple invite me please!
princemaple: Done, thanks Chris! On the off chance that you have to release a new version urgently, simply bump the versions and make a GitHub release, it automatically pushes to hex.

Patch:
diff --git a/lib/plug/mailbox_preview.ex b/lib/plug/mailbox_preview.ex
index 4160ddc..5727d0c 100644
--- a/lib/plug/mailbox_preview.ex
+++ b/lib/plug/mailbox_preview.ex
@@ -58,10 +58,7 @@ if Code.ensure_loaded?(Plug) do
 
     post "/clear" do
       conn.assigns.storage_driver.delete_all()
-
-      conn
-      |> put_resp_header("location", conn.assigns.base_path)
-      |> send_resp(302, ~c"")
+      redir(conn, conn.assigns.base_path)
     end
 
     get "/json" do
@@ -74,11 +71,15 @@ if Code.ensure_loaded?(Plug) do
     end
 
     get "/" do
-      emails = conn.assigns.storage_driver.all()
+      case conn.assigns.storage_driver.all() do
+        [%Swoosh.Email{headers: %{"Message-ID" => id}} | _] ->
+          redir(conn, Path.join(conn.assigns.base_path, id))
 
-      conn
-      |> put_resp_content_type("text/html")
-      |> send_resp(200, template(emails: emails, email: nil, conn: conn))
+        emails ->
+          conn
+          |> put_resp_content_type("text/html")
+          |> send_resp(200, template(emails: emails, email: nil, conn: conn))
+      end
     end
 
     get "/:id/html" do
@@ -196,5 +197,15 @@ if Code.ensure_loaded?(Plug) do
         end
       end)
     end
+
+    defp redir(conn, location) do
+      conn
+      |> put_resp_header("location", location)
+      |> put_resp_content_type("text/html")
+      |> send_resp(
+        302,
+        "<html><body>You are being <a href=\"#{location}\">redirected</a>.</body></html>"
+      )
+    end
   end
 end


Test patch:
diff --git a/test/plug/mailbox_preview_test.exs b/test/plug/mailbox_preview_test.exs
index 689ed65..0e97bce 100644
--- a/test/plug/mailbox_preview_test.exs
+++ b/test/plug/mailbox_preview_test.exs
@@ -1,7 +1,7 @@
 defmodule Plug.Swoosh.MailboxPreviewTest do
   use ExUnit.Case, async: true
-  import Plug.Test
-  import Plug.Conn
+
+  import Plug.{Conn, Test}
 
   alias Plug.Swoosh.MailboxPreview
 
@@ -48,6 +48,12 @@ defmodule Plug.Swoosh.MailboxPreviewTest do
     end
   end
 
+  defmodule EmptyDriver do
+    def all, do: []
+    def get(_id), do: nil
+    def template_model, do: %{}
+  end
+
   describe "/json" do
     test "renders emails in json" do
       opts = MailboxPreview.init(storage_driver: StorageDriver)
@@ -111,6 +117,26 @@ defmodule Plug.Swoosh.MailboxPreviewTest do
     end
   end
 
+  describe "/" do
+    test "with existing messages redirects to most recent" do
+      opts = MailboxPreview.init(storage_driver: StorageDriver)
+
+      conn = conn(:get, "/")
+      conn = MailboxPreview.call(conn, opts)
+
+      assert conn.state == :sent
+      assert conn.status == 302
+      assert get_resp_header(conn, "location") == ["/1"]
+    end
+
+    test "with no messages redirects to base path" do
+      opts = MailboxPreview.init(storage_driver: EmptyDriver)
+      conn = conn(:get, "/")
+      conn = MailboxPreview.call(conn, opts)
+      assert get_resp_header(conn, "location") == []
+    end
+  end
+
   describe "/:id/attachments/:index" do
     test "download attachment" do
       opts = MailboxPreview.init(storage_driver: StorageDriver)