diff --git a/src/types/message.rs b/src/types/message.rs
index d42835ae..54da04d9 100644
--- a/src/types/message.rs
+++ b/src/types/message.rs
@@ -7,7 +7,7 @@ use url::Url;
 use crate::types::{
     Animation, Audio, BareChatId, Chat, ChatId, Contact, Dice, Document, Game,
     InlineKeyboardMarkup, Invoice, Location, MessageAutoDeleteTimerChanged, MessageEntity,
-    MessageEntityRef, PassportData, PhotoSize, Poll, ProximityAlertTriggered, Sticker,
+    MessageEntityRef, MessageId, PassportData, PhotoSize, Poll, ProximityAlertTriggered, Sticker,
     SuccessfulPayment, True, User, Venue, Video, VideoChatEnded, VideoChatParticipantsInvited,
     VideoChatScheduled, VideoChatStarted, VideoNote, Voice, WebAppData,
 };
@@ -18,8 +18,8 @@ use crate::types::{
 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
 pub struct Message {
     /// Unique message identifier inside this chat.
-    #[serde(rename = "message_id")]
-    pub id: i32,
+    #[serde(flatten)]
+    pub id: MessageId,
 
     /// Date the message was sent in Unix time.
     #[serde(with = "crate::types::serde_date_from_unix_timestamp")]
@@ -1158,7 +1158,11 @@ impl Message {
     /// [`url`]: Message::url
     #[track_caller]
     #[must_use]
-    pub fn url_of(chat_id: ChatId, chat_username: Option<&str>, message_id: i32) -> Option<Url> {
+    pub fn url_of(
+        chat_id: ChatId,
+        chat_username: Option<&str>,
+        message_id: MessageId,
+    ) -> Option<Url> {
         use BareChatId::*;
 
         // Note: `t.me` links use bare chat ids
@@ -1181,10 +1185,10 @@ impl Message {
         let url = match chat_username {
             // If it's public group (i.e. not DM, not private group), we can produce
             // "normal" t.me link (accessible to everyone).
-            Some(username) => format!("https://t.me/{0}/{1}", username, message_id),
+            Some(username) => format!("https://t.me/{0}/{1}", username, message_id.0),
             // For private supergroups and channels we produce "private" t.me/c links. These are
             // only accessible to the group members.
-            None => format!("https://t.me/c/{0}/{1}", chat_id, message_id),
+            None => format!("https://t.me/c/{0}/{1}", chat_id, message_id.0),
         };
 
         // UNWRAP:
@@ -1276,11 +1280,11 @@ impl Message {
     pub fn url_in_thread_of(
         chat_id: ChatId,
         chat_username: Option<&str>,
-        thread_starter_msg_id: i32,
-        message_id: i32,
+        thread_starter_msg_id: MessageId,
+        message_id: MessageId,
     ) -> Option<Url> {
         Self::url_of(chat_id, chat_username, message_id).map(|mut url| {
-            url.set_query(Some(&format!("thread={thread_starter_msg_id}")));
+            url.set_query(Some(&format!("thread={}", thread_starter_msg_id.0)));
             url
         })
     }
diff --git a/src/types/message_id.rs b/src/types/message_id.rs
index fe3eb9ab..88a90902 100644
--- a/src/types/message_id.rs
+++ b/src/types/message_id.rs
@@ -1,8 +1,24 @@
 use serde::{Deserialize, Serialize};
 
-/// This object represents a unique message identifier.
+/// A unique message identifier.
 #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
-pub struct MessageId {
-    /// Unique message identifier
-    pub message_id: i32,
+pub struct MessageId(#[serde(rename = "message_id")] pub i32);
+
+#[cfg(test)]
+mod tests {
+    use crate::types::MessageId;
+
+    #[test]
+    fn smoke_deser() {
+        let json = r#"{"message_id":123}"#;
+        let mid: MessageId = serde_json::from_str(json).unwrap();
+        assert_eq!(mid, MessageId(123));
+    }
+
+    #[test]
+    fn smoke_ser() {
+        let mid: MessageId = MessageId(123);
+        let json = serde_json::to_string(&mid).unwrap();
+        assert_eq!(json, r#"{"message_id":123}"#);
+    }
 }
diff --git a/src/types/target_message.rs b/src/types/target_message.rs
index 66f2409e..e851be06 100644
--- a/src/types/target_message.rs
+++ b/src/types/target_message.rs
@@ -1,4 +1,4 @@
-use crate::types::Recipient;
+use crate::types::{MessageId, Recipient};
 
 use serde::{Deserialize, Serialize};
 
@@ -6,8 +6,14 @@ use serde::{Deserialize, Serialize};
 #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
 #[serde(untagged)]
 pub enum TargetMessage {
-    Common { chat_id: Recipient, message_id: i32 },
-    Inline { inline_message_id: String },
+    Common {
+        chat_id: Recipient,
+        #[serde(flatten)]
+        message_id: MessageId,
+    },
+    Inline {
+        inline_message_id: String,
+    },
 }
 
 impl From<String> for TargetMessage {