diff --git a/examples/e01_basic_ping_bot/src/main.rs b/examples/e01_basic_ping_bot/src/main.rs
index 24a9b22ced4..86a41bcff5f 100644
--- a/examples/e01_basic_ping_bot/src/main.rs
+++ b/examples/e01_basic_ping_bot/src/main.rs
@@ -13,7 +13,7 @@ impl EventHandler for Handler {
     //
     // Event handlers are dispatched through a threadpool, and so multiple events can be dispatched
     // simultaneously.
-    async fn message(&self, ctx: &Context, msg: &Message) {
+    async fn message(&self, ctx: Context, msg: Message) {
         if msg.content == "!ping" {
             // Sending a message can fail, due to a network error, an authentication error, or lack
             // of permissions to post in the channel, so log to stdout when some error happens,
@@ -29,7 +29,7 @@ impl EventHandler for Handler {
     // Ids, current user data, private channels, and more.
     //
     // In this case, just print what the current user's username is.
-    async fn ready(&self, _: &Context, ready: &Ready) {
+    async fn ready(&self, _: Context, ready: Ready) {
         println!("{} is connected!", ready.user.name);
     }
 }
diff --git a/examples/e02_transparent_guild_sharding/src/main.rs b/examples/e02_transparent_guild_sharding/src/main.rs
index 85eca831573..288323e8b29 100644
--- a/examples/e02_transparent_guild_sharding/src/main.rs
+++ b/examples/e02_transparent_guild_sharding/src/main.rs
@@ -24,7 +24,7 @@ struct Handler;
 
 #[async_trait]
 impl EventHandler for Handler {
-    async fn message(&self, ctx: &Context, msg: &Message) {
+    async fn message(&self, ctx: Context, msg: Message) {
         if msg.content == "!ping" {
             println!("Shard {}", ctx.shard_id);
 
@@ -34,7 +34,7 @@ impl EventHandler for Handler {
         }
     }
 
-    async fn ready(&self, _: &Context, ready: &Ready) {
+    async fn ready(&self, _: Context, ready: Ready) {
         println!("{} is connected!", ready.user.name);
     }
 }
diff --git a/examples/e03_struct_utilities/src/main.rs b/examples/e03_struct_utilities/src/main.rs
index 7b1e62ad254..e304d3dcd2e 100644
--- a/examples/e03_struct_utilities/src/main.rs
+++ b/examples/e03_struct_utilities/src/main.rs
@@ -10,7 +10,7 @@ struct Handler;
 
 #[async_trait]
 impl EventHandler for Handler {
-    async fn message(&self, context: &Context, msg: &Message) {
+    async fn message(&self, context: Context, msg: Message) {
         if msg.content == "!messageme" {
             // If the `utils`-feature is enabled, then model structs will have a lot of useful
             // methods implemented, to avoid using an often otherwise bulky Context, or even much
@@ -27,7 +27,7 @@ impl EventHandler for Handler {
         }
     }
 
-    async fn ready(&self, _: &Context, ready: &Ready) {
+    async fn ready(&self, _: Context, ready: Ready) {
         println!("{} is connected!", ready.user.name);
     }
 }
diff --git a/examples/e04_message_builder/src/main.rs b/examples/e04_message_builder/src/main.rs
index 67e61e53826..1c3fd296d21 100644
--- a/examples/e04_message_builder/src/main.rs
+++ b/examples/e04_message_builder/src/main.rs
@@ -10,7 +10,7 @@ struct Handler;
 
 #[async_trait]
 impl EventHandler for Handler {
-    async fn message(&self, context: &Context, msg: &Message) {
+    async fn message(&self, context: Context, msg: Message) {
         if msg.content == "!ping" {
             let channel = match msg.channel_id.to_channel(&context).await {
                 Ok(channel) => channel,
@@ -38,7 +38,7 @@ impl EventHandler for Handler {
         }
     }
 
-    async fn ready(&self, _: &Context, ready: &Ready) {
+    async fn ready(&self, _: Context, ready: Ready) {
         println!("{} is connected!", ready.user.name);
     }
 }
diff --git a/examples/e05_sample_bot_structure/src/main.rs b/examples/e05_sample_bot_structure/src/main.rs
index dc9bdc2c52e..76026564090 100644
--- a/examples/e05_sample_bot_structure/src/main.rs
+++ b/examples/e05_sample_bot_structure/src/main.rs
@@ -13,7 +13,7 @@ struct Handler;
 
 #[async_trait]
 impl EventHandler for Handler {
-    async fn interaction_create(&self, ctx: &Context, interaction: &Interaction) {
+    async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
         if let Interaction::Command(command) = interaction {
             println!("Received command interaction: {command:#?}");
 
@@ -22,7 +22,7 @@ impl EventHandler for Handler {
                 "id" => Some(commands::id::run(&command.data.options())),
                 "attachmentinput" => Some(commands::attachmentinput::run(&command.data.options())),
                 "modal" => {
-                    commands::modal::run(ctx, command).await.unwrap();
+                    commands::modal::run(&ctx, &command).await.unwrap();
                     None
                 },
                 _ => Some("not implemented :(".to_string()),
@@ -38,7 +38,7 @@ impl EventHandler for Handler {
         }
     }
 
-    async fn ready(&self, ctx: &Context, ready: &Ready) {
+    async fn ready(&self, ctx: Context, ready: Ready) {
         println!("{} is connected!", ready.user.name);
 
         let guild_id = GuildId::new(
diff --git a/examples/e06_env_logging/src/main.rs b/examples/e06_env_logging/src/main.rs
index 944b1f41dfb..0e1a674a29b 100644
--- a/examples/e06_env_logging/src/main.rs
+++ b/examples/e06_env_logging/src/main.rs
@@ -10,7 +10,7 @@ struct Handler;
 
 #[async_trait]
 impl EventHandler for Handler {
-    async fn ready(&self, _: &Context, ready: &Ready) {
+    async fn ready(&self, _: Context, ready: Ready) {
         // Log at the INFO level. This is a macro from the `tracing` crate.
         info!("{} is connected!", ready.user.name);
     }
@@ -20,7 +20,7 @@ impl EventHandler for Handler {
     // Handler doesn't implement Debug here, so we specify to skip that argument.
     // Context doesn't implement Debug either, so it is also skipped.
     #[instrument(skip(self, _ctx))]
-    async fn resume(&self, _ctx: &Context, _resume: &ResumedEvent) {
+    async fn resume(&self, _ctx: Context, _resume: ResumedEvent) {
         // Log at the DEBUG level.
         //
         // In this example, this will not show up in the logs because DEBUG is
diff --git a/examples/e07_shard_manager/src/main.rs b/examples/e07_shard_manager/src/main.rs
index 2c004bccc83..53ddcedfd35 100644
--- a/examples/e07_shard_manager/src/main.rs
+++ b/examples/e07_shard_manager/src/main.rs
@@ -31,7 +31,7 @@ struct Handler;
 
 #[async_trait]
 impl EventHandler for Handler {
-    async fn ready(&self, _: &Context, ready: &Ready) {
+    async fn ready(&self, _: Context, ready: Ready) {
         if let Some(shard) = ready.shard {
             // Note that array index 0 is 0-indexed, while index 1 is 1-indexed.
             //
diff --git a/examples/e08_create_message_builder/src/main.rs b/examples/e08_create_message_builder/src/main.rs
index 7a78d3d0ae6..712713e4e50 100644
--- a/examples/e08_create_message_builder/src/main.rs
+++ b/examples/e08_create_message_builder/src/main.rs
@@ -11,7 +11,7 @@ struct Handler;
 
 #[async_trait]
 impl EventHandler for Handler {
-    async fn message(&self, ctx: &Context, msg: &Message) {
+    async fn message(&self, ctx: Context, msg: Message) {
         if msg.content == "!hello" {
             // The create message builder allows you to easily create embeds and messages using a
             // builder syntax.
@@ -43,7 +43,7 @@ impl EventHandler for Handler {
         }
     }
 
-    async fn ready(&self, _: &Context, ready: &Ready) {
+    async fn ready(&self, _: Context, ready: Ready) {
         println!("{} is connected!", ready.user.name);
     }
 }
diff --git a/examples/e09_collectors/src/main.rs b/examples/e09_collectors/src/main.rs
index 1bb5793a51f..a7ecaa0819d 100644
--- a/examples/e09_collectors/src/main.rs
+++ b/examples/e09_collectors/src/main.rs
@@ -15,11 +15,11 @@ struct Handler;
 
 #[async_trait]
 impl EventHandler for Handler {
-    async fn ready(&self, _: &Context, ready: &Ready) {
+    async fn ready(&self, _: Context, ready: Ready) {
         println!("{} is connected!", ready.user.name);
     }
 
-    async fn message(&self, ctx: &Context, msg: &Message) {
+    async fn message(&self, ctx: Context, msg: Message) {
         let mut score = 0u32;
         let _ = msg.reply(&ctx, "How was that crusty crab called again? 10 seconds time!").await;
 
diff --git a/examples/e10_gateway_intents/src/main.rs b/examples/e10_gateway_intents/src/main.rs
index f847435100e..50b22a5e030 100644
--- a/examples/e10_gateway_intents/src/main.rs
+++ b/examples/e10_gateway_intents/src/main.rs
@@ -10,17 +10,17 @@ struct Handler;
 #[async_trait]
 impl EventHandler for Handler {
     // This event will be dispatched for guilds, but not for direct messages.
-    async fn message(&self, _ctx: &Context, msg: &Message) {
+    async fn message(&self, _ctx: Context, msg: Message) {
         println!("Received message: {}", msg.content);
     }
 
     // As the intents set in this example, this event shall never be dispatched.
     // Try it by changing your status.
-    async fn presence_update(&self, _ctx: &Context, _new_data: &Presence) {
+    async fn presence_update(&self, _ctx: Context, _new_data: Presence) {
         println!("Presence Update");
     }
 
-    async fn ready(&self, _: &Context, ready: &Ready) {
+    async fn ready(&self, _: Context, ready: Ready) {
         println!("{} is connected!", ready.user.name);
     }
 }
diff --git a/examples/e11_global_data/src/main.rs b/examples/e11_global_data/src/main.rs
index 41f37a13fb4..af5dd4b84fc 100644
--- a/examples/e11_global_data/src/main.rs
+++ b/examples/e11_global_data/src/main.rs
@@ -21,7 +21,7 @@ struct Handler;
 
 #[async_trait]
 impl EventHandler for Handler {
-    async fn message(&self, ctx: &Context, msg: &Message) {
+    async fn message(&self, ctx: Context, msg: Message) {
         // Since data is located in Context, this means you are able to use it within events!
         let data = ctx.data::<UserData>();
 
@@ -54,7 +54,7 @@ impl EventHandler for Handler {
         }
     }
 
-    async fn ready(&self, _: &Context, ready: &Ready) {
+    async fn ready(&self, _: Context, ready: Ready) {
         println!("{} is connected!", ready.user.name);
     }
 }
diff --git a/examples/e12_parallel_loops/src/main.rs b/examples/e12_parallel_loops/src/main.rs
index 2c00e5ec5ee..1fdf82a8004 100644
--- a/examples/e12_parallel_loops/src/main.rs
+++ b/examples/e12_parallel_loops/src/main.rs
@@ -17,7 +17,7 @@ struct Handler {
 
 #[async_trait]
 impl EventHandler for Handler {
-    async fn message(&self, ctx: &Context, msg: &Message) {
+    async fn message(&self, ctx: Context, msg: Message) {
         if msg.content.starts_with("!ping") {
             if let Err(why) = msg.channel_id.say(&ctx.http, "Pong!").await {
                 eprintln!("Error sending message: {why:?}");
@@ -25,13 +25,13 @@ impl EventHandler for Handler {
         }
     }
 
-    async fn ready(&self, _ctx: &Context, ready: &Ready) {
+    async fn ready(&self, _ctx: Context, ready: Ready) {
         println!("{} is connected!", ready.user.name);
     }
 
     // We use the cache_ready event just in case some cache operation is required in whatever use
     // case you have for this.
-    async fn cache_ready(&self, ctx: &Context, _guilds: &Vec<GuildId>) {
+    async fn cache_ready(&self, ctx: Context, _guilds: Vec<GuildId>) {
         println!("Cache built successfully!");
 
         // We need to check that the loop is not already running when this event triggers, as this
@@ -53,10 +53,9 @@ impl EventHandler for Handler {
             });
 
             // And of course, we can run more than one thread at different timings.
-            let ctx2 = ctx.clone();
             tokio::spawn(async move {
                 loop {
-                    set_activity_to_current_time(&ctx2);
+                    set_activity_to_current_time(&ctx);
                     tokio::time::sleep(Duration::from_secs(60)).await;
                 }
             });
diff --git a/examples/e13_sqlite_database/src/main.rs b/examples/e13_sqlite_database/src/main.rs
index 085d3196478..11e4b500f87 100644
--- a/examples/e13_sqlite_database/src/main.rs
+++ b/examples/e13_sqlite_database/src/main.rs
@@ -12,7 +12,7 @@ struct Bot {
 
 #[async_trait]
 impl EventHandler for Bot {
-    async fn message(&self, ctx: &Context, msg: &Message) {
+    async fn message(&self, ctx: Context, msg: Message) {
         let user_id = msg.author.id.get() as i64;
 
         if let Some(task_description) = msg.content.strip_prefix("~todo add") {
diff --git a/examples/e14_message_components/src/main.rs b/examples/e14_message_components/src/main.rs
index 8e4c065145d..54bc6401d82 100644
--- a/examples/e14_message_components/src/main.rs
+++ b/examples/e14_message_components/src/main.rs
@@ -28,7 +28,7 @@ struct Handler;
 
 #[async_trait]
 impl EventHandler for Handler {
-    async fn message(&self, ctx: &Context, msg: &Message) {
+    async fn message(&self, ctx: Context, msg: Message) {
         if msg.content != "animal" {
             return;
         }
diff --git a/examples/testing/src/main.rs b/examples/testing/src/main.rs
index 5069babfcbd..12a7d79d5d9 100644
--- a/examples/testing/src/main.rs
+++ b/examples/testing/src/main.rs
@@ -9,7 +9,7 @@ mod model_type_sizes;
 const IMAGE_URL: &str = "https://raw.githubusercontent.com/serenity-rs/serenity/current/logo.png";
 const IMAGE_URL_2: &str = "https://rustacean.net/assets/rustlogo.png";
 
-async fn message(ctx: &Context, msg: &Message) -> Result<(), serenity::Error> {
+async fn message(ctx: &Context, msg: Message) -> Result<(), serenity::Error> {
     let channel_id = msg.channel_id;
     let guild_id = msg.guild_id.unwrap();
     if let Some(_args) = msg.content.strip_prefix("testmessage ") {
@@ -235,7 +235,7 @@ async fn message(ctx: &Context, msg: &Message) -> Result<(), serenity::Error> {
 
 async fn interaction(
     ctx: &Context,
-    interaction: &CommandInteraction,
+    interaction: CommandInteraction,
 ) -> Result<(), serenity::Error> {
     if interaction.data.name == "editattachments" {
         // Respond with an image
@@ -376,13 +376,13 @@ async fn interaction(
 struct Handler;
 #[serenity::async_trait]
 impl EventHandler for Handler {
-    async fn message(&self, ctx: &Context, msg: &Message) {
-        message(ctx, msg).await.unwrap();
+    async fn message(&self, ctx: Context, msg: Message) {
+        message(&ctx, msg).await.unwrap();
     }
 
-    async fn interaction_create(&self, ctx: &Context, i: &Interaction) {
+    async fn interaction_create(&self, ctx: Context, i: Interaction) {
         match i {
-            Interaction::Command(i) => interaction(ctx, i).await.unwrap(),
+            Interaction::Command(i) => interaction(&ctx, i).await.unwrap(),
             Interaction::Component(i) => println!("{:#?}", i.data),
             Interaction::Autocomplete(i) => {
                 i.create_response(
@@ -399,7 +399,7 @@ impl EventHandler for Handler {
         }
     }
 
-    async fn reaction_remove_emoji(&self, _ctx: &Context, removed_reactions: &Reaction) {
+    async fn reaction_remove_emoji(&self, _ctx: Context, removed_reactions: Reaction) {
         println!("Got ReactionRemoveEmoji event: {removed_reactions:?}");
     }
 }
diff --git a/src/cache/mod.rs b/src/cache/mod.rs
index f732357efc8..4a53ea43ece 100644
--- a/src/cache/mod.rs
+++ b/src/cache/mod.rs
@@ -266,7 +266,7 @@ impl Cache {
     /// #[serenity::async_trait]
     /// # #[cfg(feature = "client")]
     /// impl EventHandler for Handler {
-    ///     async fn cache_ready(&self, ctx: &Context, _: &Vec<GuildId>) {
+    ///     async fn cache_ready(&self, ctx: Context, _: Vec<GuildId>) {
     ///         println!("{} unknown members", ctx.cache.unknown_members());
     ///     }
     /// }
@@ -307,7 +307,7 @@ impl Cache {
     ///
     /// #[serenity::async_trait]
     /// impl EventHandler for Handler {
-    ///     async fn ready(&self, context: &Context, _: &Ready) {
+    ///     async fn ready(&self, context: Context, _: Ready) {
     ///         let guilds = context.cache.guilds().len();
     ///
     ///         println!("Guilds in the Cache: {}", guilds);
diff --git a/src/client/context.rs b/src/client/context.rs
index d911100da32..1bf2f2a2c03 100644
--- a/src/client/context.rs
+++ b/src/client/context.rs
@@ -114,7 +114,7 @@ impl Context {
     ///
     /// #[serenity::async_trait]
     /// impl EventHandler for Handler {
-    ///     async fn message(&self, ctx: &Context, msg: &Message) {
+    ///     async fn message(&self, ctx: Context, msg: Message) {
     ///         if msg.content == "!online" {
     ///             ctx.online();
     ///         }
@@ -142,7 +142,7 @@ impl Context {
     ///
     /// #[serenity::async_trait]
     /// impl EventHandler for Handler {
-    ///     async fn message(&self, ctx: &Context, msg: &Message) {
+    ///     async fn message(&self, ctx: Context, msg: Message) {
     ///         if msg.content == "!idle" {
     ///             ctx.idle();
     ///         }
@@ -170,7 +170,7 @@ impl Context {
     ///
     /// #[serenity::async_trait]
     /// impl EventHandler for Handler {
-    ///     async fn message(&self, ctx: &Context, msg: &Message) {
+    ///     async fn message(&self, ctx: Context, msg: Message) {
     ///         if msg.content == "!dnd" {
     ///             ctx.dnd();
     ///         }
@@ -198,7 +198,7 @@ impl Context {
     ///
     /// #[serenity::async_trait]
     /// impl EventHandler for Handler {
-    ///     async fn message(&self, ctx: &Context, msg: &Message) {
+    ///     async fn message(&self, ctx: Context, msg: Message) {
     ///         if msg.content == "!invisible" {
     ///             ctx.invisible();
     ///         }
@@ -229,7 +229,7 @@ impl Context {
     ///
     /// #[serenity::async_trait]
     /// impl EventHandler for Handler {
-    ///     async fn message(&self, ctx: &Context, msg: &Message) {
+    ///     async fn message(&self, ctx: Context, msg: Message) {
     ///         if msg.content == "!reset_presence" {
     ///             ctx.reset_presence();
     ///         }
@@ -259,7 +259,7 @@ impl Context {
     ///
     /// #[serenity::async_trait]
     /// impl EventHandler for Handler {
-    ///     async fn message(&self, ctx: &Context, msg: &Message) {
+    ///     async fn message(&self, ctx: Context, msg: Message) {
     ///         let mut args = msg.content.splitn(2, ' ');
     ///
     ///         if let (Some("~setgame"), Some(game)) = (args.next(), args.next()) {
@@ -286,7 +286,7 @@ impl Context {
     ///
     /// #[serenity::async_trait]
     /// impl EventHandler for Handler {
-    ///     async fn ready(&self, ctx: &Context, _: &Ready) {
+    ///     async fn ready(&self, ctx: Context, _: Ready) {
     ///         use serenity::model::user::OnlineStatus;
     ///
     ///         ctx.set_presence(None, OnlineStatus::Idle);
@@ -303,7 +303,7 @@ impl Context {
     ///
     /// #[serenity::async_trait]
     /// impl EventHandler for Handler {
-    ///     async fn ready(&self, context: &Context, _: &Ready) {
+    ///     async fn ready(&self, context: Context, _: Ready) {
     ///         use serenity::gateway::ActivityData;
     ///         use serenity::model::user::OnlineStatus;
     ///
diff --git a/src/client/dispatch.rs b/src/client/dispatch.rs
index 6bf6b998fa6..ed973ee8ca1 100644
--- a/src/client/dispatch.rs
+++ b/src/client/dispatch.rs
@@ -1,14 +1,13 @@
 use std::sync::Arc;
 
 #[cfg(feature = "gateway")]
-use super::event_handler::{EventHandler, RawEventHandler};
+use super::event_handler::InternalEventHandler;
 use super::{Context, FullEvent};
 #[cfg(feature = "cache")]
 use crate::cache::{Cache, CacheUpdate};
 #[cfg(feature = "framework")]
 use crate::framework::Framework;
 use crate::internal::prelude::*;
-use crate::internal::tokio::spawn_named;
 use crate::model::channel::ChannelType;
 use crate::model::event::Event;
 use crate::model::guild::Member;
@@ -41,24 +40,23 @@ macro_rules! update_cache {
     ($cache:ident, $event:ident) => {};
 }
 
-pub(crate) fn dispatch_model(
+/// Calls the user's event handlers and the framework handler.
+///
+/// This MUST be called from a different task to the recv_event loop, to allow for
+/// intra-shard concurrency between the shard loop and event handler.
+pub(crate) async fn dispatch_model(
     event: Event,
     context: Context,
     #[cfg(feature = "framework")] framework: Option<Arc<dyn Framework>>,
-    event_handlers: &[Arc<dyn EventHandler>],
-    raw_event_handlers: &[Arc<dyn RawEventHandler>],
+    event_handler: Option<InternalEventHandler>,
 ) {
-    struct DispatchContext {
-        context: Context,
-        full_event: FullEvent,
-        task_name: &'static str,
-        extra_event: Option<FullEvent>,
-    }
-
-    for raw_handler in raw_event_handlers {
-        let (handler, context, event) = (Arc::clone(raw_handler), context.clone(), event.clone());
-        tokio::spawn(async move { handler.raw_event(context, event).await });
-    }
+    let handler = match event_handler {
+        Some(InternalEventHandler::Normal(handler)) => Some(handler),
+        Some(InternalEventHandler::Raw(raw_handler)) => {
+            return raw_handler.raw_event(context, event).await;
+        },
+        None => None,
+    };
 
     let (full_event, extra_event) = update_cache_with_event(
         #[cfg(feature = "cache")]
@@ -66,49 +64,21 @@ pub(crate) fn dispatch_model(
         event,
     );
 
-    let dispatch_ctx = Arc::new(DispatchContext {
-        task_name: full_event.snake_case_name(),
-        context,
-        full_event,
-        extra_event,
-    });
-
-    for handler in event_handlers {
-        let handler = Arc::clone(handler);
-        let dispatch_ctx = Arc::clone(&dispatch_ctx);
-
-        spawn_named(dispatch_ctx.task_name, async move {
-            let DispatchContext {
-                context,
-                full_event,
-                extra_event,
-                ..
-            } = &*dispatch_ctx;
-
-            if let Some(extra_event) = extra_event {
-                extra_event.dispatch(context, &*handler).await;
-            }
+    #[cfg(feature = "framework")]
+    if let Some(framework) = framework {
+        if let Some(extra_event) = &extra_event {
+            framework.dispatch(&context, extra_event).await;
+        }
 
-            full_event.dispatch(context, &*handler).await;
-        });
+        framework.dispatch(&context, &full_event).await;
     }
 
-    #[cfg(feature = "framework")]
-    if let Some(framework) = framework {
-        spawn_named("dispatch::framework::dispatch", async move {
-            let DispatchContext {
-                context,
-                full_event,
-                extra_event,
-                ..
-            } = &*dispatch_ctx;
-
-            if let Some(extra_event) = extra_event {
-                framework.dispatch(context, extra_event).await;
-            }
+    if let Some(handler) = handler {
+        if let Some(extra_event) = extra_event {
+            extra_event.dispatch(context.clone(), &*handler).await;
+        }
 
-            framework.dispatch(context, full_event).await;
-        });
+        full_event.dispatch(context, &*handler).await;
     }
 }
 
diff --git a/src/client/event_handler.rs b/src/client/event_handler.rs
index 43c8f850e3d..a980aaf9554 100644
--- a/src/client/event_handler.rs
+++ b/src/client/event_handler.rs
@@ -1,6 +1,7 @@
 use std::collections::VecDeque;
 #[cfg(feature = "cache")]
 use std::num::NonZeroU16;
+use std::sync::Arc;
 
 use async_trait::async_trait;
 use strum::{EnumCount, IntoStaticStr, VariantNames};
@@ -22,7 +23,7 @@ macro_rules! event_handler {
             $(
                 $( #[doc = $doc] )*
                 $( #[cfg(feature = $feature)] )?
-                async fn $method_name(&self, $($context: &Context,)? $( $arg_name: &$arg_type ),*) {
+                async fn $method_name(&self, $($context: Context,)? $( $arg_name: $arg_type ),*) {
                     // Suppress unused argument warnings
                     #[allow(dropping_references, dropping_copy_types)]
                     drop(( $($context,)? $($arg_name),* ))
@@ -66,7 +67,7 @@ macro_rules! event_handler {
             }
 
             /// Runs the given [`EventHandler`]'s code for this event.
-            pub async fn dispatch(&self, ctx: &Context, handler: &dyn EventHandler) {
+            pub async fn dispatch(self, ctx: Context, handler: &dyn EventHandler) {
                 match self {
                     $(
                         $( #[cfg(feature = $feature)] )?
@@ -484,3 +485,9 @@ pub trait RawEventHandler: Send + Sync {
     /// Dispatched when any event occurs
     async fn raw_event(&self, _ctx: Context, _ev: Event) {}
 }
+
+#[derive(Clone)]
+pub enum InternalEventHandler {
+    Raw(Arc<dyn RawEventHandler>),
+    Normal(Arc<dyn EventHandler>),
+}
diff --git a/src/client/mod.rs b/src/client/mod.rs
index 865cbfd7e12..edb808544e8 100644
--- a/src/client/mod.rs
+++ b/src/client/mod.rs
@@ -36,7 +36,7 @@ use tracing::debug;
 pub use self::context::Context;
 pub use self::error::Error as ClientError;
 #[cfg(feature = "gateway")]
-pub use self::event_handler::{EventHandler, FullEvent, RawEventHandler};
+pub use self::event_handler::{EventHandler, FullEvent, InternalEventHandler, RawEventHandler};
 #[cfg(feature = "gateway")]
 use super::gateway::GatewayError;
 #[cfg(feature = "cache")]
@@ -52,6 +52,7 @@ use crate::gateway::{ActivityData, PresenceData};
 use crate::gateway::{ShardManager, ShardManagerOptions};
 use crate::http::Http;
 use crate::internal::prelude::*;
+use crate::internal::tokio::spawn_named;
 #[cfg(feature = "gateway")]
 use crate::model::gateway::GatewayIntents;
 use crate::model::id::ApplicationId;
@@ -73,8 +74,8 @@ pub struct ClientBuilder {
     framework: Option<Box<dyn Framework>>,
     #[cfg(feature = "voice")]
     voice_manager: Option<Arc<dyn VoiceGatewayManager>>,
-    event_handlers: Vec<Arc<dyn EventHandler>>,
-    raw_event_handlers: Vec<Arc<dyn RawEventHandler>>,
+    event_handler: Option<Arc<dyn EventHandler>>,
+    raw_event_handler: Option<Arc<dyn RawEventHandler>>,
     presence: PresenceData,
 }
 
@@ -107,8 +108,8 @@ impl ClientBuilder {
             framework: None,
             #[cfg(feature = "voice")]
             voice_manager: None,
-            event_handlers: vec![],
-            raw_event_handlers: vec![],
+            event_handler: None,
+            raw_event_handler: None,
             presence: PresenceData::default(),
         }
     }
@@ -232,29 +233,30 @@ impl ClientBuilder {
     where
         H: EventHandler + 'static,
     {
-        self.event_handlers.push(event_handler.into());
-
+        self.event_handler = Some(event_handler.into());
         self
     }
 
     /// Gets the added event handlers. See [`Self::event_handler`] for more info.
     #[must_use]
-    pub fn get_event_handlers(&self) -> &[Arc<dyn EventHandler>] {
-        &self.event_handlers
+    pub fn get_event_handler(&self) -> Option<&Arc<dyn EventHandler>> {
+        self.event_handler.as_ref()
     }
 
     /// Adds an event handler with a single method where all received gateway events will be
     /// dispatched.
-    pub fn raw_event_handler<H: RawEventHandler + 'static>(mut self, raw_event_handler: H) -> Self {
-        self.raw_event_handlers.push(Arc::new(raw_event_handler));
-
+    pub fn raw_event_handler<H>(mut self, raw_event_handler: impl Into<Arc<H>>) -> Self
+    where
+        H: RawEventHandler + 'static,
+    {
+        self.raw_event_handler = Some(raw_event_handler.into());
         self
     }
 
     /// Gets the added raw event handlers. See [`Self::raw_event_handler`] for more info.
     #[must_use]
-    pub fn get_raw_event_handlers(&self) -> &[Arc<dyn RawEventHandler>] {
-        &self.raw_event_handlers
+    pub fn get_raw_event_handler(&self) -> Option<&Arc<dyn RawEventHandler>> {
+        self.raw_event_handler.as_ref()
     }
 
     /// Sets the initial activity.
@@ -289,21 +291,27 @@ impl IntoFuture for ClientBuilder {
         let data = self.data.unwrap_or(Arc::new(()));
         #[cfg(feature = "framework")]
         let framework = self.framework;
-        let event_handlers = self.event_handlers;
-        let raw_event_handlers = self.raw_event_handlers;
         let intents = self.intents;
         let presence = self.presence;
         let http = self.http;
 
+        let event_handler = match (self.event_handler, self.raw_event_handler) {
+            (Some(_), Some(_)) => panic!("Cannot provide both a normal and raw event handlers"),
+            (Some(h), None) => Some(InternalEventHandler::Normal(h)),
+            (None, Some(h)) => Some(InternalEventHandler::Raw(h)),
+            (None, None) => None,
+        };
+
         if let Some(ratelimiter) = &http.ratelimiter {
-            let event_handlers_clone = event_handlers.clone();
-            ratelimiter.set_ratelimit_callback(Box::new(move |info| {
-                for event_handler in &event_handlers_clone {
-                    let info = info.clone();
-                    let event_handler = Arc::clone(event_handler);
-                    tokio::spawn(async move { event_handler.ratelimit(&info).await });
-                }
-            }));
+            if let Some(InternalEventHandler::Normal(event_handler)) = &event_handler {
+                let event_handler = Arc::clone(event_handler);
+                ratelimiter.set_ratelimit_callback(Box::new(move |info| {
+                    let event_handler = Arc::clone(&event_handler);
+                    spawn_named("ratelimit::dispatch", async move {
+                        event_handler.ratelimit(info).await;
+                    });
+                }));
+            }
         }
 
         #[cfg(feature = "voice")]
@@ -329,8 +337,7 @@ impl IntoFuture for ClientBuilder {
             let framework_cell = Arc::new(OnceLock::new());
             let (shard_manager, shard_manager_ret_value) = ShardManager::new(ShardManagerOptions {
                 data: Arc::clone(&data),
-                event_handlers,
-                raw_event_handlers,
+                event_handler,
                 #[cfg(feature = "framework")]
                 framework: Arc::clone(&framework_cell),
                 #[cfg(feature = "voice")]
@@ -394,7 +401,7 @@ impl IntoFuture for ClientBuilder {
 ///
 /// #[serenity::async_trait]
 /// impl EventHandler for Handler {
-///     async fn message(&self, context: &Context, msg: &Message) {
+///     async fn message(&self, context: Context, msg: Message) {
 ///         if msg.content == "!ping" {
 ///             let _ = msg.channel_id.say(&context, "Pong!");
 ///         }
diff --git a/src/gateway/bridge/shard_manager.rs b/src/gateway/bridge/shard_manager.rs
index 1d045b617a5..75c0803498e 100644
--- a/src/gateway/bridge/shard_manager.rs
+++ b/src/gateway/bridge/shard_manager.rs
@@ -16,7 +16,7 @@ use super::VoiceGatewayManager;
 use super::{ShardId, ShardQueue, ShardQueuer, ShardQueuerMessage, ShardRunnerInfo};
 #[cfg(feature = "cache")]
 use crate::cache::Cache;
-use crate::client::{EventHandler, RawEventHandler};
+use crate::client::InternalEventHandler;
 #[cfg(feature = "framework")]
 use crate::framework::Framework;
 use crate::gateway::{ConnectionStage, GatewayError, PresenceData};
@@ -49,7 +49,7 @@ use crate::model::gateway::GatewayIntents;
 /// use std::env;
 /// use std::sync::{Arc, OnceLock};
 ///
-/// use serenity::client::{EventHandler, RawEventHandler};
+/// use serenity::client::{EventHandler, InternalEventHandler, RawEventHandler};
 /// use serenity::gateway::{ShardManager, ShardManagerOptions};
 /// use serenity::http::Http;
 /// use serenity::model::gateway::GatewayIntents;
@@ -59,7 +59,6 @@ use crate::model::gateway::GatewayIntents;
 /// struct Handler;
 ///
 /// impl EventHandler for Handler {}
-/// impl RawEventHandler for Handler {}
 ///
 /// # let http: Arc<Http> = unimplemented!();
 /// let gateway_info = http.get_bot_gateway().await?;
@@ -72,8 +71,7 @@ use crate::model::gateway::GatewayIntents;
 ///
 /// ShardManager::new(ShardManagerOptions {
 ///     data,
-///     event_handlers: vec![event_handler],
-///     raw_event_handlers: vec![],
+///     event_handler: Some(InternalEventHandler::Normal(event_handler)),
 ///     framework: Arc::new(OnceLock::new()),
 ///     # #[cfg(feature = "voice")]
 ///     # voice_manager: None,
@@ -129,8 +127,7 @@ impl ShardManager {
 
         let mut shard_queuer = ShardQueuer {
             data: opt.data,
-            event_handlers: opt.event_handlers,
-            raw_event_handlers: opt.raw_event_handlers,
+            event_handler: opt.event_handler,
             #[cfg(feature = "framework")]
             framework: opt.framework,
             last_start: None,
@@ -359,8 +356,7 @@ impl Drop for ShardManager {
 
 pub struct ShardManagerOptions {
     pub data: Arc<dyn std::any::Any + Send + Sync>,
-    pub event_handlers: Vec<Arc<dyn EventHandler>>,
-    pub raw_event_handlers: Vec<Arc<dyn RawEventHandler>>,
+    pub event_handler: Option<InternalEventHandler>,
     #[cfg(feature = "framework")]
     pub framework: Arc<OnceLock<Arc<dyn Framework>>>,
     #[cfg(feature = "voice")]
diff --git a/src/gateway/bridge/shard_queuer.rs b/src/gateway/bridge/shard_queuer.rs
index 278bbb273c8..428ebb952a3 100644
--- a/src/gateway/bridge/shard_queuer.rs
+++ b/src/gateway/bridge/shard_queuer.rs
@@ -23,7 +23,7 @@ use super::{
 };
 #[cfg(feature = "cache")]
 use crate::cache::Cache;
-use crate::client::{EventHandler, RawEventHandler};
+use crate::client::InternalEventHandler;
 #[cfg(feature = "framework")]
 use crate::framework::Framework;
 use crate::gateway::{ConnectionStage, PresenceData, Shard, ShardRunnerMessage};
@@ -43,14 +43,11 @@ pub struct ShardQueuer {
     ///
     /// [`Client::data`]: crate::Client::data
     pub data: Arc<dyn std::any::Any + Send + Sync>,
-    /// A reference to an [`EventHandler`], such as the one given to the [`Client`].
+    /// A reference to [`EventHandler`] or [`RawEventHandler`].
     ///
-    /// [`Client`]: crate::Client
-    pub event_handlers: Vec<Arc<dyn EventHandler>>,
-    /// A reference to an [`RawEventHandler`], such as the one given to the [`Client`].
-    ///
-    /// [`Client`]: crate::Client
-    pub raw_event_handlers: Vec<Arc<dyn RawEventHandler>>,
+    /// [`EventHandler`]: crate::client::EventHandler
+    /// [`RawEventHandler`]: crate::client::RawEventHandler
+    pub event_handler: Option<InternalEventHandler>,
     /// A copy of the framework
     #[cfg(feature = "framework")]
     pub framework: Arc<OnceLock<Arc<dyn Framework>>>,
@@ -226,8 +223,7 @@ impl ShardQueuer {
 
         let mut runner = ShardRunner::new(ShardRunnerOptions {
             data: Arc::clone(&self.data),
-            event_handlers: self.event_handlers.clone(),
-            raw_event_handlers: self.raw_event_handlers.clone(),
+            event_handler: self.event_handler.clone(),
             #[cfg(feature = "framework")]
             framework: self.framework.get().cloned(),
             manager: Arc::clone(&self.manager),
diff --git a/src/gateway/bridge/shard_runner.rs b/src/gateway/bridge/shard_runner.rs
index e40cbcc48b8..2e64075eff4 100644
--- a/src/gateway/bridge/shard_runner.rs
+++ b/src/gateway/bridge/shard_runner.rs
@@ -16,7 +16,7 @@ use super::{ShardId, ShardManager, ShardRunnerMessage};
 #[cfg(feature = "cache")]
 use crate::cache::Cache;
 use crate::client::dispatch::dispatch_model;
-use crate::client::{Context, EventHandler, RawEventHandler};
+use crate::client::{Context, InternalEventHandler};
 #[cfg(feature = "framework")]
 use crate::framework::Framework;
 use crate::gateway::{GatewayError, ReconnectType, Shard, ShardAction};
@@ -28,8 +28,7 @@ use crate::model::event::{Event, GatewayEvent};
 /// A runner for managing a [`Shard`] and its respective WebSocket client.
 pub struct ShardRunner {
     data: Arc<dyn std::any::Any + Send + Sync>,
-    event_handlers: Vec<Arc<dyn EventHandler>>,
-    raw_event_handlers: Vec<Arc<dyn RawEventHandler>>,
+    event_handler: Option<InternalEventHandler>,
     #[cfg(feature = "framework")]
     framework: Option<Arc<dyn Framework>>,
     manager: Arc<ShardManager>,
@@ -56,8 +55,7 @@ impl ShardRunner {
             runner_rx: rx,
             runner_tx: tx,
             data: opt.data,
-            event_handlers: opt.event_handlers,
-            raw_event_handlers: opt.raw_event_handlers,
+            event_handler: opt.event_handler,
             #[cfg(feature = "framework")]
             framework: opt.framework,
             manager: opt.manager,
@@ -120,15 +118,17 @@ impl ShardRunner {
             if post != pre {
                 self.update_manager().await;
 
-                for event_handler in self.event_handlers.clone() {
+                if let Some(InternalEventHandler::Normal(event_handler)) = &self.event_handler {
+                    let event_handler = Arc::clone(event_handler);
                     let context = self.make_context();
                     let event = ShardStageUpdateEvent {
                         new: post,
                         old: pre,
                         shard_id: self.shard.shard_info().id,
                     };
+
                     spawn_named("dispatch::event_handler::shard_stage_update", async move {
-                        event_handler.shard_stage_update(&context, &event).await;
+                        event_handler.shard_stage_update(context, event).await;
                     });
                 }
             }
@@ -172,14 +172,15 @@ impl ShardRunner {
             if let Some(event) = event {
                 #[cfg(feature = "collector")]
                 self.collectors.lock().expect("poison").retain_mut(|callback| (callback.0)(&event));
-
-                dispatch_model(
-                    event,
-                    self.make_context(),
-                    #[cfg(feature = "framework")]
-                    self.framework.clone(),
-                    &self.event_handlers,
-                    &self.raw_event_handlers,
+                spawn_named(
+                    "shard_runner::dispatch",
+                    dispatch_model(
+                        event,
+                        self.make_context(),
+                        #[cfg(feature = "framework")]
+                        self.framework.clone(),
+                        self.event_handler.clone(),
+                    ),
                 );
             }
 
@@ -473,8 +474,7 @@ impl ShardRunner {
 /// Options to be passed to [`ShardRunner::new`].
 pub struct ShardRunnerOptions {
     pub data: Arc<dyn std::any::Any + Send + Sync>,
-    pub event_handlers: Vec<Arc<dyn EventHandler>>,
-    pub raw_event_handlers: Vec<Arc<dyn RawEventHandler>>,
+    pub event_handler: Option<InternalEventHandler>,
     #[cfg(feature = "framework")]
     pub framework: Option<Arc<dyn Framework>>,
     pub manager: Arc<ShardManager>,
diff --git a/src/model/channel/attachment.rs b/src/model/channel/attachment.rs
index f1d7b5ce398..3fc57449a5c 100644
--- a/src/model/channel/attachment.rs
+++ b/src/model/channel/attachment.rs
@@ -105,8 +105,8 @@ impl Attachment {
     /// #[serenity::async_trait]
     /// # #[cfg(feature = "client")]
     /// impl EventHandler for Handler {
-    ///     async fn message(&self, context: &Context, message: &Message) {
-    ///         for attachment in &message.attachments {
+    ///     async fn message(&self, context: Context, message: Message) {
+    ///         for attachment in message.attachments {
     ///             let content = match attachment.download().await {
     ///                 Ok(content) => content,
     ///                 Err(why) => {
diff --git a/src/model/channel/guild_channel.rs b/src/model/channel/guild_channel.rs
index 2599c923361..9af665949ab 100644
--- a/src/model/channel/guild_channel.rs
+++ b/src/model/channel/guild_channel.rs
@@ -629,7 +629,7 @@ impl GuildChannel {
     ///
     /// #[serenity::async_trait]
     /// impl EventHandler for Handler {
-    ///     async fn message(&self, context: &Context, msg: &Message) {
+    ///     async fn message(&self, context: Context, msg: Message) {
     ///         let Some(guild) = msg.guild(&context.cache) else {
     ///             return;
     ///         };
diff --git a/src/model/error.rs b/src/model/error.rs
index f1e3c4b5bca..c30c61767e2 100644
--- a/src/model/error.rs
+++ b/src/model/error.rs
@@ -116,7 +116,7 @@ impl fmt::Display for Minimum {
 /// #[serenity::async_trait]
 /// #[cfg(feature = "client")]
 /// impl EventHandler for Handler {
-///     async fn guild_ban_removal(&self, ctx: &Context, guild_id: &GuildId, user: &User) {
+///     async fn guild_ban_removal(&self, ctx: Context, guild_id: GuildId, user: User) {
 ///         match guild_id.ban(&ctx.http, user.id, 8, Some("No unbanning people!")).await {
 ///             Ok(()) => {
 ///                 // Ban successful.
diff --git a/src/model/guild/mod.rs b/src/model/guild/mod.rs
index 4b0facdf2e3..68cb05f3c33 100644
--- a/src/model/guild/mod.rs
+++ b/src/model/guild/mod.rs
@@ -2186,7 +2186,7 @@ impl Guild {
     /// #[serenity::async_trait]
     /// #[cfg(all(feature = "cache", feature = "client"))]
     /// impl EventHandler for Handler {
-    ///     async fn message(&self, ctx: &Context, msg: &Message) {
+    ///     async fn message(&self, ctx: Context, msg: Message) {
     ///         if let Some(guild_id) = msg.guild_id {
     ///             if let Some(guild) = guild_id.to_guild_cached(&ctx.cache) {
     ///                 if let Some(role) = guild.role_by_name("role_name") {
diff --git a/src/model/guild/partial_guild.rs b/src/model/guild/partial_guild.rs
index e6c39d5ad31..a0e8281e35a 100644
--- a/src/model/guild/partial_guild.rs
+++ b/src/model/guild/partial_guild.rs
@@ -1294,7 +1294,7 @@ impl PartialGuild {
     /// #[serenity::async_trait]
     /// #[cfg(all(feature = "cache", feature = "client"))]
     /// impl EventHandler for Handler {
-    ///     async fn message(&self, ctx: &Context, msg: &Message) {
+    ///     async fn message(&self, ctx: Context, msg: Message) {
     ///         if let Some(guild_id) = msg.guild_id {
     ///             if let Some(guild) = guild_id.to_guild_cached(&ctx.cache) {
     ///                 if let Some(role) = guild.role_by_name("role_name") {
diff --git a/src/model/user.rs b/src/model/user.rs
index 96f093707d7..f1004906be1 100644
--- a/src/model/user.rs
+++ b/src/model/user.rs
@@ -511,7 +511,7 @@ impl User {
     /// #[serenity::async_trait]
     /// # #[cfg(feature = "client")]
     /// impl EventHandler for Handler {
-    ///     async fn message(&self, context: &Context, msg: &Message) {
+    ///     async fn message(&self, context: Context, msg: Message) {
     ///         if msg.content == "!mytag" {
     ///             let content = format!("Your tag is: {}", msg.author.tag());
     ///             let _ = msg.channel_id.say(&context.http, &content).await;
@@ -628,7 +628,7 @@ impl UserId {
     /// #[serenity::async_trait]
     /// # #[cfg(feature = "client")]
     /// impl EventHandler for Handler {
-    ///     async fn message(&self, ctx: &Context, msg: &Message) {
+    ///     async fn message(&self, ctx: Context, msg: Message) {
     ///         if msg.content == "~help" {
     ///             let builder = CreateMessage::new().content("Helpful info here.");
     ///