In-game mailboxes

In-game mailboxes allow game developers to communicate with their players. You can use them to tell players about in-game events, gift them useful resources, or help keep them coming back to your game.

Prerequisites

To use this sample use case, you must download and install the UGS Use Cases project in your Unity project.

Overview

When a player loads the scene for the first time, they see an inbox with a list of messages waiting for them to read. On subsequent loads, the inbox is either in the state they left it, or an updated state due to messages expiring between sessions.

Players can interact with the messages, claim their attachments, delete messages, or reset the inbox to a brand new state.

To see this use case in action:

  1. In the Unity Editor Project window, select Assets > Use Case Samples > In-Game Mailbox, and then double-click InGameMailboxSample.unity to open the sample scene.
  2. Enter Play Mode to interact with the use case.

Initialization

When the scene loads, the InGameMailboxSceneManager script performs the following initialization steps:

  1. Initializes Unity Gaming Services.

  2. Signs in the player anonymously using the Authentication service. If you’ve previously initialized any of the other sample scenes, Authentication will use your cached Player ID instead of creating a new one.

  3. Refreshes the Economy configuration data. If new Economy items were created since the last time the player opened the app, this will initialize those items in the player's configuration.

  4. Retrieves and updates currency balances from the Economy service for that authenticated user.

  5. Uses the sprite addresses stored in the Economy item configuration's custom data to load all possible currency and inventory item sprites from Addressables.

  6. Retrieves the updated message info for the player's inbox:

    1. Downloads the list of all possible messages from Remote Config.
    2. Retrieves the player's current inbox data from Cloud Save.
    3. Checks whether any of the messages already saved in the player's inbox are expired, and deletes them if so.
    4. Checks for any new messages that were downloaded from Remote Config and not yet added to the player's inbox.
    5. Saves the updated inbox state for the player in Cloud Save.
  7. Displays the updated list of inbox messages in the scene.

Functionality

The left panel in the scene displays the list of messages in the player's inbox. This list updates over time as messages expire, or based on player interaction.

Below the list, a counter displays how many messages are in the inbox and the max number of messages that can be in the inbox at any given time. When a player loads the scene for the first time, the inbox is full.

When all messages have been deleted from the inbox (either through player interaction or message expiration), a pop-up appears prompting the player to reset the inbox.

Note: This pop-up is a usability feature of the sample, and would not be an expected interaction in a real-world implementation.

Open a message

When you select a message from the list, the following occurs:

  1. The full details of the message appears on the right side of the scene.
  2. The message is marked as read, and that status is saved to Cloud Save.

If the message has an attachment, there is also an indication of which Economy items are attached, along with a button to claim them.

Claim an attachment

When you press the Claim button, the client code calls the Messages_ClaimAttachment.js Cloud Code script with the selected message's ID included as a parameter. The following occurs on the backend:

  1. The client requests the Cloud Save inbox data, and locates the message with the supplied ID.
  2. The script checks whether the message has an unclaimed attachment.
  3. Assuming the message does have an unclaimed attachment, the script makes an Economy makeVirtualPurchase call using the Virtual Purchase ID from the message.messageInfo.attachment field.
  4. If the purchase processes successfully, the message.metadata.hasUnclaimedAttachment field is set to false and saved in Cloud Save, so that the player cannot claim the attachment again.

Claim all attachments

When you press the Claim All button, the client code makes a call to the Messages_ClaimAllAttachments.js Cloud Code script. The following occurs on the backend:

  1. The client requests inbox data from Cloud Save.

  2. Cloud Save returns a list of inbox messages filtered to only show messages with message.metadata.hasUnclaimedAttachment set to true.

  3. For each message in this filtered list:

    1. The script makes an Economy makeVirtualPurchase call using the Virtual Purchase ID from the message.messageInfo.attachment field.
    2. If the purchase processes successfully, message.metadata.hasUnclaimedAttachment is set to false, and message.metadata.isRead is set to true.
  4. Once all message attachments are claimed, the updated message list is saved in Cloud Save.

Note: Saving changes in Cloud Save after each attachment is claimed would make the process more fault tolerant. However, it would also require more server calls and therefore be less efficient than the selected approach. It is up to the developer which advantage to prioritize.

Delete a message

When you press the Delete button for a message:

  1. The message is removed from the local list of inbox messages.
  2. If the inbox was previously full, such that deleting the message created space for a new message, the client rechecks the list of all possible messages downloaded from Remote Config against the message ID of the last message saved in Cloud Save to see if there are any new messages to add to the inbox. If there are, it adds as many of those messages to the inbox as will fit.
  3. The updated list of inbox messages is then saved in Cloud Save.
  4. Finally, the view refreshes to show the updated list. If the deleted message was previously selected, this also updates the UI to not display the deleted message's detail view.

Delete all read and claimed attachments

When you press the Delete Read button:

  1. The script loops through the list of inbox messages, and removes each message that has both message.metadata.isRead set to true and message.metadata.hasUnclaimedAttachment set to false. This implementation does not delete messages with unclaimed attachments, to prevent players from accidentally deleting messages with available attachments.
  2. If the inbox was previously full, such that deleting the message created space for a new message, the client rechecks the list of all possible messages downloaded from Remote Config against the message ID of the last message saved in Cloud Save to see if there are any new messages to add to the inbox. If there are, it adds as many of those messages to the inbox as will fit.
  3. The updated list of inbox messages is then saved in Cloud Save.
  4. Finally, the view refreshes to show the updated list. If the deleted message was previously selected, this also updates the UI to not display the deleted message's detail view.

Reset the inbox for a specific audience

At the bottom of the scene, you can reset the inbox while impersonating a particular audience:

  • Default
  • All Spenders
  • Unengaged Players
  • French Speakers
  • New Players

Each of the non-default audiences adds a message to the message list that is specific to that particular audience. These messages are determined by Game Overrides.

When you reset the inbox for the given audience, the following occurs:

  1. The scene resets, clearing the selected message field and deleting the Cloud Save inbox data.
  2. The client queries Remote Config with the specified audience, to retrieve the list of potential messages that includes any audience specific ones that were previously omitted.
  3. The maximum number of messages are added to the inbox from the Remote Config data, and saved in Cloud Save.
  4. The view refreshes to show the new list of messages.

Open the player inventory

When you press the inventory bag icon, the following occurs:

  1. The client calls EconomyService.Instance.PlayerInventory.GetInventoryAsync() to refresh the player's list of owned Economy inventory items.
  2. A pop-up window displays the resulting list of inventory items.

Setup

Requirements

To replicate this use case, you need the following Unity packages in your project:

PackageRole
AddressablesAllows developers to retrieve an asset by using its address. In this sample, the service looks up Economy item sprites based on the information stored in the Economy item's custom data.
AuthenticationAutomatically signs in the user anonymously to keep track of their data server-side.
Cloud CodeStores important validation logic server-side. In this use case it is used to validate that message attachments haven't already been claimed, and to process the virtual purchase that stores the attachment rewards.
Cloud SaveStores the player's inbox state, including the message info downloaded from Remote Config, and the player-specific message metadata like whether the message has been read yet.
EconomyMaintains the player's wallet and inventory, and the virtual purchase info associated with a given message's attachment.
Game OverridesDefines the audience grouping and message data for messages that you want to only send to a certain audience.
Remote ConfigProvides key-value pairs where the value that is mapped to a given key can change on the server side, either manually or based on specific Game Overrides. In this sample, we store the message info in Remote Config. Messages that should only be sent to a particular audience are stored as blank messages and are completed by Game Overrides.

Note: Although it is listed as a package and requires separate dashboard configuration, Game Overrides doesn't actually have an SDK to install from Package Manager. It is a server-side offering that affects values returned from other services.

To use these services in your game, activate each service for your Organization and project in the Unity Cloud Dashboard.

Dashboard setup

To replicate this sample scene's setup in the Unity Cloud Dashboard, you must:

  • Publish two scripts in Cloud Code.
  • Create two Currencies, two Inventory Items, and several Virtual Purchases for the Economy service.
  • Configure values and Game Overrides for the Remote Config service.

Cloud Code

Publish the following scripts in the Unity Cloud Dashboard:

ScriptParametersDescriptionLocation in project
Messages_ClaimAttachmentmessageId

The ID of the message that owns the attachment the player wants to claim.

Fetches the appropriate attachment for the given message, validates that the attachment hasn't already been claimed, calls Economy's process purchase method for that Virtual Purchase, and marks the attachment as claimed.Assets/Use Case Samples/In-Game Mailbox/Cloud Code/Messages_ClaimAttachment.js
Messages_ClaimAllAttachmentsnoneGets the list of messages in a player's inbox, finds all messages that have an unclaimed attachment, and calls Economy's process purchase method for each Virtual Purchase, marking each message as read and attachment as claimed.Assets/Use Case Samples/In-Game Mailbox/Cloud Code/Messages_ClaimAllAttachments.js

Note: The Cloud Code scripts included in the Cloud Code folder are local copies because you cannot view the sample project's dashboard. Changes to these scripts do not affect the behavior of this sample because they are not automatically uploaded to the Cloud Code service.

Economy

Configure the following resources in the Unity Cloud Dashboard:

Resource typeResource itemIDCustom DataDescription
CurrencyGemGEM
{ 
  "spriteAddress": "Sprites/Currency/Gem" 
}
A premium currency gifted by some messages.
CurrencyCoinCOIN
{ 
  "spriteAddress": "Sprites/Currency/Coin" 
}
A soft currency gifted by some messages.
Inventory ItemSwordSWORD
{ 
  "spriteAddress": "Sprites/Inventory/Sword" 
}
An inventory item gifted by some messages.
Inventory ItemShieldSHIELD
{ 
  "spriteAddress": "Sprites/Inventory/Shield" 
}
An inventory item gifted by some messages.

In addition, configure the following virtual purchases:

Item nameIDThis purchase rewardsThis purchase costs
Message 003 Gift for New PlayersMESSAGE_003_GIFT_NEW_PLAYERSSword (1), Shield (1), Coin (100)Nothing*
Message 004 Gift for Unengaged PlayersMESSAGE_004_GIFT_UNENGAGEDGem (50)Nothing*
Message 005 GiftMESSAGE_005_GIFTCoin (50)Nothing*
Message 008 GiftMESSAGE_008_GIFTCoin (100), Gem (50)Nothing*
Message 010 GiftMESSAGE_010_GIFTGem (50)Nothing*
Message 011 GiftMESSAGE_011_GIFTSword (1)Nothing*

* There is no cost associated with these Virtual Purchases because they are all gifts to the player reading the message.

Remote Config

Set up the following config values in the Unity Cloud Dashboard:

KeyTypeDescriptionValue
MESSAGES_ALLJSONThe JSON list of all possible message IDs that exist in the Unity Cloud Dashboard, listed in the order they should be downloaded in.
{
  "messageList": [
    "MESSAGE_001", 
    "MESSAGE_002", 
    "MESSAGE_003", 
    "MESSAGE_004", 
    "MESSAGE_005", 
    "MESSAGE_006", 
    "MESSAGE_007", 
    "MESSAGE_008", 
    "MESSAGE_009", 
    "MESSAGE_010", 
    "MESSAGE_011"
  ]
}
MESSAGE_001JSONOne of the messages that a player could receive.
{
  "title": "", 
  "content": "", 
  "attachment": "", 
  "expiration": "0.00:03:00.00"
}
MESSAGE_002JSONOne of the messages that a player could receive.
{
  "title": "", 
  "content": "", 
  "attachment": "", 
  "expiration": "0.00:03:00.00"
}
MESSAGE_003JSONOne of the messages that a player could receive.
{
  "title": "", 
  "content": "", 
  "attachment": "", 
  "expiration": "0.00:03:00.00"
}
MESSAGE_004JSONOne of the messages that a player could receive.
{
  "title": "", 
  "content": "", 
  "attachment": "", 
  "expiration": "0.00:03:00.00"
}
MESSAGE_005JSONOne of the messages that a player could receive.
{
  "title": "Got new Use Case sample ideas?", 
  "content": "We'd love to hear your suggestions 
  about what kind of new samples you would like 
  us to deliver. Let us know at headstart@unity3d.com", 
  "attachment": "MESSAGE_005_GIFT", 
  "expiration": "0.00:10:00.00"
}
MESSAGE_006JSONOne of the messages that a player could receive.
{
  "title": "New update coming soon", 
  "content": "Our next update will be 
  released soon and will require a client 
  update from the App Store or Google Play 
  Store. No, that's a joke. Our samples 
  are only available on Github for now ;)", 
  "attachment": "", 
  "expiration": "0.00:03:00.00"
}
MESSAGE_007JSONOne of the messages that a player could receive.
{
  "title": "There's cake at the end", 
  "content": "The cake is a lie.", 
  "attachment": "", 
  "expiration": "0.00:03:00.00"
}
MESSAGE_008JSONOne of the messages that a player could receive.
{
  "title": "The new update is here!!", 
  "content": "We hope you will enjoy the new 
  content the team has prepared for you. On 
  the menu: this brand new in-game messaging 
  sample and an updated version of the Idle 
  Clicker game sample that now showcases merging 
  and evolving inventory items.", 
  "attachment": "MESSAGE_008_GIFT", 
  "expiration": "0.00:10:00.00"
}
MESSAGE_009JSONOne of the messages that a player could receive.
{
  "title": "Where is my mind?", 
  "content": "This is a very uninspired and 
  uninspiring message. Our apologies.", 
  "attachment": "", 
  "expiration": "0.00:03:00.00"
}
MESSAGE_010JSONOne of the messages that a player could receive.
{
  "title": "Server Downtime", 
  "content": "Our servers were offline for 
  2 hours yesterday for unexpected reasons. 
  Please accept our apologies and this gift 
  as a compensation. Of course, this is only 
  for illustration purposes; UGS Use Case 
  samples are never offline!", 
  "attachment": "MESSAGE_010_GIFT", 
  "expiration": "0.00:10:00.00"
}
MESSAGE_011JSONOne of the messages that a player could receive.
{
   "title": "It's dangerous to go alone", 
   "content": "Take this!", 
   "attachment": "MESSAGE_011_GIFT", 
   "expiration": "0.00:10:00.00"
 }

Game Overrides

Configure the following Overrides in the Unity Cloud Dashboard:

DetailsName the Override “Messages All Spenders Overrides”.
Targeting

Select JEXL with the following JEXL code*:

user.audience == "AllSpenders"
ContentSelect Choose content type > Config Overrides, then enter override values for the following key:

Key:

MESSAGE_001

Value:

{
  "title": "Thank you for supporting us!", 
  "content": "This message specifically targets 
  players that spend, thanks to Game Overrides 
  that enable other Unity services to target 
  predefined or custom audiences.", 
  "attachment": "", 
  "expiration": "0.00:03:00.00"
}

SchedulingSet the following start and end dates:
  • Set Start Date to Update content immediately.
  • Set End Date to Run indefinitely.
Status

After finishing creating the Game Override, click Enable.

DetailsName the Override “Messages French Speaker Overrides”.
Targeting

Select JEXL with the following JEXL code*:

user.audience == "FrenchSpeakers"
ContentSelect Choose content type > Config Overrides, then enter override values for the following key:

Key:

MESSAGE_002

Value:

{
  "title": "Oh oui, le message!", 
  "content": "Et oui, ce message est en 
  français car il cible les joueurs dont 
  la langue du jeu est paramétrée en 
  français. Ceci est rendu possible grâce 
  à Game Overrides qui permet à certains 
  services de Unity de cibler des audiences 
  pré-définies ou personnalisées.", 
  "attachment": "", 
  "expiration": "0.00:03:00.00"
}
SchedulingSet the following start and end dates:
  • Set Start Date to Update content immediately.
  • Set End Date to Run indefinitely.
Status

After finishing creating the Game Override, click Enable.

DetailsName the Override “Messages New Players Overrides”.
Targeting

Select JEXL with the following JEXL code*:

user.audience == "NewPlayers"
ContentSelect Choose content type > Config Overrides, then enter override values for the following key:

Key:

MESSAGE_003

Value:

{
  "title": "Welcome to the game!", 
  "content": "This message specifically targets 
  new players, thanks to Game Overrides that 
  enable other Unity services to target pre-defined 
  or custom audiences.", 
  "attachment": "MESSAGE_003_GIFT_NEW_PLAYERS", 
  "expiration": "0.00:10:00.00"
}

SchedulingSet the following start and end dates:
  • Set Start Date to Update content immediately.
  • Set End Date to Run indefinitely.
Status

After finishing creating the Game Override, click Enable.

DetailsName the Override “Messages Unengaged Players Overrides”.
Targeting

Select JEXL with the following JEXL code*:

user.audience == "UnengagedPlayers"
ContentSelect Choose content type > Config Overrides, then enter override values for the following key:

Key:

MESSAGE_004

Value:

{
  "title": "Welcome back to the game!", 
  "content": "This message specifically targets 
  unengaged players, thanks to Game Overrides 
  that enable other Unity services to target 
  pre-defined or custom audiences.", 
  "attachment": "MESSAGE_004_GIFT_UNENGAGED", 
  "expiration": 0.00:10:00.00"
}

SchedulingSet the following start and end dates:
  • Set Start Date to Update content immediately.
  • Set End Date to Run indefinitely.
Status

After finishing creating the Game Override, click Enable.

* This sample determines which Game Override data to return based on a JEXL match with the audience value specified in the client. This allows the sample to simulate a player being in different audiences on-demand. In a real app, the Game Overrides would likely be set up to use built-in or custom-defined Analytics audiences for targeting. For example, during the Game Override targeting step, you could choose Stateful (Audiences) and check the appropriate Analytics audience from the list or click Build a new Audience.