diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMention/ChatMentionListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMention/ChatMentionListener.java
index 7f9a0ba..13a32c9 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMention/ChatMentionListener.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMention/ChatMentionListener.java
@@ -1,6 +1,8 @@
 package eu.mhsl.craftattack.spawn.appliances.chatMention;
 
+import eu.mhsl.craftattack.spawn.Main;
 import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
+import eu.mhsl.craftattack.spawn.appliances.chatMessages.ChatMessages;
 import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
 import eu.mhsl.craftattack.spawn.util.text.ComponentUtil;
 import io.papermc.paper.event.player.AsyncChatDecorateEvent;
@@ -8,6 +10,7 @@ import net.kyori.adventure.text.Component;
 import net.kyori.adventure.text.format.NamedTextColor;
 import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
 import org.bukkit.event.EventHandler;
+import org.bukkit.event.player.PlayerJoinEvent;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -22,6 +25,7 @@ public class ChatMentionListener extends ApplianceListener<ChatMention> {
 
         ChatMentionSetting.ChatMentionConfig config = Settings.instance()
             .getSetting(event.player(), Settings.Key.ChatMentions, ChatMentionSetting.ChatMentionConfig.class);
+        ChatMessages chatMessages = Main.instance().getAppliance(ChatMessages.class);
 
         Component result = words.stream()
             .map(word -> {
@@ -29,10 +33,11 @@ public class ChatMentionListener extends ApplianceListener<ChatMention> {
                 boolean isPlayer = getAppliance().getPlayerNames().contains(wordWithoutAnnotation);
                 if(isPlayer && config.applyMentions()) {
                     mentioned.add(wordWithoutAnnotation);
-                    return Component.text(
+                    Component mention = Component.text(
                         getAppliance().formatPlayer(wordWithoutAnnotation),
                         NamedTextColor.GOLD
                     );
+                    return chatMessages.addReportActions(mention, wordWithoutAnnotation);
                 } else {
                     return Component.text(word);
                 }
@@ -43,4 +48,9 @@ public class ChatMentionListener extends ApplianceListener<ChatMention> {
         getAppliance().notifyPlayers(mentioned);
         event.result(result);
     }
+
+    @EventHandler
+    public void onJoin(PlayerJoinEvent event) {
+        getAppliance().refreshPlayers();
+    }
 }
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMessages/ChatMessages.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMessages/ChatMessages.java
index 337ab32..3f48ad3 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMessages/ChatMessages.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/chatMessages/ChatMessages.java
@@ -19,11 +19,13 @@ public class ChatMessages extends Appliance {
     }
 
     public Component getReportablePlayerName(Player player) {
-        return Component
-            .text("")
-            .append(player.displayName())
+        return addReportActions(player.displayName(), player.getName());
+    }
+
+    public Component addReportActions(Component message, String username) {
+        return message
             .hoverEvent(HoverEvent.showText(Component.text("Klicke, um diesen Spieler zu reporten").color(NamedTextColor.GOLD)))
-            .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/report " + player.getName() + " "));
+            .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND, String.format("/report %s ", username)));
     }
 
     @Override
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
new file mode 100644
index 0000000..6ab32d1
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/PrivateMessage.java
@@ -0,0 +1,187 @@
+package eu.mhsl.craftattack.spawn.appliances.privateMessage;
+
+import eu.mhsl.craftattack.spawn.Main;
+import eu.mhsl.craftattack.spawn.appliance.Appliance;
+import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
+import eu.mhsl.craftattack.spawn.appliances.chatMessages.ChatMessages;
+import eu.mhsl.craftattack.spawn.appliances.privateMessage.commands.PrivateMessageCommand;
+import eu.mhsl.craftattack.spawn.appliances.privateMessage.commands.PrivateReplyCommand;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.ComponentBuilder;
+import net.kyori.adventure.text.TextComponent;
+import net.kyori.adventure.text.event.ClickEvent;
+import net.kyori.adventure.text.event.HoverEvent;
+import net.kyori.adventure.text.format.NamedTextColor;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class PrivateMessage extends Appliance {
+    public final int targetChangeTimeoutSeconds = 30;
+    public final int conversationTimeoutMinutes = 30;
+
+    private record Conversation(UUID target, Long lastSet) {}
+    private final Map<Player, List<Conversation>> replyMapping = new WeakHashMap<>();
+
+    public void reply(Player sender, String message) {
+        this.replyMapping.computeIfAbsent(sender, player -> new ArrayList<>());
+
+        List<Conversation> replyList = this.replyMapping.get(sender);
+
+        List<Conversation> tooOldConversations = replyList.stream()
+            .filter(conversation -> conversation.lastSet < System.currentTimeMillis() - (conversationTimeoutMinutes*60*1000))
+            .toList();
+        replyList.removeAll(tooOldConversations);
+
+        if(replyList.isEmpty()) throw new ApplianceCommand.Error("Du führst aktuell keine Konversation.");
+
+        Component privatePrefix = Component.text("[Privat] ", NamedTextColor.LIGHT_PURPLE);
+
+        Conversation youngestEntry = replyList.stream()
+            .max(Comparator.comparingLong(o -> o.lastSet))
+            .orElse(replyList.getLast());
+
+        if(message.isBlank()) {
+            Player currentTargetPlayer = Bukkit.getPlayer(youngestEntry.target());
+
+            Component currentTargetComponent = currentTargetPlayer != null
+                ? Main.instance().getAppliance(ChatMessages.class).getReportablePlayerName(currentTargetPlayer)
+                : Component.text("niemandem.");
+
+            sender.sendMessage(
+                privatePrefix
+                    .append(Component.text("Du schreibst aktuell mit ", NamedTextColor.GRAY))
+                    .append(currentTargetComponent)
+            );
+            return;
+        }
+
+        List<Conversation> oldConversations = replyList.stream()
+            .filter(conversation -> conversation.lastSet < System.currentTimeMillis() - (targetChangeTimeoutSeconds*1000))
+            .toList();
+
+        if(oldConversations.contains(youngestEntry) || replyList.size() == 1) {
+            Player target = Bukkit.getPlayer(youngestEntry.target());
+            if(target == null) throw new ApplianceCommand.Error("Der Spieler " + Bukkit.getOfflinePlayer(youngestEntry.target()).getName() + " ist nicht mehr verfügbar.");
+
+            replyList.clear();
+            this.sendWhisper(sender, new ResolvedPmUserArguments(target, message));
+            return;
+        }
+
+        ComponentBuilder<TextComponent, TextComponent.Builder> component = Component.text();
+
+        component.append(
+            Component.newline()
+                .append(privatePrefix)
+                .append(Component.text("Das Ziel für /r hat sich bei dir in den letzten ", NamedTextColor.RED))
+                .append(Component.text(String.valueOf(this.targetChangeTimeoutSeconds), NamedTextColor.RED))
+                .append(Component.text(" Sekunden geändert. Wer soll deine Nachricht erhalten? ", NamedTextColor.RED))
+                .appendNewline()
+                .appendNewline()
+        );
+
+        if(!oldConversations.isEmpty()) {
+            Conversation youngestOldConversation = oldConversations.stream()
+                .max(Comparator.comparingLong(o -> o.lastSet))
+                .orElse(oldConversations.getLast());
+            replyList.removeAll(oldConversations);
+            replyList.add(youngestOldConversation);
+        }
+
+        List<String> playerNames = replyList.stream()
+            .map(conversation -> Bukkit.getOfflinePlayer(conversation.target()).getName())
+            .distinct()
+            .toList();
+
+        playerNames.forEach(playerName -> component.append(
+            Component.text("[")
+                .append(Component.text(playerName, NamedTextColor.GOLD))
+                .append(Component.text("]"))
+                .clickEvent(ClickEvent.runCommand(String.format("/msg %s %s", playerName, message)))
+                .hoverEvent(HoverEvent.showText(Component.text("Klicke, um diesem Spieler zu schreiben.").color(NamedTextColor.GOLD))))
+            .append(Component.text("  "))
+        );
+        component.appendNewline();
+
+        sender.sendMessage(component.build());
+
+    }
+
+    public void sendWhisper(Player sender, ResolvedPmUserArguments userArguments) {
+        Conversation newReceiverConversation = new Conversation(
+            sender.getUniqueId(),
+            System.currentTimeMillis()
+        );
+
+        if(this.replyMapping.get(userArguments.receiver) != null) {
+            List<Conversation> oldEntries = this.replyMapping.get(userArguments.receiver).stream()
+                .filter(conversation -> conversation.target() == sender.getUniqueId())
+                .toList();
+            this.replyMapping.get(userArguments.receiver).removeAll(oldEntries);
+        } else {
+            this.replyMapping.put(
+                userArguments.receiver,
+                new ArrayList<>()
+            );
+        }
+
+        this.replyMapping.get(userArguments.receiver).add(newReceiverConversation);
+
+        List<Conversation> senderConversationList = new ArrayList<>();
+        senderConversationList.add(
+            new Conversation(
+                userArguments.receiver.getUniqueId(),
+                System.currentTimeMillis()
+            )
+        );
+
+        this.replyMapping.put(
+            sender,
+            senderConversationList
+        );
+
+        ChatMessages chatMessages = Main.instance().getAppliance(ChatMessages.class);
+        Component privatePrefix = Component.text("[Privat] ", NamedTextColor.LIGHT_PURPLE);
+
+        sender.sendMessage(
+            Component.text()
+                .append(privatePrefix.clickEvent(ClickEvent.suggestCommand(String.format("/msg %s ", userArguments.receiver.getName()))))
+                .append(sender.displayName())
+                .append(Component.text(" zu ", NamedTextColor.GRAY))
+                .append(chatMessages.getReportablePlayerName(userArguments.receiver))
+                .append(Component.text(" > ", NamedTextColor.GRAY))
+                .append(Component.text(userArguments.message))
+        );
+        userArguments.receiver.sendMessage(
+            Component.text()
+                .append(privatePrefix.clickEvent(ClickEvent.suggestCommand(String.format("/msg %s ", sender.getName()))))
+                .append(chatMessages.getReportablePlayerName(sender))
+                .append(Component.text(" zu ", NamedTextColor.GRAY))
+                .append(userArguments.receiver.displayName())
+                .append(Component.text(" > ", NamedTextColor.GRAY))
+                .append(Component.text(userArguments.message))
+        );
+    }
+
+    public record ResolvedPmUserArguments(Player receiver, String message) {}
+    public ResolvedPmUserArguments resolveImplicit(String[] args) {
+        if(args.length < 2) throw new ApplianceCommand.Error("Es muss ein Spieler sowie eine Nachricht angegeben werden.");
+        List<String> arguments = List.of(args);
+        Player targetPlayer = Bukkit.getPlayer(arguments.getFirst());
+        if(targetPlayer == null) throw new ApplianceCommand.Error(String.format("Der Spieler %s konnte nicht gefunden werden.", arguments.getFirst()));
+        String message = arguments.stream().skip(1).collect(Collectors.joining(" "));
+        return new ResolvedPmUserArguments(targetPlayer, message);
+    }
+
+    @Override
+    protected @NotNull List<ApplianceCommand<?>> commands() {
+        return List.of(
+            new PrivateMessageCommand(),
+            new PrivateReplyCommand()
+        );
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/commands/PrivateMessageCommand.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/commands/PrivateMessageCommand.java
new file mode 100644
index 0000000..711bbe3
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/commands/PrivateMessageCommand.java
@@ -0,0 +1,18 @@
+package eu.mhsl.craftattack.spawn.appliances.privateMessage.commands;
+
+import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
+import eu.mhsl.craftattack.spawn.appliances.privateMessage.PrivateMessage;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.jetbrains.annotations.NotNull;
+
+public class PrivateMessageCommand extends ApplianceCommand.PlayerChecked<PrivateMessage> {
+    public PrivateMessageCommand() {
+        super("msg");
+    }
+
+    @Override
+    protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
+        getAppliance().sendWhisper(getPlayer(), getAppliance().resolveImplicit(args));
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/commands/PrivateReplyCommand.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/commands/PrivateReplyCommand.java
new file mode 100644
index 0000000..fd7de78
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/privateMessage/commands/PrivateReplyCommand.java
@@ -0,0 +1,18 @@
+package eu.mhsl.craftattack.spawn.appliances.privateMessage.commands;
+
+import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
+import eu.mhsl.craftattack.spawn.appliances.privateMessage.PrivateMessage;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.jetbrains.annotations.NotNull;
+
+public class PrivateReplyCommand extends ApplianceCommand.PlayerChecked<PrivateMessage> {
+    public PrivateReplyCommand() {
+        super("r");
+    }
+
+    @Override
+    protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
+        getAppliance().reply(getPlayer(), String.join(" ", args));
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/whitelist/Whitelist.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/whitelist/Whitelist.java
index 16da0c5..dd707fb 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/whitelist/Whitelist.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/whitelist/Whitelist.java
@@ -66,12 +66,9 @@ public class Whitelist extends Appliance {
 
             queryAppliance(Outlawed.class).updateForcedStatus(player, timestampRelevant(user.outlawed_until));
 
-            String purePlayerName;
-            if(Floodgate.isBedrock(player)) {
-                purePlayerName = Floodgate.getBedrockPlayer(player).getUsername();
-            } else {
-                purePlayerName = player.getName();
-            }
+            String purePlayerName = Floodgate.isBedrock(player)
+                ? Floodgate.getBedrockPlayer(player).getUsername()
+                : player.getName();
 
             if(!user.username.trim().equalsIgnoreCase(purePlayerName))
                 throw new DisconnectInfo.Throwable(
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/util/statistics/NetworkMonitor.java b/src/main/java/eu/mhsl/craftattack/spawn/util/statistics/NetworkMonitor.java
index c9ea77c..88719e2 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/util/statistics/NetworkMonitor.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/util/statistics/NetworkMonitor.java
@@ -9,6 +9,7 @@ import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.time.Duration;
+import java.util.logging.Level;
 
 public class NetworkMonitor {
     private long previousRxBytes = 0;
@@ -78,8 +79,9 @@ public class NetworkMonitor {
             String content = new String(Files.readAllBytes(Paths.get(path)));
             return Long.parseLong(content.trim());
         } catch(IOException e) {
+            Main.logger().log(Level.SEVERE, "Statistics are only supported on Linux! Is tablist.interface config set correctly?");
             this.stop();
-            throw new RuntimeException("Failed receiving Network statistic, disabling statistics!", e);
+            throw new RuntimeException("Failed reading network statistic", e);
         }
     }
 }
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 11d8e20..a6240d1 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -49,6 +49,7 @@ playerLimit:
   maxPlayers: 10
 
 whitelist:
+  overrideIntegrityCheck: false
   api: https://mhsl.eu/craftattack/api/user
 
 tablist:
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 84efe67..0a3437b 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -39,4 +39,6 @@ commands:
   settings:
   texturepack:
   maintanance:
-  yearRank:
\ No newline at end of file
+  yearRank:
+  msg:
+  r:
\ No newline at end of file