diff --git a/modules/services/emacs.nix b/modules/services/emacs.nix
index 560d9a6c4..b8ca9f02d 100644
--- a/modules/services/emacs.nix
+++ b/modules/services/emacs.nix
@@ -9,6 +9,9 @@ let
   emacsBinPath = "${cfg.package}/bin";
   emacsVersion = getVersion cfg.package;
 
+  clientWMClass =
+    if versionAtLeast emacsVersion "28" then "Emacsd" else "Emacs";
+
   # Adapted from upstream emacs.desktop
   clientDesktopItem = pkgs.writeTextDir "share/applications/emacsclient.desktop"
     (generators.toINI { } {
@@ -24,26 +27,15 @@ let
         GenericName = "Text Editor";
         MimeType =
           "text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;";
-        Categories = "Utility;TextEditor;";
-        StartupWMClass = "Emacs";
+        Categories = "Development;TextEditor;";
+        Keywords = "Text;Editor;";
+        StartupWMClass = clientWMClass;
       };
     });
 
   # Match the default socket path for the Emacs version so emacsclient continues
-  # to work without wrapping it. It might be worthwhile to allow customizing the
-  # socket path, but we would want to wrap emacsclient in the user profile to
-  # connect to the alternative socket by default for Emacs 26, and set
-  # EMACS_SOCKET_NAME for Emacs 27.
-  #
-  # As systemd doesn't perform variable expansion for the ListenStream param, we
-  # would also have to solve the problem of matching the shell path to the path
-  # used in the socket unit, which would likely involve templating. It seems of
-  # little value for the most common use case of one Emacs daemon per user
-  # session.
-  socketPath = if versionAtLeast emacsVersion "27" then
-    "%t/emacs/server"
-  else
-    "%T/emacs%U/server";
+  # to work without wrapping it.
+  socketPath = "%t/emacs/server";
 
 in {
   meta.maintainers = [ maintainers.tadfisher ];
@@ -82,24 +74,25 @@ in {
 
   config = mkIf cfg.enable (mkMerge [
     {
-      assertions = [{
-        assertion = cfg.socketActivation.enable
-          -> versionAtLeast emacsVersion "26";
-        message = "Socket activation requires Emacs 26 or newer.";
-      }];
-
       systemd.user.services.emacs = {
         Unit = {
-          Description = "Emacs: the extensible, self-documenting text editor";
+          Description = "Emacs text editor";
           Documentation =
             "info:emacs man:emacs(1) https://gnu.org/software/emacs/";
 
           # Avoid killing the Emacs session, which may be full of
           # unsaved buffers.
           X-RestartIfChanged = false;
+        } // optionalAttrs (cfg.socketActivation.enable) {
+          # Emacs deletes its socket when shutting down, which systemd doesn't
+          # handle, resulting in a server without a socket.
+          # See https://github.com/nix-community/home-manager/issues/2018
+          RefuseManualStart = true;
         };
 
         Service = {
+          Type = "notify";
+
           # We wrap ExecStart in a login shell so Emacs starts with the user's
           # environment, most importantly $PATH and $NIX_PROFILES. It may be
           # worth investigating a more targeted approach for user services to
@@ -113,9 +106,11 @@ in {
               optionalString cfg.socketActivation.enable
               "=${escapeShellArg socketPath}"
             }"'';
-          # We use '(kill-emacs 0)' to avoid exiting with a failure code, which
-          # would restart the service immediately.
-          ExecStop = "${emacsBinPath}/emacsclient --eval '(kill-emacs 0)'";
+
+          # Emacs will exit with status 15 after having received SIGTERM, which
+          # is the default "KillSignal" value systemd uses to stop services.
+          SuccessExitStatus = 15;
+
           Restart = "on-failure";
         };
       } // optionalAttrs (!cfg.socketActivation.enable) {
@@ -128,7 +123,7 @@ in {
     (mkIf cfg.socketActivation.enable {
       systemd.user.sockets.emacs = {
         Unit = {
-          Description = "Emacs: the extensible, self-documenting text editor";
+          Description = "Emacs text editor";
           Documentation =
             "info:emacs man:emacs(1) https://gnu.org/software/emacs/";
         };
diff --git a/tests/modules/services/emacs/default.nix b/tests/modules/services/emacs/default.nix
index af27538d9..86f68c4c8 100644
--- a/tests/modules/services/emacs/default.nix
+++ b/tests/modules/services/emacs/default.nix
@@ -1,5 +1,6 @@
 {
-  emacs-service = ./emacs-service.nix;
-  emacs-socket-26 = ./emacs-socket-26.nix;
+  emacs-service-27 = ./emacs-service-27.nix;
+  emacs-service-28 = ./emacs-service-28.nix;
   emacs-socket-27 = ./emacs-socket-27.nix;
+  emacs-socket-28 = ./emacs-socket-28.nix;
 }
diff --git a/tests/modules/services/emacs/emacs-emacsclient.desktop b/tests/modules/services/emacs/emacs-27-emacsclient.desktop
similarity index 87%
rename from tests/modules/services/emacs/emacs-emacsclient.desktop
rename to tests/modules/services/emacs/emacs-27-emacsclient.desktop
index 1dafb4e0f..89503c790 100644
--- a/tests/modules/services/emacs/emacs-emacsclient.desktop
+++ b/tests/modules/services/emacs/emacs-27-emacsclient.desktop
@@ -1,9 +1,10 @@
 [Desktop Entry]
-Categories=Utility;TextEditor;
+Categories=Development;TextEditor;
 Comment=Edit text
 Exec=@emacs@/bin/emacsclient -c %F
 GenericName=Text Editor
 Icon=emacs
+Keywords=Text;Editor;
 MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;
 Name=Emacs Client
 StartupWMClass=Emacs
diff --git a/tests/modules/services/emacs/emacs-28-emacsclient.desktop b/tests/modules/services/emacs/emacs-28-emacsclient.desktop
new file mode 100644
index 000000000..96b727525
--- /dev/null
+++ b/tests/modules/services/emacs/emacs-28-emacsclient.desktop
@@ -0,0 +1,12 @@
+[Desktop Entry]
+Categories=Development;TextEditor;
+Comment=Edit text
+Exec=@emacs@/bin/emacsclient -c %F
+GenericName=Text Editor
+Icon=emacs
+Keywords=Text;Editor;
+MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;
+Name=Emacs Client
+StartupWMClass=Emacsd
+Terminal=false
+Type=Application
diff --git a/tests/modules/services/emacs/emacs-service.nix b/tests/modules/services/emacs/emacs-service-27.nix
similarity index 89%
rename from tests/modules/services/emacs/emacs-service.nix
rename to tests/modules/services/emacs/emacs-service-27.nix
index be27e9ab3..fa5f9f62b 100644
--- a/tests/modules/services/emacs/emacs-service.nix
+++ b/tests/modules/services/emacs/emacs-service-27.nix
@@ -6,7 +6,7 @@ with lib;
   config = {
     nixpkgs.overlays = [
       (self: super: rec {
-        emacs = pkgs.writeShellScriptBin "dummy-emacs" "" // {
+        emacs = pkgs.writeShellScriptBin "dummy-emacs-27.2" "" // {
           outPath = "@emacs@";
         };
         emacsPackagesFor = _:
@@ -31,7 +31,7 @@ with lib;
                           }
                         }
       assertFileContent home-path/share/applications/emacsclient.desktop \
-                        ${./emacs-emacsclient.desktop}
+                        ${./emacs-27-emacsclient.desktop}
     '';
   };
 }
diff --git a/tests/modules/services/emacs/emacs-service-28.nix b/tests/modules/services/emacs/emacs-service-28.nix
new file mode 100644
index 000000000..092cd1453
--- /dev/null
+++ b/tests/modules/services/emacs/emacs-service-28.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  config = {
+    nixpkgs.overlays = [
+      (self: super: rec {
+        emacs = pkgs.writeShellScriptBin "dummy-emacs-28.0.5" "" // {
+          outPath = "@emacs@";
+        };
+        emacsPackagesFor = _:
+          makeScope super.newScope (_: { emacsWithPackages = _: emacs; });
+      })
+    ];
+
+    programs.emacs.enable = true;
+    services.emacs.enable = true;
+    services.emacs.client.enable = true;
+
+    nmt.script = ''
+      assertPathNotExists home-files/.config/systemd/user/emacs.socket
+      assertFileExists home-files/.config/systemd/user/emacs.service
+      assertFileExists home-path/share/applications/emacsclient.desktop
+
+      assertFileContent home-files/.config/systemd/user/emacs.service \
+                        ${
+                          pkgs.substituteAll {
+                            inherit (pkgs) runtimeShell;
+                            src = ./emacs-service-emacs.service;
+                          }
+                        }
+      assertFileContent home-path/share/applications/emacsclient.desktop \
+                        ${./emacs-28-emacsclient.desktop}
+    '';
+  };
+}
diff --git a/tests/modules/services/emacs/emacs-service-emacs.service b/tests/modules/services/emacs/emacs-service-emacs.service
index d8a618a26..00b0c8eb2 100644
--- a/tests/modules/services/emacs/emacs-service-emacs.service
+++ b/tests/modules/services/emacs/emacs-service-emacs.service
@@ -3,10 +3,11 @@ WantedBy=default.target
 
 [Service]
 ExecStart=@runtimeShell@ -l -c "@emacs@/bin/emacs --fg-daemon"
-ExecStop=@emacs@/bin/emacsclient --eval '(kill-emacs 0)'
 Restart=on-failure
+SuccessExitStatus=15
+Type=notify
 
 [Unit]
-Description=Emacs: the extensible, self-documenting text editor
+Description=Emacs text editor
 Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
 X-RestartIfChanged=false
diff --git a/tests/modules/services/emacs/emacs-socket-26-emacs.service b/tests/modules/services/emacs/emacs-socket-26-emacs.service
deleted file mode 100644
index 2d731c7ee..000000000
--- a/tests/modules/services/emacs/emacs-socket-26-emacs.service
+++ /dev/null
@@ -1,9 +0,0 @@
-[Service]
-ExecStart=@runtimeShell@ -l -c "@emacs@/bin/emacs --fg-daemon='%T/emacs%U/server'"
-ExecStop=@emacs@/bin/emacsclient --eval '(kill-emacs 0)'
-Restart=on-failure
-
-[Unit]
-Description=Emacs: the extensible, self-documenting text editor
-Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
-X-RestartIfChanged=false
diff --git a/tests/modules/services/emacs/emacs-socket-26-emacs.socket b/tests/modules/services/emacs/emacs-socket-26-emacs.socket
deleted file mode 100644
index d2fa78e22..000000000
--- a/tests/modules/services/emacs/emacs-socket-26-emacs.socket
+++ /dev/null
@@ -1,12 +0,0 @@
-[Install]
-WantedBy=sockets.target
-
-[Socket]
-DirectoryMode=0700
-FileDescriptorName=server
-ListenStream=%T/emacs%U/server
-SocketMode=0600
-
-[Unit]
-Description=Emacs: the extensible, self-documenting text editor
-Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
diff --git a/tests/modules/services/emacs/emacs-socket-27.nix b/tests/modules/services/emacs/emacs-socket-27.nix
index 213dedca5..22619937d 100644
--- a/tests/modules/services/emacs/emacs-socket-27.nix
+++ b/tests/modules/services/emacs/emacs-socket-27.nix
@@ -8,7 +8,7 @@ in {
   config = {
     nixpkgs.overlays = [
       (self: super: rec {
-        emacs = pkgs.writeShellScriptBin "dummy-emacs-27.0.91" "" // {
+        emacs = pkgs.writeShellScriptBin "dummy-emacs-27.2" "" // {
           outPath = "@emacs@";
         };
         emacsPackagesFor = _:
@@ -27,16 +27,16 @@ in {
       assertFileExists home-path/share/applications/emacsclient.desktop
 
       assertFileContent home-files/.config/systemd/user/emacs.socket \
-                        ${./emacs-socket-27-emacs.socket}
+                        ${./emacs-socket-emacs.socket}
       assertFileContent home-files/.config/systemd/user/emacs.service \
                         ${
                           pkgs.substituteAll {
                             inherit (pkgs) runtimeShell;
-                            src = ./emacs-socket-27-emacs.service;
+                            src = ./emacs-socket-emacs.service;
                           }
                         }
       assertFileContent home-path/share/applications/emacsclient.desktop \
-                        ${./emacs-emacsclient.desktop}
+                        ${./emacs-27-emacsclient.desktop}
     '';
   };
 }
diff --git a/tests/modules/services/emacs/emacs-socket-26.nix b/tests/modules/services/emacs/emacs-socket-28.nix
similarity index 80%
rename from tests/modules/services/emacs/emacs-socket-26.nix
rename to tests/modules/services/emacs/emacs-socket-28.nix
index 65f06159e..f04d93cbe 100644
--- a/tests/modules/services/emacs/emacs-socket-26.nix
+++ b/tests/modules/services/emacs/emacs-socket-28.nix
@@ -2,11 +2,13 @@
 
 with lib;
 
-{
+let
+
+in {
   config = {
     nixpkgs.overlays = [
       (self: super: rec {
-        emacs = pkgs.writeShellScriptBin "dummy-emacs-26.3" "" // {
+        emacs = pkgs.writeShellScriptBin "dummy-emacs-28.0.5" "" // {
           outPath = "@emacs@";
         };
         emacsPackagesFor = _:
@@ -25,16 +27,16 @@ with lib;
       assertFileExists home-path/share/applications/emacsclient.desktop
 
       assertFileContent home-files/.config/systemd/user/emacs.socket \
-                        ${./emacs-socket-26-emacs.socket}
+                        ${./emacs-socket-emacs.socket}
       assertFileContent home-files/.config/systemd/user/emacs.service \
                         ${
                           pkgs.substituteAll {
                             inherit (pkgs) runtimeShell;
-                            src = ./emacs-socket-26-emacs.service;
+                            src = ./emacs-socket-emacs.service;
                           }
                         }
       assertFileContent home-path/share/applications/emacsclient.desktop \
-                        ${./emacs-emacsclient.desktop}
+                        ${./emacs-28-emacsclient.desktop}
     '';
   };
 }
diff --git a/tests/modules/services/emacs/emacs-socket-27-emacs.service b/tests/modules/services/emacs/emacs-socket-emacs.service
similarity index 63%
rename from tests/modules/services/emacs/emacs-socket-27-emacs.service
rename to tests/modules/services/emacs/emacs-socket-emacs.service
index 408a5d24b..52d446cf4 100644
--- a/tests/modules/services/emacs/emacs-socket-27-emacs.service
+++ b/tests/modules/services/emacs/emacs-socket-emacs.service
@@ -1,9 +1,11 @@
 [Service]
 ExecStart=@runtimeShell@ -l -c "@emacs@/bin/emacs --fg-daemon='%t/emacs/server'"
-ExecStop=@emacs@/bin/emacsclient --eval '(kill-emacs 0)'
 Restart=on-failure
+SuccessExitStatus=15
+Type=notify
 
 [Unit]
-Description=Emacs: the extensible, self-documenting text editor
+Description=Emacs text editor
 Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
+RefuseManualStart=true
 X-RestartIfChanged=false
diff --git a/tests/modules/services/emacs/emacs-socket-27-emacs.socket b/tests/modules/services/emacs/emacs-socket-emacs.socket
similarity index 76%
rename from tests/modules/services/emacs/emacs-socket-27-emacs.socket
rename to tests/modules/services/emacs/emacs-socket-emacs.socket
index 8fa68bf59..7fffcb0d0 100644
--- a/tests/modules/services/emacs/emacs-socket-27-emacs.socket
+++ b/tests/modules/services/emacs/emacs-socket-emacs.socket
@@ -8,5 +8,5 @@ ListenStream=%t/emacs/server
 SocketMode=0600
 
 [Unit]
-Description=Emacs: the extensible, self-documenting text editor
+Description=Emacs text editor
 Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/