Appearance
Implementing In-Game Mail
This guide introduces a basic implementation of custom in-game mails in your game based on Metaplay SDK's example code.
Appearance
This guide introduces a basic implementation of custom in-game mails in your game based on Metaplay SDK's example code.
MetaPlayerRewards implemented - If you have not implemented MetaPlayerRewards in your game yet, have a look at Implementing MetaRewards.In-Game Mail refers to messages and resources you can send players through the LiveOps Dashboard.
The Metaplay SDK Mail system consists of PlayerMailItems, representing the Mail containers, and MetaInGameMails, representing Mail contents. The base container implementation keeps track of whether the mail has been consumed or read, and the contents consist of localized title and body strings, and a list of rewards to be given to the receiving player.

Both the container and contents can be extended or overridden with code. This way, you can give some Mails special characteristics, like being able to be consumed multiple times. You can also keep more than one implementation of each and choose what type of mail to send on a case-by-case basis, using the dashboard.
Each Mail has a Consume method that you can call using the PlayerConsumeMail player action, which is part of the core PlayerModel.
For most games, you will probably want to implement a "minimum viable" version of sending and consuming In-Game Mails first. This basic implementation lets you understand how Mails work in Metaplay and immediately use them to gift resources to players using the LiveOps Dashboard.
After this implementation, you will be able to:
Metaplay's default PlayerModel ships with an inbox that can hold PlayerMailItems. You can test that it works (and that your custom MetaPlayerRewards are implemented correctly) by accessing a player in the LiveOps Dashboard and using the "Send Mail" action.



Now that you can create Mails with the payload you want, you need to be able to consume them on the client. The easiest way to do so is to add a small piece of code in your game's update loop that checks if any Mails are available and calls the PlayerConsumeMail accordingly.
Here's an example implementation:
// If there's any Mail...
if (MetaplayClient.PlayerModel.MailInbox.Count > 0)
{
var mailItem = MetaplayClient.PlayerModel.MailInbox[0]; // Pick the first one
switch (mailItem.Contents) // Check the Mail contents type
{
// Sanity checking that we know what the Mail contents is and
// thus know what to do with it.
case SimplePlayerMail mail:
// Log the Mail title to the console
Debug.Log("Received mail with title: " + mail.Title.Localize());
// Consume and delete the Mail with actions
MetaplayClient.PlayerContext.ExecuteAction(new PlayerConsumeMail(mail.Id));
MetaplayClient.PlayerContext.ExecuteAction(new PlayerDeleteMail(mail.Id));
break;
default:
// Unknown Mail type -> log errors?
break;
}
}This code would immediately attempt to consume any Mails as soon as they become available. Good enough for, say, a soft launch where you just need a quick way to give resources to players!
A small but high bang-for-buck addition would be to implement a custom Mail screen in your game to display the Mail before the player claims it.
Looking at the above code snippet, we could replace the ExecuteAction() call with your own OpenMailWindow(mail) or a similar function to display the Mail and a 'claim' button to consume it accordingly.
The SimplePlayerMail content type included with Metaplay SDK implements the message title and body as LocalizedString, a special string container with multiple localizations of a string. Mails that arrive on the client are, by default, already localized on the server so that the client can call LocalizedString.Localize() to turn it into a string.
string mailTitle = mail.Title.Localize();
string mailBody = mail.Body.Localize();🚨 Be Careful!
Just using the Localize() method will return a string in the game's default language, not necessarily the player's. To have it return the player's selected language, use Localize(MetaplayClient.PlayerModel.Language.LanguageId).
The PlayerMailItem Mail wrapper tracks if a Mail has been read and if it has been consumed. Instead of consuming and deleting individual Mails as they arrive, you could implement a fully-featured inbox GUI that lists the current Mails in the inbox and uses the provided PlayerConsumeMail and PlayerToggleMailIsRead actions for modifying the state of Mail items in the inbox.
Metaplay has a default implementation of the abstract MetaInGameMail class called SimplePlayerMail that contains
Title localized string.Body localized string.MetaPlayerRewardBase objects as attachments.This implementation is a good starting point for simple communication needs, but providing your own Mail content format is also fully supported. The recommended way of doing this is to introduce a class that derives from MetaInGameMail as part of your game logic code.
Add a class to the project anywhere under Assets/SharedCode/. In this example, we'll create an even simpler Mail format that has just a string field:
[MetaSerializableDerived(2)] // Inform the serialization system about this class
[MetaReservedMembers(0, 100)] // Reserve tagIds 0..99 for this
public class MyPlayerMail : MetaInGameMail
{
[MetaValidateRequired]
[MetaMember(1)] public string MyMailString { get; private set; }
public override string Description => MyMailString; // Description to show in the dashboard
[MetaDeserializationConstructor]
public MyPlayerMail(string myMailString) { MyMailString = myMailString; }
}See Deep Dive: Data Serialization for a description of the serialization attributes used here. The [MetaValidateRequired] attribute is used in the dashboard to tell the form that this field is required before the Mail can be sent.
To add rewards to your Mail, you can override the base class's ConsumableRewards property, an IEnumerable of type MetaPlayerRewardBase. Adding your rewards like this makes it so that when the player consumes the Mail, its ConsumableRewards are consumed automatically. Take a look at Implementing MetaRewards if you need a refresher on implementing your custom reward types. In this Mail content type, we also make the rewards optional by overriding the MustBeConsumed property and returning false.
[MetaSerializableDerived(3)] // Inform the serialization system about this class
[MetaReservedMembers(0, 100)] // Reserve tagIds 0..99 for this
public class MyPlayerMailWithRewards : MetaInGameMail
{
[MetaValidateRequired]
[MetaMember(1)] public string MyMailString { get; private set; }
[MetaMember(2)] public List<MetaPlayerRewardBase> Rewards { get; private set; }
public override string Description => MyMailString; // Description to show in the dashboard
public override IEnumerable<MetaPlayerRewardBase> ConsumableRewards => Rewards; // Return rewards list
public override bool MustBeConsumed => false; // Make the rewards optional
// An empty or deserialization constructor (see Deep Dive: Data Serialization for more info) is needed
public MyPlayerMailWithRewards() { }
}Open the LiveOps Dashboard and click on the “Send Mail” button to bring up the Mail form. Fill in the fields and click “Send” to send off your Mail.

After sending, ensure that the new Mail appears correctly in the player’s inbox.

If you don’t like the look of the automatically generated form or the inbox item, you can override both with custom dashboard components. Learn more about the generated forms system at Working with Generated Dashboard UI.
A player Mail consists of the Mail contents, deriving from MetaInGameMail, which is contained inside an envelope, deriving from PlayerMailItem. In addition, the wrapper includes information about whether the Mail has been read or consumed, as well as MetaTime fields recording when those actions have happened. Most customization can be done by introducing new content types, but sometimes one might want to customize the Mail wrapper itself.
Add a class to the project anywhere under Assets/SharedCode/. Let’s make a Mail item type with rewards that a player can consume multiple times.
[MetaSerializableDerived(1)]
[MetaReservedMembers(0, 100)]
public sealed class MyCustomMailItem : PlayerMailItem
{
public new MyPlayerMailWithRewards Contents => (MyPlayerMailWithRewards) base.Contents;
[MetaMember(1)] public int TimesConsumed;
public override void ConsumeRewards(IPlayerModelBase player)
{
foreach (MetaPlayerRewardBase consumable in Contents.ConsumableRewards)
consumable.InvokeConsume(player, rewardSourceProvider.DeclareMailRewardSource(Contents));
TimesConsumed++;
if (TimesConsumed >= Contents.MaxConsumedAmount)
{
HasBeenConsumed = true;
ConsumedAt = player.CurrentTime;
}
}
public MyCustomMailItem() { }
public MyCustomMailItem(MyPlayerMailWithRewards contents, MetaTime sentAt) : base(contents, sentAt) { }
}In this Mail type, we’ve added a new TimesConsumed property, which keeps track of how many times the Mail has been consumed. In addition, we’ve specified the content type to be MyPlayerMailWithRewards and added a new integer property called MaxConsumedAmount.
To create our new MyCustomMailItems, we need to override the default Mail creation logic. This can be done by overriding the InGameMailIntegration class and creating our Mail type in the overridden MakePlayerMailItem method.
public class MyInGameMailIntegration : InGameMailIntegration
{
public override PlayerMailItem MakePlayerMailItem(MetaInGameMail contents, MetaTime sentAt)
{
if (contents is MyPlayerMailWithRewards rewardsContent)
return new MyCustomMailItem(rewardsContent, sentAt); // return our custom Mail item for our own contents type
// otherwise use default implementation
return base.MakePlayerMailItem(contents, sentAt);
}
}Next, we can add custom handling for the custom Mail format in the update loop:
// If there's any Mail...
if (MetaplayClient.PlayerModel.MailInbox.Count > 0)
{
var mailItem = MetaplayClient.PlayerModel.MailInbox[0]; // Pick the first one
if(mailItem is MyCustomMailItem customMailItem)
{
// Do something cool here...
// Delete Mail
MetaplayClient.PlayerContext.ExecuteAction(new PlayerDeleteMail(customMailItem.Id));
}
else // mailItem is DefaultPlayerMailItem
{
switch (mailItem.Contents) // Check the Mail contents type
{
// Sanity checking that we know what the Mail contents is and
// thus know what to do with it.
case SimplePlayerMail simpleMail:
// Log the Mail title to the console
Debug.Log("Received mail with title: {Title}", simpleMail.Title.Localize());
// Consume and delete the Mail with actions
MetaplayClient.PlayerContext.ExecuteAction(new PlayerConsumeMail(simpleMail.Id));
MetaplayClient.PlayerContext.ExecuteAction(new PlayerDeleteMail(simpleMail.Id));
break;
case MyPlayerMail myMail:
// Log my string and delete
Debug.Log("Received my mail with string: {Content}", myMail.MyMailString);
MetaplayClient.PlayerContext.ExecuteAction(new PlayerDeleteMail(myMail.Id));
default:
// Unknown Mail type -> log errors?
break;
}
}
}And we’re done!
You can find information on how to use the broadcasts system to share content through Mail on Managing Broadcasts.
If you’re interested in customizing the generated dashboard forms and views, take a look at Working with Generated Dashboard UI.