跳到主要内容

游戏内邮箱

1. 概述

邮件服务帮助开发者通过 PGOS 网页门户或虚拟服务器异步向玩家发送带有附件的游戏内邮件。游戏客户端可以提取邮件、标记邮件、删除邮件以及领取邮件中的附件。

邮件服务可应用于以下场景:

  1. 系统通知:开发者可以通过发送邮件向玩家更新通知。
  2. 活动事件:开发者可以配置成就事件触发器,以向玩家分发奖励。
  3. 礼物(暂不支持):玩家可以购买游戏内物品,并通过发送邮件附件的方式将其作为礼物赠送给其他玩家。

2. 关键概念

邮件服务有三个关键概念:

  1. 邮箱:每个玩家在PGOS注册后都会拥有一个邮箱。
  2. 邮件:可以向玩家发送邮件并实时通知。邮件将在后端保存3个月。在有效期内,收件人可以检索和管理邮件。
  3. 附件:每封邮件可以附加任意数量的物品和货币。玩家可以通过领取附件来获取这些物品和货币。
提示

所有邮件默认在后台保留90天。过期邮件将无法查看或操作。如需修改保留期限,请联系我们。

3. 发送邮件

3.1 在网页端发送邮件

点击“参与”菜单,您将看到以下页面。

image-20240918111342995

字段说明:

  • 收件人:邮件的接收者(一次最多可选择 1000 位玩家);
  • 指定玩家:将此邮件发送给指定数量的玩家
  • 全局:将此邮件发送给所有玩家
  • 类别:邮件信息的类别,例如:系统、通知、活动、奖励、礼物、自定义;
  • 自定义数据:邮件信息的自定义数据。您可以定义一些数据并将其传递给 SDK;
  • 标题和内容:邮件的内容。您可以定义多种语言的标题和内容信息;
  • 附件:邮件中的附件。您可以从经济部分选择游戏内物品货币

image-20240918111426625

在弹出的对话框中选择邮件收件人。您也可以通过搜索玩家ID来添加收件人。

image-20220620151658197

发送给所有玩家

如果选择全局范围,则必须指定过期时间。

image-20240918111529415

提示

当接收范围设置为“全局”时,所有玩家都会收到邮件。由于在线玩家数量可能较大,接收“全局”邮件可能会延迟 1-2 分钟。离线玩家登录游戏后,邮件将立即送达。

全局邮件只会发送给在截止日期前登录或保持在线的玩家。截止日期过后,已收到全局邮件的玩家将保留邮件,但在此时间之后登录的玩家将不再收到邮件。

如何添加标题和内容

点击“标题和内容”底部的“+ 添加语言”按钮,并在弹出的对话框中输入邮件内容。

image-20220620152341853

  • 语言:PGOS 默认提供大部分语言类型,您也可以添加语言类型:
  • 标题:邮件主题,最大长度为 1024 个字符;
  • 内容:邮件内容,最大长度为 4096 个字符;
提示

在内容中,您可以使用占位符 {{receiver_name}} 来表示接收者的玩家名称。例如:

您好 {{receiver_name}},邮件内容……

您添加的第一种语言将被设置为默认语言,您可以更改它。

image-20220620155056054

如何添加附件

点击“附件”底部的“+ 添加附件”按钮,并在弹出的对话框中选择邮件附件。

image-20220620155236599

选择邮件关联的游戏内物品货币后,您可以设置其金额。附件金额不能小于 1,也不能大于 32 位无符号整数(4,294,967,295)。

image-20220620155353719

发送邮件

确认所有信息正确无误后,点击底部的发送按钮发送邮件。

从邮件模板导入

您可以点击“从邮件模板导入”快速导入邮件模板并填写数据。如何创建邮件模板,请点击这里

image-20220620155823979

3.2 通过虚拟服务器发送邮件

Virtual Server可以利用下表列出的 HTTP API 来实现发送和撤回邮件等功能。

接口Description
SendMail向玩家发送邮件。在请求中指定收件人、多语言内容和附件。
SendGlobalMail向所有玩家发送全球邮件。
SendMailWithTemplate使用模板发送邮件。请先在门户网站上配置模板。
SendGlobalMailWithTemplate向所有玩家发送带有模板的全球邮件。
GetPlayerMails获取玩家邮件,该接口支持分页显示。
GetDeletedPlayerMails获取玩家已删除的邮件(已删除的邮件将保留3个月)。
RecoverDeletedMails恢复指定玩家已删除的邮件。
RetractGlobalMail撤回之前发送的全局邮件。

4. 邮件模版

切换到顶部标签页至邮件模板,所有邮件模板将会显示出来。

image-20220620161931224

创建邮件模板

点击“+ 添加邮件模板”按钮,如下图所示:

image-20220620161556672

删除邮件模板

选择要删除的邮件模板,点击底部的删除邮件模板按钮,并在弹出的对话框中点击提交即可删除。

image-20220620162712305

修改/克隆邮件模板

选择邮件模板,然后点击编辑或克隆按钮。

image-20220629155936935

5. 邮件环境变量

您可以使用邮件变量来自定义发送邮件的内容。PGOS 提供了许多预定义的环境变量:

  • {{receiver_name}}。将被替换为邮件接收方玩家的显示名称。
  • {{goal_name}}。此变量可用于 PGOS 目标服务发送的邮件,并将替换为触发该邮件的玩家目标的名称。
  • {{cur_tier_order}}。此变量可用于 PGOS 目标服务发送的邮件,并将替换为超级目标的当前等级。

所有可用变量均可在门户的“参与度 > 邮件 > 邮件环境变量”中查看。

此外,还可以在发送邮件时添加和使用邮件环境变量。

image-20230620173353274

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 无效,邮件将抛出错误。
kBackendDefaultLanguageNotExistSendMail邮件将使用玩家信息中定义的本地语言回复邮件。当本地语言缺失时,邮件将使用默认语言。因此,发件人必须确保邮件文本映射中包含 default_language 键。
kBackendIllegalCategoryEnumSendMail发件人在发送邮件时必须指定有效的类别枚举。“MAIL_CATEGORY_ALL”在批处理接口中具有特殊含义,因此无法使用。
kBackendMailTitleContainProfanityWords
kBackendMailContentContainProfanityWords
SendMail发件人必须确保邮件标题及内容不含有敏感词。
kBackendGetMailCountBeyondLimitGetMailList获取邮件数量不能为零或超过 100
kBackendGrantItemNumExceedClaimMailAttachmentById ClaimMailAttachmentByCategory赠予物品数量超出限制
kBackendCheckIdempotentKeyFailedSendMail存在幂等密钥重复的问题。