Skip to content

OperationMail 运营邮件 Skill ​

何时使用 ​

任务涉及以下内容时使用本 Skill:

  • OperationMailApi、OperationMailRecord、OperationMailQueryResult 或 OperationMailResult
  • 查询运营后台配置的全服邮件或个人邮件
  • 自建邮件 UI、红点、已读、领取状态,或选择 GameSystemUI 现成收件箱
  • 判断“运营邮件”和“玩家互发邮件”的边界
  • 排查 ServiceNotInitialized、FailedToSend、ResultParseFailed、查询为空等问题

硬规则 ​

  • OperationMail 是服务端能力;客户端不能直接查询后台邮件。
  • OperationMail 是“运营后台/系统投递给玩家”的邮件查询,不是玩家互发邮件。
  • 不要开放 SQL 字符串,也不要在游戏代码里拼 SQL。底层 Host 已经固定查询 email.admin_email 和 email.user_email。
  • 不要用 OperationMail 实现排行榜奖励发信或脚本发信;当前只做查询。
  • OperationMail 本身不保存已读、已领取、隐藏、红点等玩家状态;自建收件箱时由项目用 CloudData 保存,使用 GameSystemUI 时由其邮件系统管理。
  • 附件领取必须服务端幂等,不能只依赖客户端 UI 状态。

现成收件箱 ​

需要运营邮件入口、红点、收件箱和领取时,优先阅读 docs/systems/GameSystemUIMail.md:GameSystemUI 已提供 MailWindow.Create() / MailEntrance.Create(window),服务端默认按 Game.RuntimeEnvironment 查询 debug / test / formal 邮件,并维护 CloudData 状态;只有特殊后台配置才显式覆盖 MailSystemServer.EnvironmentName。它不支持玩家互发邮件或脚本主动发信。下方示例只适用于自行实现收件箱的项目。

推荐调用模式 ​

csharp
#if SERVER
using GameCore.OperationMail;

private static async Task<IReadOnlyList<OperationMailRecord>> QueryInboxAsync(long userId)
{
    await OperationMailApi.WaitUntilReadyAsync();

    var mapName = "my_map";
    var env = Game.RuntimeEnvironment;
    var now = DateTime.UtcNow;

    var global = await OperationMailApi.QueryGlobalOperationMailsAsync(mapName, env, now);
    if (!global.IsSuccess)
    {
        Game.Logger.LogWarning("全服运营邮件查询失败: {Result}", global.Result);
        return [];
    }

    var personal = await OperationMailApi.QueryUserOperationMailsAsync(userId, mapName, env, now);
    if (!personal.IsSuccess)
    {
        Game.Logger.LogWarning("个人运营邮件查询失败: {Result}", personal.Result);
        return global.Records;
    }

    return global.Records.Concat(personal.Records).ToArray();
}
#endif

CloudData 状态建议 ​

自建收件箱时,只把玩家相关状态放 CloudData。运营邮件正文、标题、附件原始数据仍来自 OperationMail 查询。

推荐状态 key:

  • operation_mail_read_{mailId}
  • operation_mail_claimed_{mailId}
  • operation_mail_hidden_{mailId}

如果同一项目同时处理全服邮件和个人邮件,优先用 Uuid 或带来源前缀的 key,避免 id 冲突。

排障清单 ​

  • MailSystem: load mail player data failed: TableNotFound:这是 CloudData 中的玩家邮件状态读取失败。修改项目 ID 或跨部署环境调试、且目标环境未发布过该项目时,缺表是预期状态;正常新建项目会建配套邮件表,目标环境首次发布也可自动建表。先核对项目/环境及发布记录,按授权发布后复测,详见 CloudData 缺表与首次发布。不要把缺表当成成功的空收件箱或无领取记录,也不要仅切换邮件查询 env。
  • ServiceNotInitialized:先确认是否 await OperationMailApi.WaitUntilReadyAsync(),以及服务端 Host provider 是否初始化。
  • FailedToSend:Host 拒绝或发送失败,可有限重试,不要无限循环。
  • QueryMapNameMissing / QueryEnvironmentMissing:确认传入的是发布地图名和后台环境名。
  • QueryUserIdMissing:确认使用平台 User.UserId,不是本局 Player.Id。
  • ResultParseFailed:通常是后台返回结构和解析字段不一致,需要记录原始错误并对齐后台字段。
  • 查询为空:先确认 mapName、env、state = 0、时间窗口和个人 userId。
  • 环境:优先传 Game.RuntimeEnvironment 的枚举重载,映射 Debug/Test/Formal 为后台 debug/test/formal;Unknown 会抛出参数异常。原字符串重载保留兼容,不能把部署环境 production 当作邮件 env。
  • 时区:后台邮件窗口按北京时间配置;传 DateTime.UtcNow 或服务端 DateTime.Now,查询层自动转北京墙钟。旧项目若手动 DateTime.Now.AddHours(8),升级后必须去掉。Unspecified 必须已经是北京墙钟;返回的 StartTime / EndTime 是 UTC,展示前需转换。

相关文档 ​

  • docs/systems/OperationMailSystem.md
  • docs/systems/GameSystemUIMail.md
  • docs/systems/CloudDataSystem.md
  • docs/ai/skills/cloud-data/SKILL.md