Messaging
This page highlights the steps you will need to follow to begin integrating chat messaging into your products
The message payload is always the same regardless of which Development Kit the user is using. Users also have a choice on what type of message they want to send.‌

Message Types

Amity supports the sending and receiving of 5 types of messages. They are Text, Image, Audio, File, and Custom.

Image

Amity will automatically optimize the image and when queried, will return the image in small, medium and large sizes. If the image is marked as isFull on upload, the original size of the image can also be returned.

Specifications on image size

When an image is uploaded, it is automatically resized into multiple sizing options. The size of the image is determined by its longest dimension (in pixels) with the aspect ratios being unchanged.
The maximum file size of an image cannot exceed 1 GB.‌

Message Format

When a user is observing messages, the message will always appear in this format.
1
{
2
"messageId": "string",
3
"parentId": "string",
4
"childrenNumber": 0,
5
"channelId": "string",
6
"userId": "string",
7
"type": "string",
8
"tags": [
9
"string"
10
],
11
"data": {
12
"text": "string"
13
},
14
"isDeleted": true,
15
"createdAt": "2020-01-02T06:54:59.244Z",
16
"updatedAt": "2020-01-02T06:54:59.244Z",
17
"editedAt": "2020-01-02T06:54:59.244Z",
18
"flagCount": 0,
19
"reactions": {
20
"reactionName": 1,
21
},
22
"reactionsCount": 0,
23
"myReactions": [
24
"string"
25
]
26
}
Copied!

Messages Description

Name
Data Type
Description
Attributes
messageId
string
The id of this message
​Content
parentId
string
The messageId of the parent of this message
​Content
childrenNumber
integer
The number of messages with parentId of this message
​Content
channelId
string
The name of the channel this message was created in
​Content
userId
string
The name of the user this message was created by
​Content
type
string
The message type
enum*: text custom image file
tags
Array.<string>
The message tags
​Content
data
Object
The message data (any text will be stored in text key)
text: Text message
isDeleted
boolean
The message has been marked as deleted
​Content
createdAt
date
The date/time the message was created at
​Content
updatedAt
date
The date/time the message was updated at
​Content
editedAt
date
The date/time the message was edited at
​Content
flagCount
integer
The number of users that have flagged this message
​Content
reactions
Object
The reaction data (stored as a reactionName and counter key/value pair)
Example: { like: 1, dislike: 2 }
reactionsCount
integer
The total number of reactions on this message
​Content
myReactions
Array.<string>
A list of user's reactions on this message
​Content
Messages are JSON content containers that can weigh up to 100KB and will be synchronized among all channel users in real-time. If a message requires larger binary data (such as when sending files), we recommend to upload the data to another cloud storage service, such as AWS S3, and store the URL to the content in the message data.
In addition to the JSON message type, the SDK also provides support for common text and image message types. These additional types are built on top of the standard JSON message layer.
In case of image messages, the SDK freely provides a cloud storage service that will process and store all images uploaded: you don't need to set up and manage a separate cloud storage service for this common case.
All messaging methods are contained in a AmityMessageRepository class. Before calling any messaging methods, you must ensure to first instantiate a repository instance using the AmityClient instance you created on setup.
1
let messageRepository = AmityMessageRepository(client: client)
Copied!

Sending Messages

All message sending methods are designed to be robust enough to work under any network conditions. When you send any message, that message will automatically be put into a queue in case there are unforeseen unstable network conditions. Once the SDK reconnects to the server, it will automatically resend all the queued messages.
Additionally, sent messages are always returned in message queries, even before they have been delivered to the server. This provides the user with a fluid messaging flow: when a user sends a message, that sent message will appear in the message stream right away (instead of waiting until it has been confirmed by the server). To check or display the current status of message delivery for your application, use the syncState property in the message model.
Every message sending method returns you a live object. This live object should be observed until message has been synced with the server.

Text Messages

Initiate the messaging with the following scripts, depending on your platform of choice.
1
var token: AmityNotificationToken?
2
3
...
4
5
let newMessage = messageRepository.createTextMessage(withChannelId: "channel1", text: "Hello World!")
6
7
token = newMessage.observe { [weak self] (liveMessage, error) in
8
guard let message = liveMessage.object else { return }
9
10
if message.syncState == .synced {
11
// We can stop observing once message is created on server
12
self?.token?.invalidate()
13
}
14
}
Copied!
The limit for sending text messages is 20,000 characters per text message. Messages exceeding that limit will return an error and will not be sent.

Image Message

To send an image message, you must pass in a valid URL instance instead. The SDK will resize and process the image object before sending it to the server.
You can also pass in an optional caption as part of the message, this caption will be accessible under the data property in the message model, under the caption key. You can add up to 1,000 characters of text caption per message.
1
let image = UIImage()
2
var token: AmityNotificationToken?
3
4
...
5
6
let newMessage = messageRepository.createImageMessage(withChannelId: "channel1", imageFile: image, caption: "Hello Image!", fullImage: false)
7
8
token = newMessage.observe { (message, error) in
9
// Observer block gets called when message is created
10
}
Copied!
Use the fullImage parameter to request to upload the image at its original resolution. Note that this can drastically reduce the speed of message sending, depending on the original image size. If the fullImage is set to false, then the uploaded image size will be up to 1500x1500 pixels.

File Message

To send a file message, you must pass in an URL instance of the selected file and file name for the file. The default file name is file. The SDK will check the size of the data whether it is exceeding the limit or not before sending it to the server. If the size of the data is more than the limit, the callback will return an Error object with the Dictionary of the error information that you can parse it and show an error message.
You can also pass in an optional caption as part of the message. You can add up to 1,000 characters of text caption per message.
1
let data = Data()
2
var token: AmityNotificationToken?
3
4
...
5
6
let newMessage = messageRepository.createFileMessage(withChannelId: "channel1",
7
file: fileUrl,
8
filename: "myfile.pdf",
9
caption: "some caption"
10
tags: nil,
11
parentId: nil)
12
13
14
token = newMessage.observe { (message, error) in
15
// ...
16
}
Copied!
Viewing File Message
The server will automatically generate a filename for you under the fileName key on the message data property, based on the file name that you choose to use. We advised that you handle the file size issues beforehand, as currently our limitation is set to less than 1 GB.

Audio Message

You can send small voice clips as a message.AmityMessageRepository provides createAudioMessage: a method to send voice clips to other users. It accepts voice clips as a URL instance.
1
var token: AmityNotificationToken?
2
3
...
4
5
let newMessage = messageRepository.createAudioMessage(withChannelId: "channel-id", audioFile: audioUrl, fileName: "audio-file-name.m4a", parentId: nil, tags: [])
6
7
token = newMessage.observe { (message, error) in
8
// ...
9
}
Copied!

Delete Message

You can delete a message using AmityMessageRepository. It provides deleteMessage: method to delete message with messageId.
1
messageRepository.deleteMessage(withId: "message1") { (success, error) in
2
...
3
}
4
Copied!

Custom Message

To send a custom message, you must pass in a Dictionary instance. The server will populate this as a JSON format. Later on, you must parse it by yourselves depending on the key and value that you set earlier on.
1
// Sending an integer
2
let data = ["anykey": 10]
3
4
// Sending an array
5
let data = ["anykey": [10, 14]]
6
7
// Sending a dictionary of an array of integer
8
let data = ["anykey": ["anykey": [10, 12]]]
9
10
...
11
12
let newMessage = messageRepository.createCustomMessage(withChannelId: "channel1", data: data)
13
14
token = newMessage.observe { (message, error) in
15
// ...
16
}
Copied!
Viewing Custom Message
A custom message will be sent as a JSON format. If you wish to show the message, remember to parse the value. You are responsible to parse your own custom type data.

Message Query

To query for a list of all messages in a channel:
1
let messagesCollection: AmityCollection<AmityMessage> = messageRepository.getMessages(channelId: channelId, includingTags: includingTags, excludingTags: excludingTags, filterByParentId: false, parentId: parentId, reverse: false)
2
3
self.messagesNotification = messagesCollection.observe { collection, change, error in
4
5
// refresh tableview with latest message data
6
}
Copied!
This method will return a LiveObject of all messages in the specified channel. You can observe the LiveObject in order to update your view whenever you receive new messages.

Get single message for some message

To get a specific message:
1
let messageObject = messageRepository.getMessage(messageId!)
2
3
token = messageObject?.observe({ message, error in
4
// Do something with the message
5
})
Copied!

Message First Fetch

While the SDK will always return messages in chronological order, developers can ask for the messages to be returned starting from the oldest first, or the newest first.
On a typical messaging application, the messages are fetched from the latest (newest) first, and then more older messages are explored.
1
let messagesCollection = messageRepository.getMessages(channelId: channelId, includingTags: includingTags, excludingTags: excludingTags, filterByParentId: false, parentId: parentId, reverse: true)
2
3
...
4
5
// When necessary load more (older) messages
6
messageCollection.loadPrevious()
Copied!
There are other situations where fetching the oldest messages is preferred, for example for archiving purposes or in community forums.
1
let messagesCollection = messageRepository.getMessages(channelId: channelId, includingTags: includingTags, excludingTags: excludingTags, filterByParentId: false, parentId: parentId, reverse: false)
2
3
...
4
5
// When necessary load more (newer) messages
6
messageCollection.loadNext()
Copied!

Message Filtering

Like for channels, we also have various ways to obtain messages that only match specific criteria:
    the includingTags and excludingTags parameters let you filter messages based on the tags set (or not set) in each message
    the filterByParentId and parentId parameters let you filter messages by their relationship:
      when filterByParentId is set to false, any message will match.
      when filterByParentId is set to true and no parentId is passed, query for all messages without a parent.
      when filterByParentId is set to true and a parentId is passed: query for all messages with the parentId as parent.
1
// queries for messages tagged as "games", and not tagged as "staff-only"
2
let messagesCollection = messageRepository.getMessages(channelId: "123",
3
includingTags: ["games"],
4
excludingTags: ["staff-only"],
5
filterByParentId: true,
6
parentId: nil,
7
reverse: false)
8
9
// queries for messages without a parent
10
let messagesCollection2 = messageRepository.getMessages(channelId: "123",
11
includingTags: [],
12
excludingTags: [],
13
filterByParentId: true,
14
parentId: nil,
15
reverse: false)
16
17
// queries for children messages of a message with id "999"
18
let messagesCollection3 = messageRepository.getMessages(channelId: "123",
19
includingTags: [],
20
excludingTags: [],
21
filterByParentId: true,
22
parentId: "999",
23
reverse: false)
24
25
// queries for messages without a parent and tagged as "games", and not tagged as "staff-only"
26
let messagesCollection4 = messageRepository.getMessages(channelId: "123",
27
includingTags: ["games"],
28
excludingTags: ["staff-only"],
29
filterByParentId: true,
30
parentId: nil,
31
reverse: false)
Copied!

Message Reply

When replying to a message, you can observe the message reply using AmityMessageRepository. You can observe the message with specific channelId, force the filterByParentId to true, and inject the parentId of the message. (See message filtering section)
When creating a reply, pass the parentId to the message creation.
1
// queries for messages tagged as "games", and not tagged as "staff-only"
2
let commentsCollection = messageRepository.getMessages(channelId: "123",
3
includingTags: ["games"],
4
excludingTags: ["staff-only"],
5
filterByParentId: true,
6
parentId: "999",
7
reverse: false)
8
9
// Create a message reply
10
let newMessage = messageRepository.createTextMessage(withChannelId: channelId,
11
text: text,
12
parentId: parentId)
13
token = newMessage.observe { (message, error) in
14
// ...
15
}
Copied!

Viewing Message

Text Message

The following method can return a live object of specific AmityMessage based on messageId. Since it is a live object remember to observe its changes on top of the token.
1
let messageId = "12345"
2
let messagesObject = messageRepository.getMessage(messageId)
3
4
token = messageObject?.observe { message, error in
5
// ...
6
}
Copied!

Image Message

Images in messages are not downloaded automatically when the client receives a message. In order to save bandwidth, images should be downloaded only when they're about to be displayed. A typical implementation to trigger the download would be in the tableView:cellForRowAtIndexPath: method. You can download an image using downloadImage: or downloadImageAsData: method from the AmityFileRepository class. You need to pass the String instance and the method provides the appropriate image url depending upon the sync state of message.
1
fileRepository.downloadImage(fromURL: imageUrl, size: .medium) { (imageData, error) in {
2
...
3
}
4
5
fileRepository.downloadImageAsData(fromURL: fileUrl, size: .small, completion: { (image, size, error) in
6
...
7
})
Copied!
Please use the appropriate image size depending on your use-case in order to save bandwidth.
    small is used for image thumbnails, with a maximum image size of 160 pixels per dimension. For example, this should be used for small previews in an image gallery that displays a large amount of images in a grid.
    medium is used for standard image display, with a maximum image size of 600 pixels per dimension. It's sized to take up about half of a mobile phone's screen size.
    large is used for full screen image display, with a maximum image size of 1500 pixels per dimension.
    full is used to get the original image. This size is only valid if the image is uploaded with the property fullImage set to YES. If a full sized image is not available, a large sized image will be returned instead.
The SDK do not cache image.

File/Audio Message

AmityFileRepository provides downloadFile: and downloadFileAsData: method to download the file from URL. The same method can be used to download the audio.

Messages Reaction

Reaction Query

To query for a list of all reactions on the specific message in a channel, right now to get the list of reactions is controlled by AmityReactionRepository. We can observe in the same way as observing message. The only difference is sending postId as the parameter. The operation will return an instance AmityCollection of AmityReaction that contains reaction name, message id, reactor id and reactor display name of the user and the reaction used.
1
var token: AmityNotificationToken?
2
3
...
4
5
let reactionsCollection: AmityCollection<AmityReaction> = reactionRepository.getReactions(message.messageId, referenceType: .message, reactionName: nil)
6
token = reactionsCollection.observe { collection, change, error in
7
// reload your view
8
}
Copied!
This method will return a LiveObject of all the reactions of a specific message in the specified channel. You can observe the LiveObject in order to update your view whenever you receive a new reaction on that message.

Add Reaction

For adding a new reaction, we can use AmityReactionRepository to perform its operation. Initialize the reactor with the client, don't forget to specify the reaction name as well.
1
let reactionRepository = AmityReactionRepository(client: client)
2
3
reactionRepository.addReaction(reaction: reaction, referenceId: message.messageId, referenceType: .message, completion: { success, error in ... })
Copied!

Remove Reaction

For removing a new reaction, we can use AmityReactionRepository to perform its operation. Initialize the reactor with the client and specify the reaction name.
1
let reactionRepository = AmityReactionRepository(client: client)
2
3
reactionRepository.removeReaction(reaction, referenceId: message.messageId, referenceType: .message) { success, error in ... })
Copied!

Editing Messages

A special AmityMessageEditor class is also provided for you to perform actions on messages you've sent or received. These actions include editing and deleting an existing message, as well as marking a message as being read by you.
To start, first instantiate a AmityMessageEditor instance with the AmityClient instance you created on setup, as well as a valid messageID.
1
let editor = AmityMessageEditor(client: client, messageId: "message1")
Copied!

Edit and Delete

You can only perform edit and delete operations on your own messages. Once the operation is complete, the message's editedAtDate will be set to the current time. This allows you to provide UI to the user to inform the user of specific messages that have been edited, if needed. An optional completion block can be provided to notify you of operation success.
1
// edit current message with new text
2
editor.editText("New edited text") { success, error in ...}
3
4
5
// edit custom message with new data
6
editor.editCustomMessage(data) { success, error in ...}
7
8
9
// set tags for current message
10
editor.setTags([tags]) { success, error in ...}
Copied!

Mention

The mention feature in chat can be used to call attention to specific members. By @mentioning their names, this will promptly notify them so you can get a faster response to your message.
You can mention members or a channel in text messages. Just specify the display name to mention. Up to 30 channel members can be mentioned per message.
You can fetch mention configurations and provide information about unread text messages with mention.
Mention is supported in these channel types:
    Community
    Live
Mention is not supported in the Conversation channel type.
AmityMessage contains new properties related to mention feature:
    mentionedUsers - an array of AmityUser objects. It provides information of mentioned users in the current text message.
    metadata - a dictionary which can contain any information about the current message. Our default structure to represent mentions is inside the metadata property. Customers can use any structure to represent mentions. AmityMessageMentionMetadatais a helper class which can transform metadata into mentions and vice versa. This class uses our custom structure of representing mentions in text messages.
    mentionees - an array of AmityMentionees objects. AmityMentionees model represents the following information about mentions in the message:
      type: AmityMessageMentionType. The type of the current mention. This can either be user or channel.
      users: Nullable array of AmityUser. The users list who are mentioned in the current message. If AmityMessageMentionType is channel, this array will be empty.
The mentionees array can contain a maximum of two objects because mention type can be only eitherchannel or user. Each object in the array represents information about a specific type of mention.

How mention works

You can define your own structure to represent mentions. To do this, you can use the metadata property of AmityMessage and store mentions in the metadata property. To represent mentions with our own structure, you need to use the AmityMessageMentionMetadata helper class and AmityMention class.
AmityMessageMentionMetadata has two class member functions:
Member function
Description
(nonnull NSArray<AmityMention*>*)mentionsFromMetadata:(nonnull NSDictionary<NSString *, id> *) metadata
Takes metadata dictionary as an argument and if it’s possible, then it gets mentions from metadata and returns an array of AmityMention.
(nonnull NSDictionary<NSString *, id> *)metadataFromMentions:(nonnull NSArray<AmityMention*>*) mentions
Takes an array of AmityMention and creates a metadata dictionary. This method helps to create text messages with mentions.
How to use AmityMention
To represent mention with our structure, you must use the AmityMention model array provided by AmityMessageMentionMetadata’s mentionsFromMetadata method.
AmityMention model contains four properties:
    type: AmityMessageMentionType - type of the mention
    index: Integer - start index of current mention
    userId: String - userId of the user being mentioned. If the type of the mention is channel, then userId is undefined.
    length: Integer - length of the mention
The length property doesn’t include the “@“ mention sign. For example, “@all” mention’s length is 3.
1
if let metadata = message.metadata, let text = message.data?["text"] {
2
let mentions = AmityMessageMentionMetadata.mentions(fromMetadata: metadata)
3
let attributedString = NSMutableAttributedString.init(string: text)
4
for mention in mentions {
5
let range = NSRange(location: mention.index, length: mention.length + 1)
6
if range.location != NSNotFound && range.location + range.length <= text.count {
7
attributedString.addAttribute(.foregroundColor, value: UIColor.green, range: range)
8
}
9
}
10
11
messageLabel.attributedText = attributedString
12
}
Copied!

How to create a mention with our structure

We create mentions in metadata to be able to represent mentions on different platforms(iOS, Android, and Web). Back-end won’t validate metadata property for message, but we need to provide amentionees object to Back-end to be able to handle notifications.
AmityMentioneesBuilder class helps build mentionees object. It has two methods:
    mentionChannel() - builds mention with channel
    mentionUsers(userIds: [String]) - takes an array of userIds and builds mentions with type user and userIds
To create a text message with mentions, there is a new method in AmityMessageRepository:
1
- (nonnull AmityObject<AmityMessage *>*) createTextMessageWithChannelId:(nonnull NSString *)channelId
2
text:(nonnull NSString *)text
3
tags:(nullable NSArray<NSString *> *)tags
4
parentId:(nullable NSString *)parentId
5
metadata:(nullable NSDictionary<NSString *, id> *)metadata
6
mentionees:(nonnull AmityMentioneesBuilder *)mentionees
Copied!
To update a text message and provide mention information, there is a new method in AmityMessageEditor:
1
- (void)editText:(nonnull NSString *)text
2
metadata:(nullable NSDictionary<NSString *, id> *)metadata
3
mentionees:(nonnull AmityMentioneesBuilder *)mentionees
4
completion:(AmityRequestCompletion _Nullable)completion
Copied!
Create metadata and mentionees objects. Send a text message with mentions.
1
let userMention = AmityMention(type: .user, index: 3, length: 6, userId: "userId")
2
let channelMention = AmityMention(type: .channel, index: 15, length: 3, userId: nil)
3
4
// Note: The order of the mentions is necessary.
5
let mentions = [userMention, channelMention]
6
let metadata = AmityMessageMentionMetadata.metadata(from: mentions)
7
let userIds = mentions.filter{ $0.type == .user }.compactMap { $0.userId }
8
let mentionees: AmityMentioneesBuilder = AmityMentioneesBuilder()
9
10
if !userIds.isEmpty {
11
mentionees.mentionUsers(userIds: userIds)
12
}
13
14
if mentions.contains(where: { $0.type == .channel }) {
15
mentionees.mentionChannel()
16
}
17
18
var messageRepository = AmityMessageRepository(client: self.client)
19
messageRepository.createTextMessage(channelId: "channelId", text: text, tags: nil, parentId: "parentId", metadata: metadata, mentionees: mentionees)
Copied!

How to update mentions for a text message

1
let userMention = AmityMention(type: .user, index: 3, length: 6, userId: "userId")
2
let channelMention = AmityMention(type: .channel, index: 15, length: 3, userId: nil)
3
4
// Note: The order of the mentions is necessary.
5
let mentions = [userMention, channelMention]
6
let metadata = AmityMessageMentionMetadata.metadata(from: mentions)
7
8
let userIds = mentions.filter{ $0.type == .user }.compactMap { $0.userId }
9
let mentionees: AmityMentioneesBuilder = AmityMentioneesBuilder()
10
11
if !userIds.isEmpty {
12
mentionees.mentionUsers(userIds: userIds)
13
}
14
15
if mentions.contains(where: { $0.type == .channel }) {
16
mentionees.mentionChannel()
17
}
18
19
let editor = AmityMessageEditor(client: client, messageId: message.messageId)
20
if let mentionees = mentionees {
21
editor.editText(text, metadata: metadata, mentionees: mentionees) {[weak self] success, error in
22
...
23
}
24
}
Copied!

Mention Configurations

Mention channel can be turned off. It means that you cannot mention that specific channel. To get configurations for mention channel, you must use AmityClient’s mentionConfigurations property which is the AmityMentionConfigurations object.
AmityMentionConfigurations has the isMentionAllEnabled property which is an indicator whether you can mention the channel or not.

Search members

To search members when mentioning, you must use AmityChannelParticipation’s searchMembers function:
1
- (nonnull AmityCollection<AmityChannelMember *> *)
2
searchMembers:(NSString *)displayName
3
filterBuilder:(AmityChannelMembershipFilterBuilder *)filterBuilder
4
roles:(NSArray<NSString *> *)roles
Copied!
The above function takes the following parameters to make a search:
    displayName - display name of the member being searched
    filterBuilder - to help build a filter for filtering search results
    roles
It will return AmityCollection of AmityChannelMember.
You can filter search results with more than one option. For example, filter by muted and banned users. AmityChannelMembershipFilterBuilder has this method:
1
-(void))addFilter:(AmityChannelMembershipSearchFilter)filter
Copied!
This function takes the AmityChannelMembershipSearchFilter type as an argument. Search results are ordered by last created.
AmityChannelMembershipSearchFilter is an enum which has three cases:
    member - Standard member
    mute - Muted member
    ban - Banned member
Usage:
1
let builder = AmityChannelMembershipFilterBuilder()
2
builder.add(filter: .mute)
3
builder.add(filter: .ban)
4
5
var membersCollection: AmityCollection<AmityChannelMember>?
6
membersCollection = channelPartecipation.searchMembers(displayName: key ?? "", filterBuilder: builder, roles: []).observe { [weak self] collection, _, _ in
7
....
8
}
Copied!
This code will return AmityCollection of AmityChannelMember which contains only banned and muted users.

Unread mention count

When a member is mentioned(mention type could be channel as well) in a text message and the message is not read, then hasMention property is "true" in AmityChannel class. Everytime the hasMention property is true, this means that the member has an unread message with mention(message could be created or updated as well).

Mentioning banned users

Banned users cannot be mentioned. However, admins can still find the banned users' names in the suggestion list during search when composing the message. But once the message is sent, the banned users’ information will not be included in the message payload anymore.
Banned users will not be notified nor receive any push notification.
Users who are not admins will not be able to search for the banned users' names as the latter will not appear in the suggestion list.

Mention notifications

When users are being mentioned, they will receive push notifications. You can customize the push notification content such as the title and the body using the notification setting API.
Provide the notification title and body in the titleTemplate and bodyTemplate parameters respectively. Here is a sample model:
1
{
2
"level": "network",
3
"isPushNotifiable": true,
4
"notifiableEvents": [
5
{
6
"name": "text-mention-message.created",
7
"isPushNotifiable": true,
8
"titleTemplate": "{{UserDisplayName}} mentioned you in {{ChannelName}}",
9
"bodyTemplate": "{{Message}}"
10
}
11
]
12
}
Copied!
Name
Data Type
Description
name
String
Event name
isPushNotifiable
Boolean
If set to true,push notification will be received for all text messages. If set to false, only messages with mention will have push notifications.
titleTemplate
String
Notification title
bodyTemplate
String
Notification body
Last modified 9d ago