diff --git a/bindings/channels.go b/bindings/channels.go
index 24a0b5450712461a385bddbd324ade3167189992..42171d7f913014179b556721f5defdbb08ac98d2 100644
--- a/bindings/channels.go
+++ b/bindings/channels.go
@@ -1158,22 +1158,26 @@ func ValidForever() int {
 //     to the user should be tracked while all actions should not be.
 //   - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty,
 //     and [GetDefaultCMixParams] will be used internally.
-//   - pingsJSON - JSON of a slice of public keys of users that should receive
-//     mobile notifications for the message.
-//
-// Example pingsJSON:
-//
-//	[
-//	  "FgJMvgSsY4rrKkS/jSe+vFOJOs5qSSyOUSW7UtF9/KU=",
-//	  "fPqcHtrJ398PAC35QyWXEU9PHzz8Z4BKQTCxSvpSygw=",
-//	  "JnjCgh7g/+hNiI9VPKW01aRSxGOFmNulNCymy3ImXAo="
-//	]
+//   - pingsMapJSON - JSON of a map of slices of [ed25519.PublicKey] of users
+//     that should receive mobile notifications for the message. Each slice keys
+//     on a [channels.PingType] that describes the type of notification it is.
+//
+// Example pingsMapJSON:
+//  {
+//    "usrMention": [
+//      "CLdKxbe8D2WVOpx1mT63TZ5CP/nesmxHLT5DUUalpe0=",
+//      "S2c6NXjNqgR11SCOaiQUughWaLpWBKNufPt6cbTVHMA="
+//    ],
+//    "usrReply": [
+//      "aaMzSeA6Cu2Aix2MlOwzrAI+NnpKshzvZRT02PZPVec="
+//    ]
+//  }
 //
 // Returns:
 //   - []byte - JSON of [ChannelSendReport].
 func (cm *ChannelsManager) SendGeneric(channelIdBytes []byte, messageType int,
 	message []byte, validUntilMS int64, tracked bool, cmixParamsJSON []byte,
-	pingsJSON []byte) ([]byte, error) {
+	pingsMapJSON []byte) ([]byte, error) {
 
 	// Unmarshal channel ID and parameters
 	channelID, params, err :=
@@ -1190,14 +1194,14 @@ func (cm *ChannelsManager) SendGeneric(channelIdBytes []byte, messageType int,
 		lease = channels.ValidForever
 	}
 
-	pings, err := unmarshalPingsJson(pingsJSON)
+	pingsMap, err := unmarshalPingsMapJson(pingsMapJSON)
 	if err != nil {
 		return nil, err
 	}
 
 	// Send message
 	messageID, rnd, ephID, err := cm.api.SendGeneric(
-		channelID, msgType, message, lease, tracked, params.CMIX, pings)
+		channelID, msgType, message, lease, tracked, params.CMIX, pingsMap)
 	if err != nil {
 		return nil, err
 	}
@@ -2025,10 +2029,23 @@ func (cm *ChannelsManager) SetMobileNotificationsLevel(
 //
 // Example return:
 //
-//	[
-//	  {"channel":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD","type":1},
-//	  {"channel":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD","type":2}
-//	]
+//  [
+//    {
+//      "channel": "jOgZopfYj4zrE/AHtKmkf+QEWnfUKv9KfIy/+Bsg0PkD",
+//      "type": 1,
+//      "pingType": "usrMention"
+//    },
+//    {
+//      "channel": "GKmfN/LKXQYM6++TC6DeZYqoxvSUPkh5UAHWODqh9zkD",
+//      "type": 2,
+//      "pingType": "usrReply"
+//    },
+//    {
+//      "channel": "M+28xtj0coHrhDHfojGNcyb2c4maO7ZuheB6egS0Pc4D",
+//      "type": 1,
+//      "pingType": ""
+//    }
+//  ]
 func GetChannelNotificationReportsForMe(notificationFilterJSON []byte,
 	notificationDataCSV string) ([]byte, error) {
 	var nfs []channels.NotificationFilter
@@ -3410,3 +3427,11 @@ func unmarshalPingsJson(b []byte) ([]ed25519.PublicKey, error) {
 	}
 	return pings, nil
 }
+
+func unmarshalPingsMapJson(b []byte) (map[channels.PingType][]ed25519.PublicKey, error) {
+	var pingsMap map[channels.PingType][]ed25519.PublicKey
+	if b != nil && len(b) > 0 {
+		return pingsMap, json.Unmarshal(b, &pingsMap)
+	}
+	return pingsMap, nil
+}
diff --git a/channels/interface.go b/channels/interface.go
index 1f29b861446f2e34d93fa45b4e63816ed0b3ec23..2533c86fd01b8654b203e02ec337bca62d92754d 100644
--- a/channels/interface.go
+++ b/channels/interface.go
@@ -122,7 +122,7 @@ type Manager interface {
 	// ID (i.e., always returns 0) cannot be tracked, or it will cause errors.
 	SendGeneric(channelID *id.ID, messageType MessageType, msg []byte,
 		validUntil time.Duration, tracked bool, params cmix.CMIXParams,
-		pings []ed25519.PublicKey) (
+		pingsMap map[PingType][]ed25519.PublicKey) (
 		message.ID, rounds.Round, ephemeral.Id, error)
 
 	// SendMessage is used to send a formatted message over a channel.
diff --git a/channels/notifications.go b/channels/notifications.go
index 5da23e13ba54cb1167e176cf6a2f196f083aebe2..b340befe0d013f621a17939c6ab2eaa2c3b9aa6a 100644
--- a/channels/notifications.go
+++ b/channels/notifications.go
@@ -93,7 +93,8 @@ func (n *notifications) addChannel(channelID *id.ID) {
 	err := n.nm.Set(
 		channelID, notificationGroup, NotifyNone.Marshal(), clientNotif.Mute)
 	if err != nil {
-		jww.WARN.Printf("[CH] Failed to add channel (%s) to notifications manager: %+v", channelID, err)
+		jww.WARN.Printf("[CH] Failed to add channel (%s) to notifications "+
+			"manager: %+v", channelID, err)
 	}
 
 }
@@ -103,7 +104,8 @@ func (n *notifications) addChannel(channelID *id.ID) {
 func (n *notifications) removeChannel(channelID *id.ID) {
 	err := n.nm.Delete(channelID)
 	if err != nil {
-		jww.WARN.Printf("[CH] Failed to remove channel (%s) from notifications manager: %+v", channelID, err)
+		jww.WARN.Printf("[CH] Failed to remove channel (%s) from notifications "+
+			"manager: %+v", channelID, err)
 	}
 }
 
@@ -226,7 +228,8 @@ func (n *notifications) processesNotificationUpdates(group clientNotif.Group,
 	changed := make([]NotificationState, 0, len(created)+len(edits))
 
 	nfs := make([]NotificationFilter, 0, len(group))
-	tags := makeUserPingTags(n.pubKey)
+	tags := makeUserPingTags(map[PingType][]ed25519.PublicKey{
+		ReplyPing: {n.pubKey}, MentionPing: {n.pubKey}})
 	for chanID, notif := range group {
 		channelID := chanID.DeepCopy()
 
@@ -303,6 +306,10 @@ type NotificationReport struct {
 
 	// Type is the MessageType of the message that the notification belongs to.
 	Type MessageType `json:"type"`
+
+	// PingType describes the type of ping. If it is empty, then it is a generic
+	// ping.
+	PingType PingType `json:"pingType,omitempty"`
 }
 
 // GetNotificationReportsForMe checks the notification data against the filter
@@ -331,10 +338,12 @@ func GetNotificationReportsForMe(nfs []NotificationFilter,
 
 			if found {
 				messageType := UnmarshalMessageType(b)
-				if nf.match(matchedTags, messageType) {
+				match, pt := nf.match(matchedTags, messageType)
+				if match {
 					nr = append(nr, NotificationReport{
-						Channel: nf.ChannelID,
-						Type:    messageType,
+						Channel:  nf.ChannelID,
+						Type:     messageType,
+						PingType: pt,
 					})
 				}
 			}
@@ -394,7 +403,9 @@ type AllowLists struct {
 // match determines if the message with the given tags and message type are
 // allowed through the filter.
 func (nf NotificationFilter) match(
-	matchedTags map[string]struct{}, mt MessageType) bool {
+	matchedTags map[string]struct{}, mt MessageType) (bool, PingType) {
+	pt := GenericPing
+
 	// Check if any filter tags match the matched tags
 	for _, tag := range nf.Tags {
 
@@ -402,18 +413,30 @@ func (nf NotificationFilter) match(
 		// with tags list
 		if _, exists := matchedTags[tag]; exists {
 			if _, exists = nf.AllowWithTags[mt]; exists {
-				return true
+				currentPT, err := pingTypeFromTag(tag)
+				if err != nil {
+					jww.WARN.Printf(
+						"[CH] Failed to get ping type for tag %q: %+v", tag, err)
+				}
+
+				pt = getRankingPingType(pt, currentPT)
+			} else {
+				return false, ""
 			}
-			return false
+
 		}
 	}
 
+	if pt != GenericPing {
+		return true, pt
+	}
+
 	// If no tag matches, then check if the message type is in the allowed
 	// without tags list
 	if _, exists := nf.AllowWithoutTags[mt]; exists {
-		return true
+		return true, pt
 	}
-	return false
+	return false, ""
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -438,7 +461,7 @@ var notificationLevelAllowLists = map[notificationSourceType]map[NotificationLev
 			AllowWithoutTags: map[MessageType]struct{}{},
 		},
 		NotifyAll: {
-			AllowWithTags:    map[MessageType]struct{}{},
+			AllowWithTags:    map[MessageType]struct{}{Text: {}},
 			AllowWithoutTags: map[MessageType]struct{}{Text: {}},
 		},
 	},
@@ -448,7 +471,7 @@ var notificationLevelAllowLists = map[notificationSourceType]map[NotificationLev
 			AllowWithoutTags: map[MessageType]struct{}{Pinned: {}},
 		},
 		NotifyAll: {
-			AllowWithTags:    map[MessageType]struct{}{},
+			AllowWithTags:    map[MessageType]struct{}{AdminText: {}, Pinned: {}},
 			AllowWithoutTags: map[MessageType]struct{}{AdminText: {}, Pinned: {}},
 		},
 	},
diff --git a/channels/notifications_test.go b/channels/notifications_test.go
index e04488d8374992a368adc08e23244aa4645e2362..1d37db109f3b03d62339d44b70c0eab0e7686c21 100644
--- a/channels/notifications_test.go
+++ b/channels/notifications_test.go
@@ -11,6 +11,7 @@ import (
 	"bytes"
 	"crypto/ed25519"
 	"encoding/json"
+	"fmt"
 	"math/rand"
 	"reflect"
 	"sort"
@@ -320,17 +321,20 @@ func Test_notifications_processesNotificationUpdates(t *testing.T) {
 		})
 
 		if level != NotifyNone {
+			tags := makeUserPingTags(map[PingType][]ed25519.PublicKey{
+				ReplyPing: {n.pubKey}, MentionPing: {n.pubKey}})
+			sort.Strings(tags)
 			ex = append(ex,
 				NotificationFilter{
 					Identifier: ch.broadcast.AsymmetricIdentifier(),
 					ChannelID:  channelID,
-					Tags:       makeUserPingTags(n.pubKey),
+					Tags:       tags,
 					AllowLists: notificationLevelAllowLists[asymmetric][level],
 				},
 				NotificationFilter{
 					Identifier: ch.broadcast.SymmetricIdentifier(),
 					ChannelID:  channelID,
-					Tags:       makeUserPingTags(n.pubKey),
+					Tags:       tags,
 					AllowLists: notificationLevelAllowLists[symmetric][level],
 				})
 		}
@@ -359,6 +363,10 @@ func Test_notifications_processesNotificationUpdates(t *testing.T) {
 		return bytes.Compare(nf[i].Identifier, nf[j].Identifier) == -1
 	})
 
+	for i := range nf {
+		sort.Strings(nf[i].Tags)
+	}
+
 	if !reflect.DeepEqual(ex, nf) {
 		t.Errorf("Unexpected filter list."+
 			"\nexpected: %+v\nreceived: %+v", ex, nf)
@@ -386,6 +394,7 @@ func TestGetNotificationReportsForMe(t *testing.T) {
 	types := []MessageType{Text, AdminText, Reaction, Delete, Pinned, Mute,
 		AdminReplay, FileTransfer}
 	levels := []NotificationLevel{NotifyPing, NotifyAll}
+	pingTypes := []PingType{ReplyPing, MentionPing}
 
 	var expected []NotificationReport
 	var notifData []*primNotif.Data
@@ -400,7 +409,8 @@ func TestGetNotificationReportsForMe(t *testing.T) {
 					identifier := append(chanID.Marshal(), []byte("identifier")...)
 					tags := make([]string, 1+rng.Intn(4))
 					for j := range tags {
-						tags[j] = makeUserPingTag(makeEd25519PubKey(rng, t))
+						tags[j] = makeUserPingTag(makeEd25519PubKey(rng, t),
+							pingTypes[rng.Intn(len(pingTypes))])
 					}
 
 					mtByte := mt.Marshal()
@@ -417,8 +427,13 @@ func TestGetNotificationReportsForMe(t *testing.T) {
 
 					if includeChannel {
 						var filterTags []string
+						var pt PingType
 						if includeTags {
 							filterTags = []string{tags[rng.Intn(len(tags))]}
+							pt, err = pingTypeFromTag(filterTags[0])
+							if err != nil {
+								t.Errorf("Failed to get Ping Type: %+v", err)
+							}
 						}
 						nfs = append(nfs, NotificationFilter{
 							Identifier: identifier,
@@ -436,8 +451,9 @@ func TestGetNotificationReportsForMe(t *testing.T) {
 						}
 
 						expected = append(expected, NotificationReport{
-							Channel: chanID,
-							Type:    mt,
+							Channel:  chanID,
+							Type:     mt,
+							PingType: pt,
 						})
 					}
 				}
@@ -447,6 +463,9 @@ func TestGetNotificationReportsForMe(t *testing.T) {
 
 	nrs := GetNotificationReportsForMe(nfs, notifData)
 
+	data, _ := json.MarshalIndent(nrs, "//  ", "  ")
+	fmt.Printf("//  %s\n", data)
+
 	if !reflect.DeepEqual(nrs, expected) {
 		t.Errorf("NotificationReport list does not match expected."+
 			"\nexpected: %+v\nreceived: %+v", expected, nrs)
@@ -520,7 +539,8 @@ func TestNotificationFilter_JSON(t *testing.T) {
 	nf := NotificationFilter{
 		Identifier: append(chanID.Marshal(), []byte("Identifier")...),
 		ChannelID:  chanID,
-		Tags:       makeUserPingTags(makeEd25519PubKey(rng, t)),
+		Tags: makeUserPingTags(map[PingType][]ed25519.PublicKey{
+			MentionPing: {makeEd25519PubKey(rng, t)}}),
 		AllowLists: notificationLevelAllowLists[symmetric][NotifyPing],
 	}
 
@@ -553,7 +573,8 @@ func TestNotificationFilter_Slice_JSON(t *testing.T) {
 		nfs[i] = NotificationFilter{
 			Identifier: append(chanID.Marshal(), []byte("Identifier")...),
 			ChannelID:  chanID,
-			Tags:       makeUserPingTags(makeEd25519PubKey(rng, t)),
+			Tags: makeUserPingTags(map[PingType][]ed25519.PublicKey{
+				MentionPing: {makeEd25519PubKey(rng, t)}}),
 			AllowLists: notificationLevelAllowLists[sourceTypes[i%len(sourceTypes)]][levels[i%len(levels)]],
 		}
 	}
diff --git a/channels/pingTags.go b/channels/pingTags.go
new file mode 100644
index 0000000000000000000000000000000000000000..4ae28f1c9ed2907caabec24785b8b6247f13e5d9
--- /dev/null
+++ b/channels/pingTags.go
@@ -0,0 +1,86 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2022 xx foundation                                             //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file.                                                              //
+////////////////////////////////////////////////////////////////////////////////
+
+package channels
+
+import (
+	"crypto/ed25519"
+	"encoding/hex"
+	"strings"
+
+	"github.com/pkg/errors"
+)
+
+// PingType describes a user ping. It is used to describe more information about
+// the ping to the user.
+type PingType string
+
+const (
+	// GenericPing indicates a generic notification that is not a reply or
+	// mention.
+	GenericPing PingType = ""
+
+	// ReplyPing indicates that someone replied to this user's message.
+	ReplyPing PingType = "usrReply"
+
+	// MentionPing indicates that someone mentioned (tagged) the user in a
+	// message.
+	MentionPing PingType = "usrMention"
+)
+
+// getRankingPingType returns the ping that ranks higher.
+func getRankingPingType(a, b PingType) PingType {
+	if pingHierarchy[a] > pingHierarchy[b] {
+		return a
+	}
+	return b
+}
+
+// pingHierarchy describes the ranking of ping types. If a user receives a ping
+// on multiple tags, the highest ranking ping tag is reported to the user.
+var pingHierarchy = map[PingType]uint32{
+	GenericPing: 0,
+	MentionPing: 100,
+	ReplyPing:   200,
+}
+
+// delimiter between the public key and ping type in a ping tag.
+const pingTagDelim = "-"
+
+// makeUserPingTags creates a list of tags to include in a cmix.Service from
+// channel pings.
+func makeUserPingTags(pings map[PingType][]ed25519.PublicKey) []string {
+	if pings == nil || len(pings) == 0 {
+		return nil
+	}
+	var tags []string
+	for pt, users := range pings {
+		s := make([]string, len(users))
+		for i := range users {
+			s[i] = makeUserPingTag(users[i], pt)
+		}
+		tags = append(tags, s...)
+	}
+
+	return tags
+}
+
+// makeUserPingTag creates a tag from a user's public key and ping type to be
+// used in a tag list in a cmix.Service.
+func makeUserPingTag(user ed25519.PublicKey, pt PingType) string {
+	return hex.EncodeToString(user) + pingTagDelim + string(pt)
+}
+
+// pingTypeFromTag gets the PingType from the ping tag.
+func pingTypeFromTag(tag string) (PingType, error) {
+	s := strings.SplitN(tag, pingTagDelim, 2)
+	if len(s) < 2 {
+		return "", errors.New("invalid ping tag")
+	}
+
+	return PingType(s[1]), nil
+}
diff --git a/channels/pingTags_test.go b/channels/pingTags_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..0d98c8fd7a3963e806311729123c3a58af314c77
--- /dev/null
+++ b/channels/pingTags_test.go
@@ -0,0 +1,133 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2022 xx foundation                                             //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file.                                                              //
+////////////////////////////////////////////////////////////////////////////////
+
+package channels
+
+import (
+	"crypto/ed25519"
+	"math/rand"
+	"reflect"
+	"sort"
+	"testing"
+)
+
+// Tests that all combinations of defined PingType passed into
+// getRankingPingType result in the expected type.
+func Test_getRankingPingType(t *testing.T) {
+	tests := []struct{ a, b, expected PingType }{
+		{GenericPing, ReplyPing, ReplyPing},
+		{GenericPing, MentionPing, MentionPing},
+		{ReplyPing, GenericPing, ReplyPing},
+		{ReplyPing, MentionPing, ReplyPing},
+		{MentionPing, GenericPing, MentionPing},
+		{MentionPing, ReplyPing, ReplyPing},
+	}
+
+	for i, tt := range tests {
+		pt := getRankingPingType(tt.a, tt.b)
+		if tt.expected != pt {
+			t.Errorf("Unexpected PingType when comparing %q with %q (%d)."+
+				"\nexpected: %q\nreceived: %q", tt.a, tt.b, i, tt.expected, pt)
+		}
+	}
+}
+
+// Unit test of makeUserPingTags.
+func Test_makeUserPingTags(t *testing.T) {
+	prng := rand.New(rand.NewSource(579498))
+	types := []PingType{ReplyPing, MentionPing}
+
+	for i := 0; i < 6; i++ {
+		pings := map[PingType][]ed25519.PublicKey{
+			ReplyPing: {}, MentionPing: {},
+		}
+		expected := make([]string, i)
+		for j := 0; j < i; j++ {
+			pt := types[prng.Intn(len(types))]
+			user, _, _ := ed25519.GenerateKey(prng)
+			pings[pt] = append(pings[pt], user)
+			expected[j] = makeUserPingTag(user, pt)
+		}
+		if i == 0 {
+			expected = nil
+		}
+		tags := makeUserPingTags(pings)
+		sort.Strings(expected)
+		sort.Strings(tags)
+		if !reflect.DeepEqual(expected, tags) {
+			t.Errorf("Unexpected tags (%d).\nexpected: %#v\nreceived: %#v",
+				i, expected, tags)
+		}
+	}
+}
+
+// Consistency test of makeUserPingTag.
+func TestManager_makeUserPingTag_Consistency(t *testing.T) {
+	prng := rand.New(rand.NewSource(579498))
+	expected := []string{
+		"32b15a6bd6db85bf239a7fe6aeb84072c2646b96188c9c4f7dec6d35928436c2-usrReply",
+		"88ab667cdc60e041849a924ed7b6e60fa2de62eca4b67336c86e7620a19d270f-usrReply",
+		"ddab884d040d9182950aca0eab2899977caf62eef098d0973f99aa1b4c97f541-usrMention",
+		"76341ec7e5da6fe34b80021c4e0f6362d3a1a37091126f8cb74ec8ee5bb375fb-usrMention",
+		"d0f05d30b6d62460788ac7a7bde3b0de8065c6b1673ba3a724af90e87f1a152e-usrMention",
+		"69a8a814d254be6d3652c5f013d063803c6542a94c0dcd52cb9d9f68bcf14041-usrMention",
+		"215a17025a25d892baa595d6677046fe74d625505ad65a64d367975c94477831-usrMention",
+		"9b6400094a53921ff041c024265748df635843cb6a50e8a4d6f68f7c52f9e766-usrMention",
+		"456e99e886cf889b6b9ce63ecece752267b199032a2236239aaedf9d519a491b-usrReply",
+		"2cf8839b549c5b360fb5434de944918d5680e203e173889fd48dd4c91099762e-usrMention",
+		"a0a48cffe820a8f7e213b6bb99b99c1270fc8126bbde165b756449895f6f6603-usrReply",
+		"d1086f945874607d89d9cc59c6e940217bd69c8d71dc45a0f3728cb6146ae154-usrReply",
+		"db60467fc127405470c73cbc4734d1098d43e85c61904a114d95466cfb30781c-usrReply",
+		"7bd7b36cf1393fdf8576df1182e5b668bb3f021aa4a1b562d885711153a30395-usrReply",
+		"61a25b1fbb5dd9490d43b64d6899ae3f1fac2c2e7bf8cade5fbbcced897f10fe-usrReply",
+		"689c8cc76cb15c457f6666457b360701d4f1a6312f6f1a1f1c910353a5d0ce10-usrMention",
+		"64c7b1b343f38077d30cb9b99a0ed2844974d984fbd9630999a4a59069a964a7-usrReply",
+		"f56ee72f86ee39d52887a08927606bce8a9d21726a82792655aee098705c48a9-usrReply",
+		"008da28a9f8f70dafe533283acabbed77cb92bf5058f044bb50e1167ce6f05b4-usrMention",
+		"70a659261ad311cf6111639ab6be9677f5afc0ab9e13546b3afc64597ccaabfd-usrReply",
+	}
+	types := []PingType{ReplyPing, MentionPing}
+
+	for i, exp := range expected {
+		pubKey, _, _ := ed25519.GenerateKey(prng)
+		tag := makeUserPingTag(pubKey, types[prng.Intn(len(types))])
+		if exp != tag {
+			t.Errorf("Unexpected tag for key %X (%d)."+
+				"\nexpected: %s\nreceived: %s", pubKey, i, exp, tag)
+		}
+	}
+}
+
+// Unit test of pingTypeFromTag.
+func Test_pingTypeFromTag(t *testing.T) {
+	prng := rand.New(rand.NewSource(65881))
+	types := []PingType{ReplyPing, MentionPing}
+
+	for i := 0; i < 10; i++ {
+		pubKey, _, _ := ed25519.GenerateKey(prng)
+		expected := types[prng.Intn(len(types))]
+		tag := makeUserPingTag(pubKey, expected)
+
+		pt, err := pingTypeFromTag(tag)
+		if err != nil {
+			t.Errorf("Failed to get type (%d): %+v", i, err)
+		}
+		if expected != pt {
+			t.Errorf("Unexpected PingType (%d)."+
+				"\nexpected: %s\nreceived: %s", i, expected, pt)
+		}
+	}
+}
+
+// Error path: Tests that pingTypeFromTag returns an error for an invalid tag.
+func Test_typeFromPingTag_InvalidTagError(t *testing.T) {
+	_, err := pingTypeFromTag(
+		"64c7b1b343f38077d30cb9b99a0ed2844974d984fbd9630999a4a59069a964a7")
+	if err == nil {
+		t.Errorf("Did not get error for invalid tag.")
+	}
+}
diff --git a/channels/send.go b/channels/send.go
index f91d323b9e6344c18bbaf7e59030bcab11595347..c0f58e442cc7b5d983fe70b0c07df32874f04489 100644
--- a/channels/send.go
+++ b/channels/send.go
@@ -13,20 +13,21 @@ import (
 	"crypto/hmac"
 	"encoding/base64"
 	"fmt"
-	cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
 	"time"
 
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
+	"golang.org/x/crypto/blake2b"
+	"google.golang.org/protobuf/proto"
+
 	"gitlab.com/elixxir/client/v4/cmix"
 	"gitlab.com/elixxir/client/v4/cmix/rounds"
 	"gitlab.com/elixxir/client/v4/emoji"
+	cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
 	"gitlab.com/elixxir/crypto/message"
 	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/id/ephemeral"
 	"gitlab.com/xx_network/primitives/netTime"
-	"golang.org/x/crypto/blake2b"
-	"google.golang.org/protobuf/proto"
 )
 
 const (
@@ -109,7 +110,7 @@ func timeNow() string { return netTime.Now().Format("15:04:05.9999999") }
 // for this message. They must be in the channel and have notifications enabled
 func (m *manager) SendGeneric(channelID *id.ID, messageType MessageType,
 	msg []byte, validUntil time.Duration, tracked bool, params cmix.CMIXParams,
-	pings []ed25519.PublicKey) (
+	pingsMap map[PingType][]ed25519.PublicKey) (
 	message.ID, rounds.Round, ephemeral.Id, error) {
 
 	if hmac.Equal(channelID.Bytes(), emptyChannelID.Bytes()) {
@@ -242,7 +243,7 @@ func (m *manager) SendGeneric(channelID *id.ID, messageType MessageType,
 	}
 
 	log += fmt.Sprintf("Broadcasting message at %s. ", timeNow())
-	tags := makeUserPingTags(pings...)
+	tags := makeUserPingTags(pingsMap)
 	mt := messageType.Marshal()
 	r, ephID, err := ch.broadcast.BroadcastWithAssembler(assemble, tags,
 		[2]byte{mt[0], mt[1]}, params)
@@ -301,8 +302,10 @@ func (m *manager) SendMessage(channelID *id.ID, msg string,
 		return message.ID{}, rounds.Round{}, ephemeral.Id{}, err
 	}
 
+	pingMap := map[PingType][]ed25519.PublicKey{MentionPing: pings}
+
 	return m.SendGeneric(
-		channelID, Text, txtMarshaled, validUntil, true, params, pings)
+		channelID, Text, txtMarshaled, validUntil, true, params, pingMap)
 }
 
 // SendReply is used to send a formatted message over a channel.
@@ -339,8 +342,20 @@ func (m *manager) SendReply(channelID *id.ID, msg string,
 		return message.ID{}, rounds.Round{}, ephemeral.Id{}, err
 	}
 
+	// Get the public key of the sender
+	mm, err := m.events.model.GetMessage(replyTo)
+	if err != nil {
+		return message.ID{}, rounds.Round{}, ephemeral.Id{}, errors.Wrapf(err,
+			"failed getting message %s from event model", replyTo)
+	}
+
+	pingMap := map[PingType][]ed25519.PublicKey{
+		ReplyPing:   {mm.PubKey},
+		MentionPing: pings,
+	}
+
 	return m.SendGeneric(
-		channelID, Text, txtMarshaled, validUntil, true, params, pings)
+		channelID, Text, txtMarshaled, validUntil, true, params, pingMap)
 }
 
 // SendReaction is used to send a reaction to a message over a channel. The
@@ -471,9 +486,11 @@ func (m *manager) SendInvite(channelID *id.ID, msg string,
 		return message.ID{}, rounds.Round{}, ephemeral.Id{}, err
 	}
 
+	pingMap := map[PingType][]ed25519.PublicKey{MentionPing: pings}
+
 	// Send invitation
 	return m.SendGeneric(channelID, Invitation, invitationMarshalled,
-		validUntil, true, params, pings)
+		validUntil, true, params, pingMap)
 }
 
 // replayAdminMessage is used to rebroadcast an admin message asa a norma user.
@@ -791,21 +808,5 @@ func makeChaDebugTag(
 	h.Write(id)
 
 	tripCode := base64.RawStdEncoding.EncodeToString(h.Sum(nil))[:12]
-	return fmt.Sprintf("%s-%s", baseTag, tripCode)
-}
-
-func makeUserPingTags(users ...ed25519.PublicKey) []string {
-	if users == nil || len(users) == 0 {
-		return nil
-	}
-	s := make([]string, len(users))
-	for i := 0; i < len(s); i++ {
-		s[i] = makeUserPingTag(users[i])
-	}
-	return s
-}
-
-func makeUserPingTag(user ed25519.PublicKey) string {
-	return fmt.Sprintf("%x-usrping", user)
-
+	return baseTag + "-" + tripCode
 }
diff --git a/channels/send_test.go b/channels/send_test.go
index b09b33d5c66bf67e04d6b391c300d11880ee884f..9881a7406b0dae488399a97225912e848e7bf7e3 100644
--- a/channels/send_test.go
+++ b/channels/send_test.go
@@ -14,27 +14,25 @@ import (
 	"testing"
 	"time"
 
+	"github.com/golang/protobuf/proto"
 	"github.com/stretchr/testify/require"
-	"gitlab.com/elixxir/client/v4/collective"
 
-	"github.com/golang/protobuf/proto"
+	"gitlab.com/elixxir/client/v4/broadcast"
+	"gitlab.com/elixxir/client/v4/cmix"
 	"gitlab.com/elixxir/client/v4/cmix/identity/receptionID"
 	"gitlab.com/elixxir/client/v4/cmix/rounds"
+	"gitlab.com/elixxir/client/v4/collective"
 	"gitlab.com/elixxir/client/v4/collective/versioned"
+	cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
 	cryptoChannel "gitlab.com/elixxir/crypto/channel"
 	"gitlab.com/elixxir/crypto/fastRNG"
 	"gitlab.com/elixxir/crypto/message"
 	"gitlab.com/elixxir/crypto/rsa"
 	"gitlab.com/elixxir/ekv"
 	"gitlab.com/xx_network/crypto/csprng"
-	"gitlab.com/xx_network/primitives/netTime"
-
 	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/id/ephemeral"
-
-	"gitlab.com/elixxir/client/v4/broadcast"
-	"gitlab.com/elixxir/client/v4/cmix"
-	cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
+	"gitlab.com/xx_network/primitives/netTime"
 )
 
 func Test_manager_SendGeneric(t *testing.T) {
@@ -289,7 +287,7 @@ func Test_manager_SendReply(t *testing.T) {
 		channels:        make(map[id.ID]*joinedChannel),
 		local:           kv,
 		rng:             crng,
-		events:          initEvents(&mockEventModel{}, 512, kv, crng),
+		events:          initEvents(&MockEvent{}, 512, kv, crng),
 		nicknameManager: &nicknameManager{byChannel: make(map[id.ID]string), remote: nil},
 		st: loadSendTracker(&mockBroadcastClient{}, kv, func(*id.ID,
 			*userMessageInternal, []byte, time.Time,
@@ -816,6 +814,7 @@ func Test_manager_MuteUser(t *testing.T) {
 	}
 }
 
+
 ////////////////////////////////////////////////////////////////////////////////
 // Mock Interfaces                                                            //
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/channelsFileTransfer/utils_test.go b/channelsFileTransfer/utils_test.go
index 656af2634d8eb3e68790e1e1f63b9901cde6f43f..e11f5e45d4f4f3cc73e5ca1d9fb63af53fceb71e 100644
--- a/channelsFileTransfer/utils_test.go
+++ b/channelsFileTransfer/utils_test.go
@@ -582,21 +582,19 @@ func (m *mockChannelsManager) GetChannels() []*id.ID                      { pani
 func (m *mockChannelsManager) GetChannel(*id.ID) (*cryptoBroadcast.Channel, error) {
 	panic("implement me")
 }
-func (m *mockChannelsManager) SendSilent(channelID *id.ID, validUntil time.Duration, params cmix.CMIXParams) (cryptoMessage.ID, rounds.Round, ephemeral.Id, error) {
+func (m *mockChannelsManager) SendSilent(*id.ID, time.Duration, cmix.CMIXParams) (cryptoMessage.ID, rounds.Round, ephemeral.Id, error) {
 	panic("implement me")
 }
-
-func (m *mockChannelsManager) SendInvite(channelID *id.ID, msg string, inviteTo *cryptoBroadcast.Channel, host string, validUntil time.Duration, params cmix.CMIXParams, pings []ed25519.PublicKey) (cryptoMessage.ID, rounds.Round, ephemeral.Id, error) {
+func (m *mockChannelsManager) SendInvite(*id.ID, string, *cryptoBroadcast.Channel, string, time.Duration, cmix.CMIXParams, []ed25519.PublicKey) (cryptoMessage.ID, rounds.Round, ephemeral.Id, error) {
 	panic("implement me")
 }
-
-func (m *mockChannelsManager) GetNotificationStatus(channelID *id.ID) (clientNotif.NotificationState, error) {
+func (m *mockChannelsManager) GetNotificationStatus(*id.ID) (clientNotif.NotificationState, error) {
 	panic("implement me")
 }
 
 func (m *mockChannelsManager) SendGeneric(channelID *id.ID,
 	messageType channels.MessageType, msg []byte, validUntil time.Duration,
-	_ bool, _ cmix.CMIXParams, _ []ed25519.PublicKey) (
+	_ bool, _ cmix.CMIXParams, _ map[channels.PingType][]ed25519.PublicKey) (
 	cryptoMessage.ID, rounds.Round, ephemeral.Id, error) {
 
 	msgID := cryptoMessage.DeriveChannelMessageID(channelID, 0, msg)
@@ -645,11 +643,10 @@ func (m *mockChannelsManager) GetNickname(*id.ID) (nickname string, exists bool)
 
 func (m *mockChannelsManager) Muted(*id.ID) bool                        { panic("implement me") }
 func (m *mockChannelsManager) GetMutedUsers(*id.ID) []ed25519.PublicKey { panic("implement me") }
-func (m *mockChannelsManager) GetNotificationLevel(channelID *id.ID) (channels.NotificationLevel, error) {
+func (m *mockChannelsManager) GetNotificationLevel(*id.ID) (channels.NotificationLevel, error) {
 	panic("implement me")
 }
-func (m *mockChannelsManager) SetMobileNotificationsLevel(channelID *id.ID, level channels.NotificationLevel,
-	status clientNotif.NotificationState) error {
+func (m *mockChannelsManager) SetMobileNotificationsLevel(*id.ID, channels.NotificationLevel, clientNotif.NotificationState) error {
 	panic("implement me")
 }
 func (m *mockChannelsManager) IsChannelAdmin(*id.ID) bool { panic("implement me") }
diff --git a/channelsFileTransfer/wrapper.go b/channelsFileTransfer/wrapper.go
index 7b9e6b9e724fb90065677a75666909c76a6937d4..6c4fcaf35f486b21d9833ee2279792802274e7da 100644
--- a/channelsFileTransfer/wrapper.go
+++ b/channelsFileTransfer/wrapper.go
@@ -343,8 +343,11 @@ func (w *Wrapper) Send(channelID *id.ID, fileLink []byte, fileName,
 			errors.Wrap(err, "error JSON marshalling file info")
 	}
 
+	pingMap := map[channels.PingType][]ed25519.PublicKey{
+		channels.MentionPing: pings}
+
 	return w.ch.SendGeneric(channelID, channels.FileTransfer,
-		fileInfo, validUntil, true, params.CMIX, pings)
+		fileInfo, validUntil, true, params.CMIX, pingMap)
 }
 
 // RegisterSentProgressCallback registers the callback to the given file