diff --git a/src/SKIT.FlurlHttpClient.Wechat.Work/Extensions/WechatWorkClientExecuteCgibinExmailExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.Work/Extensions/WechatWorkClientExecuteCgibinExmailExtensions.cs
index d3ac0ec2..bb18ce1c 100644
--- a/src/SKIT.FlurlHttpClient.Wechat.Work/Extensions/WechatWorkClientExecuteCgibinExmailExtensions.cs
+++ b/src/SKIT.FlurlHttpClient.Wechat.Work/Extensions/WechatWorkClientExecuteCgibinExmailExtensions.cs
@@ -9,6 +9,30 @@ namespace SKIT.FlurlHttpClient.Wechat.Work
{
public static class WechatWorkClientExecuteCgibinExmailExtensions
{
+ #region App
+ ///
+ /// 异步调用 [POST] /cgi-bin/exmail/app/compose_send 接口。
+ /// REF: https://developer.work.weixin.qq.com/document/path/97445
+ /// REF: https://developer.work.weixin.qq.com/document/path/97854
+ /// REF: https://developer.work.weixin.qq.com/document/path/97855
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static async Task ExecuteCgibinExmailAppComposeSendAsync(this WechatWorkClient client, Models.CgibinExmailAppComposeSendRequest request, CancellationToken cancellationToken = default)
+ {
+ if (client is null) throw new ArgumentNullException(nameof(client));
+ if (request is null) throw new ArgumentNullException(nameof(request));
+
+ IFlurlRequest flurlReq = client
+ .CreateRequest(request, HttpMethod.Post, "cgi-bin", "exmail", "app", "compose_send")
+ .SetQueryParam("access_token", request.AccessToken);
+
+ return await client.SendRequestWithJsonAsync(flurlReq, data: request, cancellationToken: cancellationToken);
+ }
+ #endregion
+
#region Group
///
/// 异步调用 [POST] /cgi-bin/exmail/group/create 接口。
diff --git a/src/SKIT.FlurlHttpClient.Wechat.Work/Models/CgibinExmail/App/CgibinExmailAppComposeSendRequest.cs b/src/SKIT.FlurlHttpClient.Wechat.Work/Models/CgibinExmail/App/CgibinExmailAppComposeSendRequest.cs
new file mode 100644
index 00000000..c287e4f6
--- /dev/null
+++ b/src/SKIT.FlurlHttpClient.Wechat.Work/Models/CgibinExmail/App/CgibinExmailAppComposeSendRequest.cs
@@ -0,0 +1,297 @@
+using System.Collections.Generic;
+
+namespace SKIT.FlurlHttpClient.Wechat.Work.Models
+{
+ ///
+ /// 表示 [POST] /cgi-bin/exmail/app/compose_send 接口的请求。
+ ///
+ public class CgibinExmailAppComposeSendRequest : WechatWorkRequest
+ {
+ public static class Types
+ {
+ public class Recipient
+ {
+ ///
+ /// 获取或设置邮箱地址列表。
+ ///
+ [Newtonsoft.Json.JsonProperty("emails")]
+ [System.Text.Json.Serialization.JsonPropertyName("emails")]
+ public IList? EmailList { get; set; }
+
+ ///
+ /// 获取或设置企业成员的 UserId 列表。
+ ///
+ [Newtonsoft.Json.JsonProperty("userids")]
+ [System.Text.Json.Serialization.JsonPropertyName("userids")]
+ public IList? UserIdList { get; set; }
+ }
+
+ public class Attachment
+ {
+ ///
+ /// 获取或设置内容。
+ ///
+ [Newtonsoft.Json.JsonProperty("file_name")]
+ [System.Text.Json.Serialization.JsonPropertyName("file_name")]
+ public string FileName { get; set; } = string.Empty;
+
+ ///
+ /// 获取或设置经过 Base64 编码的文件内容。
+ ///
+ [Newtonsoft.Json.JsonProperty("content")]
+ [System.Text.Json.Serialization.JsonPropertyName("content")]
+ public string EncodingContent { get; set; } = string.Empty;
+ }
+
+ public class Schedule
+ {
+ public static class Types
+ {
+ public class Reminder : CgibinOAScheduleAddRequest.Types.Schedule.Types.Reminder
+ {
+ ///
+ /// 获取或设置日程开始前多久提醒(单位:分钟)。
+ ///
+ [Newtonsoft.Json.JsonProperty("remind_before_event_mins")]
+ [System.Text.Json.Serialization.JsonPropertyName("remind_before_event_mins")]
+ public new int? RemindBeforeEventTime { get; set; }
+
+ ///
+ /// 获取或设置每月哪几周重复。
+ ///
+ [Newtonsoft.Json.JsonProperty("repeat_week_of_month")]
+ [System.Text.Json.Serialization.JsonPropertyName("repeat_week_of_month")]
+ public IList? RepeatWeekOfMonthList { get; set; }
+
+ ///
+ /// 获取或设置每年哪几月重复。
+ ///
+ [Newtonsoft.Json.JsonProperty("repeat_month_of_year")]
+ [System.Text.Json.Serialization.JsonPropertyName("repeat_month_of_year")]
+ public IList? RepeatMonthOfYearList { get; set; }
+ }
+ }
+
+ ///
+ /// 获取或设置日程 ID。
+ ///
+ [Newtonsoft.Json.JsonProperty("schedule_id")]
+ [System.Text.Json.Serialization.JsonPropertyName("schedule_id")]
+ public string? ScheduleId { get; set; }
+
+ ///
+ /// 获取或设置日程方法。
+ ///
+ [Newtonsoft.Json.JsonProperty("method")]
+ [System.Text.Json.Serialization.JsonPropertyName("method")]
+ public string? Method { get; set; }
+
+ ///
+ /// 获取或设置日程开始时间戳。
+ ///
+ [Newtonsoft.Json.JsonProperty("start_time")]
+ [System.Text.Json.Serialization.JsonPropertyName("start_time")]
+ public long StartTimestamp { get; set; }
+
+ ///
+ /// 获取或设置日程结束时间戳。
+ ///
+ [Newtonsoft.Json.JsonProperty("end_time")]
+ [System.Text.Json.Serialization.JsonPropertyName("end_time")]
+ public long EndTimestamp { get; set; }
+
+ ///
+ /// 获取或设置日程地点。
+ ///
+ [Newtonsoft.Json.JsonProperty("location")]
+ [System.Text.Json.Serialization.JsonPropertyName("location")]
+ public string? Location { get; set; }
+
+ ///
+ /// 获取或设置日程提醒信息。
+ ///
+ [Newtonsoft.Json.JsonProperty("reminders")]
+ [System.Text.Json.Serialization.JsonPropertyName("reminders")]
+ public Types.Reminder? Reminder { get; set; }
+ }
+
+ public class Meeting
+ {
+ public static class Types
+ {
+ public class Option
+ {
+ ///
+ /// 获取或设置入会密码。
+ ///
+ [Newtonsoft.Json.JsonProperty("password")]
+ [System.Text.Json.Serialization.JsonPropertyName("password")]
+ public string? Password { get; set; }
+
+ ///
+ /// 获取或设置自动录制模式。
+ ///
+ [Newtonsoft.Json.JsonProperty("auto_record")]
+ [System.Text.Json.Serialization.JsonPropertyName("auto_record")]
+ public int? AutoRecordMode { get; set; }
+
+ ///
+ /// 获取或设置是否开启等候室。
+ ///
+ [Newtonsoft.Json.JsonProperty("enable_waiting_room")]
+ [System.Text.Json.Serialization.JsonPropertyName("enable_waiting_room")]
+ public bool? EnableWaitingRoom { get; set; }
+
+ ///
+ /// 获取或设置是否允许成员在主持人进会前加入。
+ ///
+ [Newtonsoft.Json.JsonProperty("allow_enter_before_host")]
+ [System.Text.Json.Serialization.JsonPropertyName("allow_enter_before_host")]
+ public bool? AllowEnterBeforeHost { get; set; }
+
+ ///
+ /// 获取或设置限制成员入会模式。
+ ///
+ [Newtonsoft.Json.JsonProperty("enter_restraint")]
+ [System.Text.Json.Serialization.JsonPropertyName("enter_restraint")]
+ public int? EnterRestraintMode { get; set; }
+
+ ///
+ /// 获取或设置是否开启屏幕水印。
+ ///
+ [Newtonsoft.Json.JsonProperty("enable_screen_watermark")]
+ [System.Text.Json.Serialization.JsonPropertyName("enable_screen_watermark")]
+ public bool? EnableScreenWatermark { get; set; }
+
+ ///
+ /// 获取或设置成员入会时是否静音。
+ ///
+ [Newtonsoft.Json.JsonProperty("enable_enter_mute")]
+ [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.NumericalNullableBooleanConverter))]
+ [System.Text.Json.Serialization.JsonPropertyName("enable_enter_mute")]
+ [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.NumericalNullableBooleanConverter))]
+ public bool? EnableEnterMute { get; set; }
+
+ ///
+ /// 获取或设置会议开始提醒范围。
+ ///
+ [Newtonsoft.Json.JsonProperty("remind_scope")]
+ [System.Text.Json.Serialization.JsonPropertyName("remind_scope")]
+ public int? RemindScope { get; set; }
+
+ ///
+ /// 获取或设置水印类型。
+ ///
+ [Newtonsoft.Json.JsonProperty("water_mark_type")]
+ [System.Text.Json.Serialization.JsonPropertyName("water_mark_type")]
+ public int? WaterMarkType { get; set; }
+ }
+
+ public class Host
+ {
+ ///
+ /// 获取或设置企业成员的 UserId 列表。
+ ///
+ [Newtonsoft.Json.JsonProperty("userids")]
+ [System.Text.Json.Serialization.JsonPropertyName("userids")]
+ public IList? UserIdList { get; set; }
+ }
+
+ public class Administrator
+ {
+ ///
+ /// 获取或设置企业成员的 UserId 列表。
+ ///
+ [Newtonsoft.Json.JsonProperty("userids")]
+ [System.Text.Json.Serialization.JsonPropertyName("userids")]
+ public IList? UserIdList { get; set; }
+ }
+ }
+
+ ///
+ /// 获取或设置会议设置信息。
+ ///
+ [Newtonsoft.Json.JsonProperty("option")]
+ [System.Text.Json.Serialization.JsonPropertyName("option")]
+ public Types.Option? Option { get; set; }
+
+ ///
+ /// 获取或设置会议主持人信息。
+ ///
+ [Newtonsoft.Json.JsonProperty("hosts")]
+ [System.Text.Json.Serialization.JsonPropertyName("hosts")]
+ public Types.Host? Host { get; set; }
+
+ ///
+ /// 获取或设置会议管理员信息。
+ ///
+ [Newtonsoft.Json.JsonProperty("meeting_admins")]
+ [System.Text.Json.Serialization.JsonPropertyName("meeting_admins")]
+ public Types.Administrator Administrator { get; set; } = new Types.Administrator();
+ }
+ }
+
+ ///
+ /// 获取或设置收件人信息。
+ ///
+ [Newtonsoft.Json.JsonProperty("to")]
+ [System.Text.Json.Serialization.JsonPropertyName("to")]
+ public Types.Recipient To { get; set; } = new Types.Recipient();
+
+ ///
+ /// 获取或设置抄送信息。
+ ///
+ [Newtonsoft.Json.JsonProperty("cc")]
+ [System.Text.Json.Serialization.JsonPropertyName("cc")]
+ public Types.Recipient? CC { get; set; }
+
+ ///
+ /// 获取或设置密送信息。
+ ///
+ [Newtonsoft.Json.JsonProperty("bcc")]
+ [System.Text.Json.Serialization.JsonPropertyName("bcc")]
+ public Types.Recipient? BCC { get; set; }
+
+ ///
+ /// 获取或设置标题。
+ ///
+ [Newtonsoft.Json.JsonProperty("subject")]
+ [System.Text.Json.Serialization.JsonPropertyName("subject")]
+ public string Subject { get; set; } = string.Empty;
+
+ ///
+ /// 获取或设置内容。
+ ///
+ [Newtonsoft.Json.JsonProperty("content")]
+ [System.Text.Json.Serialization.JsonPropertyName("content")]
+ public string Content { get; set; } = string.Empty;
+
+ ///
+ /// 获取或设置内容类型。
+ ///
+ [Newtonsoft.Json.JsonProperty("content_type")]
+ [System.Text.Json.Serialization.JsonPropertyName("content_type")]
+ public string? ContentType { get; set; }
+
+ ///
+ /// 获取或设置附件列表。
+ ///
+ [Newtonsoft.Json.JsonProperty("attachment_list")]
+ [System.Text.Json.Serialization.JsonPropertyName("attachment_list")]
+ public IList? AttachmentList { get; set; }
+
+ ///
+ /// 获取或设置日程信息。
+ ///
+ [Newtonsoft.Json.JsonProperty("schedule")]
+ [System.Text.Json.Serialization.JsonPropertyName("schedule")]
+ public Types.Schedule? Schedule { get; set; }
+
+ ///
+ /// 获取或设置会议信息。
+ ///
+ [Newtonsoft.Json.JsonProperty("meeting")]
+ [System.Text.Json.Serialization.JsonPropertyName("meeting")]
+ public Types.Meeting? Meeting { get; set; }
+ }
+}
diff --git a/src/SKIT.FlurlHttpClient.Wechat.Work/Models/CgibinExmail/App/CgibinExmailAppComposeSendResponse.cs b/src/SKIT.FlurlHttpClient.Wechat.Work/Models/CgibinExmail/App/CgibinExmailAppComposeSendResponse.cs
new file mode 100644
index 00000000..251127c5
--- /dev/null
+++ b/src/SKIT.FlurlHttpClient.Wechat.Work/Models/CgibinExmail/App/CgibinExmailAppComposeSendResponse.cs
@@ -0,0 +1,9 @@
+namespace SKIT.FlurlHttpClient.Wechat.Work.Models
+{
+ ///
+ /// 表示 [POST] /cgi-bin/exmail/app/compose_send 接口的响应。
+ ///
+ public class CgibinExmailAppComposeSendResponse : WechatWorkResponse
+ {
+ }
+}
diff --git a/test/SKIT.FlurlHttpClient.Wechat.Work.UnitTests/ModelSamples/CgibinExmail/App/CgibinExmailAppComposeSendRequest.json b/test/SKIT.FlurlHttpClient.Wechat.Work.UnitTests/ModelSamples/CgibinExmail/App/CgibinExmailAppComposeSendRequest.json
new file mode 100644
index 00000000..753de72b
--- /dev/null
+++ b/test/SKIT.FlurlHttpClient.Wechat.Work.UnitTests/ModelSamples/CgibinExmail/App/CgibinExmailAppComposeSendRequest.json
@@ -0,0 +1,61 @@
+{
+ "to": {
+ "emails": ["word@bgjkcsqy.wecom.work", "jyan1@tiyantest.wang"],
+ "userids": ["william"]
+ },
+ "cc": {
+ "emails": [],
+ "userids": ["panyy"]
+ },
+ "bcc": {
+ "emails": ["zoro@bgjkcsqy.wecom.work"],
+ "userids": []
+ },
+ "subject": "这是标题",
+ "content": "这是邮件正文",
+ "attachment_list": [
+ {
+ "file_name": "a.txt",
+ "content": "BASE64_CONTENT"
+ }
+ ],
+ "schedule": {
+ "method": "request",
+ "location": "这是地点",
+ "start_time": 1669278600,
+ "end_time": 1669282200,
+ "reminders": {
+ "is_remind": 1,
+ "remind_before_event_mins": 15,
+ "is_repeat": 1,
+ "is_custom_repeat": 1,
+ "timezone": 8,
+ "repeat_interval": 1,
+ "repeat_type": 1,
+ "repeat_day_of_week": [4, 5],
+ "repeat_day_of_month": [],
+ "repeat_week_of_month": [],
+ "repeat_month_of_year": [],
+ "repeat_until": 1670745600
+ }
+ },
+ "meeting": {
+ "option": {
+ "password": "123456",
+ "auto_record": 2,
+ "enable_waiting_room": true,
+ "allow_enter_before_host": true,
+ "enter_restraint": 0,
+ "enable_screen_watermark": true,
+ "enable_enter_mute": 0,
+ "remind_scope": 2,
+ "water_mark_type": 0
+ },
+ "hosts": {
+ "userids": ["zoro"]
+ },
+ "meeting_admins": {
+ "userids": ["zoro"]
+ }
+ }
+}