diff --git a/scipost_django/submissions/constants.py b/scipost_django/submissions/constants.py
index a4519b4a690a00c8630503c0c01ee9c8f7529afb..8c2b6ee5af1b05882daba84d29fb8fe6442ea629 100644
--- a/scipost_django/submissions/constants.py
+++ b/scipost_django/submissions/constants.py
@@ -1,14 +1,19 @@
 __copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
 __license__ = "AGPL v3"
 
+import itertools
 
-ED_COMM_CHOICES = (
-    ("EtoA", "Editor-in-charge to Author"),
-    ("EtoR", "Editor-in-charge to Referee"),
-    ("EtoS", "Editor-in-charge to SciPost Editorial Administration"),
-    ("AtoE", "Author to Editor-in-charge"),
-    ("RtoE", "Referee to Editor-in-charge"),
-    ("StoE", "SciPost Editorial Administration to Editor-in-charge"),
+
+ED_COMM_PARTIES = (
+    ("E", "Editor-in-charge"),
+    ("A", "Author"),
+    ("R", "Referee"),
+    ("S", "SciPost Editorial Administration"),
+)
+ED_COMM_CHOICES = tuple(
+    (f"{X}to{Y}", f"{x} to {y}")
+    for (X, x), (Y, y) in itertools.product(ED_COMM_PARTIES, repeat=2)
+    if X != Y
 )
 
 REFEREE_QUALIFICATION = (
diff --git a/scipost_django/submissions/models/communication.py b/scipost_django/submissions/models/communication.py
index c7742e92eac3553271c5c802ae92a1f8c3c4142b..657fe8bd4f0581f91a7e0b5d5b8ab735a97d3209 100644
--- a/scipost_django/submissions/models/communication.py
+++ b/scipost_django/submissions/models/communication.py
@@ -6,6 +6,8 @@ from django.db import models
 from django.urls import reverse
 from django.utils import timezone
 
+from common.utils.models import get_current_domain
+
 from ..behaviors import SubmissionRelatedObjectMixin
 from ..constants import ED_COMM_CHOICES
 from ..managers import EditorialCommunicationQuerySet
@@ -51,3 +53,59 @@ class EditorialCommunication(SubmissionRelatedObjectMixin, models.Model):
     def get_absolute_url(self):
         """Return the url of the related Submission detail page."""
         return self.submission.get_absolute_url()
+
+    def _resolve_contributor_from_letter(self, letter: str):
+        recipients: dict[str, "Contributor | None"] = {
+            "E": self.submission.editor_in_charge,
+            "A": self.submission.submitted_by,
+            "R": self.referee,
+        }
+
+        return recipients.get(letter)
+
+    def _resolve_contributor_name(self, letter: str):
+        if letter == "S":
+            return "SciPost Editorial Administration"
+        elif contributor := self._resolve_contributor_from_letter(letter):
+            return f"{contributor.profile_title} {contributor.user.last_name}"
+        return "Unknown"
+
+    def _resolve_contributor_email(self, letter: str):
+        if letter == "S":
+            domain = get_current_domain()
+            return f"editorial@{domain}"
+        elif contributor := self._resolve_contributor_from_letter(letter):
+            return contributor.user.email
+        return ""
+
+    @property
+    def author_letter(self):
+        return self.comtype[0]
+
+    @property
+    def recipient_letter(self):
+        return self.comtype[-1]
+
+    @property
+    def author(self):
+        return self._resolve_contributor_from_letter(self.author_letter)
+
+    @property
+    def recipient(self):
+        return self._resolve_contributor_from_letter(self.recipient_letter)
+
+    @property
+    def author_name(self):
+        return self._resolve_contributor_name(self.author_letter)
+
+    @property
+    def recipient_name(self):
+        return self._resolve_contributor_name(self.recipient_letter)
+
+    @property
+    def author_email(self):
+        return self._resolve_contributor_email(self.author_letter)
+
+    @property
+    def recipient_email(self):
+        return self._resolve_contributor_email(self.recipient_letter)
diff --git a/scipost_django/submissions/utils.py b/scipost_django/submissions/utils.py
index 015a2564e9dd0544c0edd57915c240d85fbb6413..8cb1e566c84812c6188aa627c0fffeccc6a37f85 100644
--- a/scipost_django/submissions/utils.py
+++ b/scipost_django/submissions/utils.py
@@ -15,6 +15,8 @@ from django.template import Context, Template
 from common.utils.urls import absolute_reverse
 
 from .constants import (
+    ED_COMM_CHOICES,
+    ED_COMM_PARTIES,
     STATUS_VETTED,
     STATUS_UNCLEAR,
     STATUS_INCORRECT,
@@ -725,15 +727,6 @@ class SubmissionUtils(BaseMailUtil):
         """
         recipient_email = []
         bcc_emails = []
-        further_action_page = None
-
-        PARTIES = ["E", "A", "R", "S"]
-        # Allow only comtype to and from E(ditor)
-        valid_comtypes = [
-            f"{x}to{y}"
-            for x, y in itertools.product(PARTIES, repeat=2)
-            if "E" in [x, y] and x != y
-        ]
 
         communication: "EditorialCommunication | None" = getattr(
             cls, "communication", None
@@ -741,44 +734,23 @@ class SubmissionUtils(BaseMailUtil):
         if communication is None:
             raise ValueError("No communication attribute found. Please `.load()` it.")
 
+        valid_comtypes = [comtype[0] for comtype in ED_COMM_CHOICES]
         if communication.comtype not in valid_comtypes:
             raise ValueError(
                 f"Invalid comtype {communication.comtype}. "
                 f"Valid comtypes are {valid_comtypes}."
             )
 
-        recipients: dict[str, "Contributor | None"] = {
-            "E": communication.submission.editor_in_charge,
-            "A": communication.submission.submitted_by,
-            "R": communication.referee,
-        }
-
-        author, recipient = re.match(r"(\w)to(\w)", communication.comtype).groups()
-
-        # Use standard greeting for all communications except to Editorial Administrator
-        if recipient != "S":
-            recipient_contributor = recipients.get(recipient, None)
-            if recipient_contributor is None:
-                raise ValueError(
-                    f"Recipient not found for comtype {communication.comtype}. Must be one of {recipients.keys()}"
-                )
-
-            recipient_email.append(recipient_contributor.user.email)
-            recipient_greeting = f"Dear {recipient_contributor.profile_title} {recipient_contributor.user.last_name}"
-
-            # BCC all non-edadmin communications to the Editorial Administrator
+        # BCC all non-edadmin communications to the Editorial Administrator
+        if communication.recipient_letter != "S":
             bcc_emails.append(f"submissions@{domain}")
-        else:
-            # Communication to Editorial Administrator doesn't have a specific recipient contributor
-            recipient_email.append(f"submissions@{domain}")
-            recipient_greeting = "Dear Editorial Administrators"
 
         # BCC all editor-authored communications to the Editor-in-charge
-        if author == "E":
-            bcc_emails.append(communication.submission.editor_in_charge.user.email)
+        if communication.author_letter == "E":
+            bcc_emails.append(communication.author_email)
 
         # Further action page for Editor and Editorial Administrator
-        if recipient in ["E", "S"]:  # EtoS and _toE
+        if communication.recipient_letter in ["E", "S"]:  # _toS and _toE
             editorial_page_url = absolute_reverse(
                 "submissions:editorial_page",
                 args=[communication.submission.preprint.identifier_w_vn_nr],
@@ -787,24 +759,32 @@ class SubmissionUtils(BaseMailUtil):
                 f"You can take follow-up actions from {editorial_page_url}."
             )
         # Further action page for Author and Referee
-        elif recipient in ["R", "A"]:  # EtoA and EtoR
+        elif communication.recipient_letter in ["R", "A"]:  # _toA and _toR
             preprint_identifier = communication.submission.preprint.identifier_w_vn_nr
-            communication_url = absolute_reverse(
+            reverse_communication_url = absolute_reverse(
                 "submissions:communication",
-                args=[preprint_identifier, recipient + "toE"],
+                args=[
+                    preprint_identifier,
+                    communication.recipient_letter + "to" + communication.author_letter,
+                    # Reverse the communication direction, e.g. EtoA -> AtoE
+                ],
             )
             submission_page_url = absolute_reverse(
                 "submissions:submission",
                 args=[preprint_identifier],
             )
+
+            # Create a dictionary mapping author letters to author kinds
+            author_kinds = dict(zip(*zip(*ED_COMM_PARTIES)))
+            author_kind = author_kinds.get(communication.author_letter)
             further_action = (
-                f"To reply to the Editor-in-charge, please visit {communication_url}. "
-                "You can find all previous communications between you and the Editor-in-charge, as well as other communications tools, "
+                f"To reply to the {author_kind}, please visit {reverse_communication_url}. "
+                f"You can find all previous communications between you and the {author_kind}, as well as other communications tools, "
                 f"on the submission page at {submission_page_url} (login needed)."
             )
 
         email_text = (
-            f"{recipient_greeting},\n\n"
+            f"Dear {communication.recipient_name},\n\n"
             f"Please find here a communication ({communication.get_comtype_display()}) concerning Submission:\n\n"
             f"{communication.submission.title}\nby {communication.submission.author_list}.\n"
             f"(https://{domain}/{communication.submission.get_absolute_url()})\n\n"
@@ -819,10 +799,10 @@ class SubmissionUtils(BaseMailUtil):
         )
 
         emailmessage = EmailMessage(
-            "SciPost: communication (" + communication.get_comtype_display() + ")",
+            f"SciPost: communication ({communication.get_comtype_display()})",
             email_text,
             f"SciPost Editorial Admin <submissions@{domain}>",
-            recipient_email,
+            [communication.recipient_email],
             bcc_emails,
             reply_to=[f"submissions@{domain}"],
         )