游戏内邮箱
1. 概述
邮件服务帮助开发者通过 PGOS 网页门户或虚拟服务器异步向玩家发送带有附件的游戏内邮件。游戏客户端可以提取邮件、标记邮件、删除邮件以及领取邮件中的附件。
邮件服务可应用于以下场景:
- 系统通知:开发者可以通过发送邮件向玩家更新通知。
- 活动事件:开发者可以配置成就事件触发器,以向玩家分发奖励。
- 礼物(暂不支持):玩家可以购买游戏内物品,并通过发送邮件附件的方式将其作为礼物赠送给其他玩家。
2. 关键概念
邮件服务有三个关键概念:
- 邮箱:每个玩家在PGOS注册后都会拥有一个邮箱。
- 邮件:可以向玩家发送邮件并实时通知。邮件将在后端保存3个月。在有效期内,收件人可以检索和管理邮件。
- 附件:每封邮件可以附加任意数量的物品和货币。玩家可以通过领取附件来获取这些物品和货币。
所有邮件默认在后台保留90天。过期邮件将无法查看或操作。如需修改保留期限,请联系我们。
3. 发送邮件
3.1 在网页端发送邮件
点击“参与”菜单,您将看到以下页面。
字段说明:
- 收件人:邮件的接收者(一次最多可选择 1000 位玩家);
- 指定玩家:将此邮件发送给指定数量的玩家
- 全局:将此邮件发送给所有玩家
- 类别:邮件信息的类别,例如:系统、通知、活动、奖励、礼物、自定义;
- 自定义数据:邮件信息的自定义数据。您可以定义一些数据并将其传递给 SDK;
- 标题和内容:邮件的内容。您可以定义多种语言的标题和内容信息;
- 附件:邮件中的附件。您可以从经济部分选择游戏内物品和货币。
在弹出的对话框中选择邮件收件人。您也可以通过搜索玩家ID来添加收件人。
发送给所有玩家
如果选择全局范围,则必须指定过期时间。
当接收范围设置为“全局”时,所有玩家都会收到邮件。由于在线玩家数量可能较大,接收“全局”邮件可能会延迟 1-2 分钟。离线玩家登录游戏后,邮件将立即送达。
全局邮件只会发送给在截止日期前登录或保持在线的玩家。截止日期过后,已收到全局邮件的玩家将保留邮件,但在此时间之后登录的玩家将不再收到邮件。
如何添加标题和内容
点击“标题和内容”底部的“+ 添加语言”按钮,并在弹出的对话框中输入邮件内容。
- 语言:PGOS 默认提供大部分语言类型,您也可以添加语言类型:
- 标题:邮件主题,最大长度为 1024 个字符;
- 内容:邮件内容,最大长度为 4096 个字符;
在内容中,您可以使用占位符 {{receiver_name}} 来表示接收者的玩家名称。例如:
您好 {{receiver_name}},邮件内容……
您添加的第一种语言将被设置为默认语言,您可以更改它。
如何添加附件
点击“附件”底部的“+ 添加附件”按钮,并在弹出的对话框中选择邮件附件。
选择邮件关联的游戏内物品和货币后,您可以设置其金额。附件金额不能小于 1,也不能大于 32 位无符号整数(4,294,967,295)。
发送邮件
确认所有信息正确无误后,点击底部的发送按钮发送邮件。
从邮件模板导入
您可以点击“从邮件模板导入”快速导入邮件模板并填写数据。如何创建邮件模板,请点击这里。
3.2 通过虚拟服务器发送邮件
Virtual Server可以利用下表列出的 HTTP API 来实现发送和撤回邮件等功能。
接口 | Description |
---|---|
SendMail | 向玩家发送邮件。在请求中指定收件人、多语言内容和附件。 |
SendGlobalMail | 向所有玩家发送全球邮件。 |
SendMailWithTemplate | 使用模板发送邮件。请先在门户网站上配置模板。 |
SendGlobalMailWithTemplate | 向所有玩家发送带有模板的全球邮件。 |
GetPlayerMails | 获取玩家邮件,该接口支持分页显示。 |
GetDeletedPlayerMails | 获取玩家已删除的邮件(已删除的邮件将保留3个月)。 |
RecoverDeletedMails | 恢复指定玩家已删除的邮件。 |
RetractGlobalMail | 撤回之前发送的全局邮件。 |
4. 邮件模版
切换到顶部标签页至邮件模板,所有邮件模板将会显示出来。
创建邮件模板
点击“+ 添加邮件模板”按钮,如下图所示:
删除邮件模板
选择要删除的邮件模板,点击底部的删除邮件模板按钮,并在弹出的对话框中点击提交即可删除。
修改/克隆邮件模板
选择邮件模板,然后点击编辑或克隆按钮。
5. 邮件环境变量
您可以使用邮件变量来自定义发送邮件的内容。PGOS 提供了许多预定义的环境变量:
{{receiver_name}}
。将被替换为邮件接收方玩家的显示名称。{{goal_name}}
。此变量可用于 PGOS 目标服务发送的邮件,并将替换为触发该邮件的玩家目标的名称。{{cur_tier_order}}
。此变量可用于 PGOS 目标服务发送的邮件,并将替换为超级目标的当前等级。
所有可用变量均可在门户的“参与度 > 邮件 > 邮件环境变量”中查看。
此外,还可以在发送邮件时添加和使用邮件环境变量。
6. 管理邮件
本章主要介绍客户端 API。
注意:
发送邮件时,邮件标题和内容可以配置多种语言,其中一种语言为默认语言。玩家收到的邮件语言类型根据其自身设置的语言决定:
- 如果邮件的多种语言中包含玩家使用的语言,则返回与玩家语言对应的邮件。
- 如果邮件的多种语言中没有玩家使用的语言,则返回与默认语言对应的邮件。
开发者可以通过调用 UPgosPlayerProfileAPI:SetMyLanguage API 来更新玩家的语言。
6.1 数据结构
FClientMailInfo 用于描述一封邮件。FClientMailInfo 中的关键字段如下:
- mail_id:邮件的唯一标识符。
- title:邮件的标题。
- category:邮件的类别。
- System:系统邮件。
- Notice:游戏通知邮件。
- Campaign:游戏活动邮件。
- Award:用于向玩家发放奖励。
- Gift:玩家可以购买游戏内物品,并以邮件附件的形式赠送给其他玩家。
- Custom:自定义类别,如果以上类别不满足,开发者可以使用自定义类别来表示。
- content:邮件内容。
- attachment:邮件附件,包含游戏内物品和货币。
- custom_data:邮件自定义数据。
- is_read:邮件是否已读。
- sent_time:邮件发送时间。
FClientMailAttachmentInfo 用于描述邮件附件。FClientMailAttachmentInfo 中的关键字段如下:
- items:等待领取的游戏内物品数组。
- currencies:等待领取的货币数组。
- is_gift_from_player:表示附件是否由其他玩家赠送。
- giver:如果
is_gift_from_player
为 true,则giver
表示赠送礼物的玩家。 - is_claimed:表示附件是否已被领取。
6.2 获取邮件列表
玩家可以通过调用 UPgosMailAPI::GetMailList
API 分页获取自己的邮件列表。获取邮件时可以指定邮件类别,EClientMailCategory::All
代表所有邮件类别。
接口原型:
/**
* Get mail list by category, category 'All' represents all categories.
*
* @param Params Request parameters for get mail list.
*/
void GetMailList(
const FClientGetMailListParams& Params,
TFunction<void(const FPgosResult& Ret, const FClientGetMailListResult* Data)> Callback) const;
/** Request parameters for get mail list. */
struct FClientGetMailListParams {
/** Query the specified category. */
MailCategory category;
/** The starting position of the list. */
uint32_t offset = 0;
/** The query count. */
uint32_t count = 0;
};
Example Code:
#include "PgosSDKCpp.h"
void SomeUObjectClass::SomeFunction()
{
auto Mail = IPgosSDKCpp::Get().GetClientMailAPI();
if (Mail)
{
FClientGetMailListParams Params;
// Fill get mail list params
Mail->GetMailList(Params, [](const FPgosResult& Ret, const FClientGetMailListResult* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("GetMailList Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("GetMailList Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
6.3 将邮件标记为已读
玩家可以通过调用 UPgosMailAPI::MarkMailAsReadById
/UPgosMailAPI::MarkMailAsReadByCategory
API 将邮件标记为已读。这两个 API 分别表示通过 ID 和类别标记邮件状态。
在 UPgosMailAPI::MarkMailAsReadByCategory
API 中,EClientMailCategory::All
表示所有类别。
接口原型:
/**
* Mark mail as read by id.
*
* @param MailIds Mail IDs.
*/
void MarkMailAsReadById(
const TArray<FString>& MailIds,
TFunction<void(const FPgosResult& Ret)> Callback) const;
/**
* Mark mail as read by category.
*
* @param Category Mail category, category 'All' represents all categories.
*/
void MarkMailAsReadByCategory(
EClientMailCategory Category,
TFunction<void(const FPgosResult& Ret)> Callback) const;
Example Code:
#include "PgosSDKCpp.h"
void SomeUObjectClass::SomeFunction()
{
auto Mail = IPgosSDKCpp::Get().GetClientMailAPI();
if (Mail)
{
TArray<FString> MailIds;
Mail->MarkMailAsReadById(MailIds, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("MarkMailAsReadById Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("MarkMailAsReadById Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
void SomeUObjectClass::SomeFunction1()
{
auto Mail = IPgosSDKCpp::Get().GetClientMailAPI();
if (Mail)
{
EClientMailCategory Category;
// specify mail category
Mail->MarkMailAsReadByCategory(Category, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("MarkMailAsReadByCategory Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("MarkMailAsReadByCategory Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
6.4 领取邮件附件
玩家可以通过调用 UPgosMailAPI::ClaimMailAttachmentById
/UPgosMailAPI::ClaimMailAttachmentByCategory
API 领取邮件附件。成功领取附件后,玩家可以在物品栏中找到相应的物品实例,并在余额中找到相应的货币。
在 UPgosMailAPI::ClaimMailAttachmentByCategory
API 中,EClientMailCategory::All
代表所有类别。
成功领取附件后,未读邮件将自动标记为已读。
返回的结果保存在 FClientClaimMailAttachmentResult
结构体中:
struct FClientClaimMailAttachmentResult
{
/** The item instances obtained after claim attachments. */
FItemInstPack item_insts;
/** The currencies obtained after claim attachments. */
TArray<FCurrency> currencies;
/** Unique items that already in inventory cause failed to be granted. */
TArray<FUnclaimedItem> conflict_unique_items;
/** Mails that failed to claim attachments, key: mail ID, value: failed reason. */
TMap<FString, FString> fails;
};
接口原型:
/**
* Claim mail attachments by mail ID.
*
* @param MailIds Mail IDs.
*/
void ClaimMailAttachmentById(
const TArray<FString>& MailIds,
TFunction<void(const FPgosResult& Ret, const FClientClaimMailAttachmentResult* Data)> Callback) const;
/**
* Claim mail attachments by category.
*
* @param Category Mail category, category 'All' represents all categories.
*/
void ClaimMailAttachmentByCategory(
EClientMailCategory Category,
TFunction<void(const FPgosResult& Ret, const FClientClaimMailAttachmentResult* Data)> Callback) const;
Example Code:
#include "PgosSDKCpp.h"
void SomeUObjectClass::SomeFunction()
{
auto Mail = IPgosSDKCpp::Get().GetClientMailAPI();
if (Mail)
{
TArray<FString> MailIds;
Mail->ClaimMailAttachmentById(MailIds, [](const FPgosResult& Ret, const FClientClaimMailAttachmentResult* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("ClaimMailAttachmentById Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("ClaimMailAttachmentById Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
void SomeUObjectClass::SomeFunction1()
{
auto Mail = IPgosSDKCpp::Get().GetClientMailAPI();
if (Mail)
{
EClientMailCategory Category;
// specify mail category
Mail->ClaimMailAttachmentByCategory(Category, [](const FPgosResult& Ret, const FClientClaimMailAttachmentResult* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("ClaimMailAttachmentByCategory Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("ClaimMailAttachmentByCategory Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
6.5 删除邮件
玩家可以通过调用 UPgosMailAPI::DeleteMailById
/UPgosMailAPI::DeleteReadMailByCategory
API 删除邮件。这两个 API 分别表示按 ID 和类别删除邮件。需要注意的是,UPgosMailAPI::DeleteReadMailByCategory
是按类别删除已读邮件,而 UPgosMailAPI::DeleteMailById
不区分已读和未读。
在 UPgosMailAPI::ClaimMailAttachmentByCategory
API 中,EClientMailCategory::All
代表所有类别。
接口原型:
/**
* Delete mail by mail ID.
*
* @param MailIds Mail IDs.
*/
void DeleteMailById(
const TArray<FString>& MailIds,
TFunction<void(const FPgosResult& Ret)> Callback) const;
/**
* Delete read mails by category.
*
* @param Category Mail category, category 'All' represents all categories.
*/
void DeleteReadMailByCategory(
EClientMailCategory Category,
TFunction<void(const FPgosResult& Ret)> Callback) const;
Example Code:
#include "PgosSDKCpp.h"
void SomeUObjectClass::SomeFunction()
{
auto Mail = IPgosSDKCpp::Get().GetClientMailAPI();
if (Mail)
{
TArray<FString> MailIds;
Mail->DeleteMailById(MailIds, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("DeleteMailById Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("DeleteMailById Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
void SomeUObjectClass::SomeFunction()
{
auto Mail = IPgosSDKCpp::Get().GetClientMailAPI();
if (Mail)
{
EClientMailCategory Category;
Mail->DeleteReadMailByCategory(Category, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("DeleteReadMailByCategory Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("DeleteReadMailByCategory Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
6.6 获取未读邮件数量
玩家可以通过调用 UPgosMailAPI::GetMailBadgeNum
API 获取各类别的未读邮件数量以及总未读邮件数量。
接口原型:
/**
* Get the count of unread mails.
*/
void GetMailBadgeNum(TFunction<void(const FPgosResult& Ret, const FClientGetMailBadgeNumResult* Data)> Callback) const;
Example Code:
#include "PgosSDKCpp.h"
void SomeUObjectClass::SomeFunction()
{
auto Mail = IPgosSDKCpp::Get().GetClientMailAPI();
if (Mail)
{
Mail->GetMailBadgeNum([](const FPgosResult& Ret, const FClientGetMailBadgeNumResult* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("GetMailBadgeNum Successfully"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("GetMailBadgeNum Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
6.7 监控邮件事件
使用邮件时会触发以下事件:
- OnBadgeNumOfMail:登录后立即触发,之后未读邮件数量发生变化时也会触发。事件结果保存在
FClientBadgeNumOfMailEvt
中。 - OnReceiveNewMail:新邮件到达时会实时触发此事件,需要注意的是,离线接收的邮件不会触发此事件。事件结果保存在
FClientReceiveNewMailEvt
中。
struct FClientBadgeNumOfMailEvt
{
/** Total unread count. */
int32 total_badge_num = 0;
};
struct FClientReceiveNewMailEvt
{
/** New mail. */
FClientMailInfo mail;
};
7. 关键错误处理
错误码 | 相关API | 处理建议 |
---|---|---|
kBackendReceiverIDIllegal kBackendReceiverEmpty | SendMail | 发件人必须至少指定一个有效的 PlayerID 才能成功发送邮件。如果其中一个 PlayerID 无效,邮件将抛出错误。 |
kBackendDefaultLanguageNotExist | SendMail | 邮件将使用玩家信息中定义的本地语言回复邮件。当本地语言缺失时,邮件将使用默认语言。因此,发件人必须确保邮件文本映射中包含 default_language 键。 |
kBackendIllegalCategoryEnum | SendMail | 发件人在发送邮件时必须指定有效的类别枚举。“MAIL_CATEGORY_ALL”在批处理接口中具有特殊含义,因此无法使用。 |
kBackendMailTitleContainProfanityWords kBackendMailContentContainProfanityWords | SendMail | 发件人必须确保邮件标题及内容不含有敏感词。 |
kBackendGetMailCountBeyondLimit | GetMailList | 获取邮件数量不能为零或超过 100 |
kBackendGrantItemNumExceed | ClaimMailAttachmentById ClaimMailAttachmentByCategory | 赠予物品数量超出限制 |
kBackendCheckIdempotentKeyFailed | SendMail | 存在幂等密钥重复的问题。 |