# email-serverr-cli Email Server API 的 Go 客户端库。提供管理端(ServiceAuth)与发件端(AppAuth)两种客户端, 覆盖邮件发送、账号、签名、配额、通道/发信、审核、队列、健康检查等全部后端能力。 - 模块路径: `gitea.s1f.ren/shiran/email-serverr-cli` - 依赖: 仅使用标准库 `net/http`、`encoding/json`,无第三方依赖 - 风格: 对每一类资源一个文件,请求/响应类型在 `types.go`,底层请求在 `client.go` ## 安装 ```bash go get gitea.s1f.ren/shiran/email-serverr-cli ``` ```go import emailcli "gitea.s1f.ren/shiran/email-serverr-cli" ``` ## 快速开始 ### 管理客户端(ServiceAuth) ```go client := emailcli.NewServiceClient( "https://your-server.com", "your-service-token", ) accounts, err := client.ListAccounts(context.Background(), emailcli.AccountListQuery{ PaginationQuery: emailcli.PaginationQuery{Page: 1, PageSize: 20}, }) ``` ### 发件客户端(AppAuth) ```go client := emailcli.NewAppClient( "https://your-server.com", "your-app-key", "your-app-secret", ) resp, err := client.SendMail(context.Background(), emailcli.SendMailReq{ To: []string{"recipient@example.com"}, Subject: "Hello", Body: "

Hello World

", // Channel 可选:不传时优先使用账号默认通道,其次使用允许通道列表首个可用项 }) ``` ### 客户端选项 ```go client := emailcli.NewServiceClient(baseURL, token, emailcli.WithTimeout(60*time.Second), emailcli.WithHTTPClient(customClient), ) ``` ## 认证方式 | 模式 | 构造函数 | 请求头 | 适用范围 | |------|----------|--------|----------| | ServiceAuth | `NewServiceClient` | `Authorization: Bearer ` | 所有 `/api/v1` 管理接口 | | AppAuth | `NewAppClient` | `X-App-Key` + `X-App-Secret` | 仅 `POST /api/v1/mail/send` | --- ## 枚举值说明 ### Account.Status(账号状态) | 值 | 含义 | |----|------| | 0 | 禁用 | | 1 | 启用 | ### Account.AuditMode(审核模式) | 值 | 含义 | |----|------| | 0 | 免审核(直接入队) | | 1 | 自动(按规则判定) | | 2 | 人工(待审核) | ### Signature.Status(签名状态) | 值 | 含义 | |----|------| | 0 | 待审核 | | 1 | 已通过 | | 2 | 已驳回 | ### MailLog.Status(邮件状态) | 值 | 含义 | |----|------| | 0 | 待审核 | | 1 | 排队中 | | 2 | 发送中 | | 3 | 成功 | | 4 | 失败 | | 5 | 放弃 | | 6 | 驳回 | ### MailQuota.QuotaType(配额类型) | 值 | 含义 | |----|------| | 1 | 总量配额(`total` 为总发送上限) | | 2 | 周期配额(到期自动重置) | ### MailQuota.CycleUnit(配额周期单位) 当 `quota_type=2` 时生效,取值:`day`、`week`、`month`、`year` ### MailQuota.Status | 值 | 含义 | |----|------| | 0 | 禁用 | | 1 | 启用 | ### Channel.Status / SenderAccount.Status | 值 | 含义 | |----|------| | 0 | 禁用 | | 1 | 启用 | ### Channel.Strategy(发信挑选策略) | 值 | 含义 | |------|------| | `round_robin` | 轮询(默认) | | `weight` | 按 `weight` 加权随机 | | `least_used` | 今日发送数最少优先 | ### AuditRule.Action(规则动作) | 值 | 含义 | |----|------| | 1 | 自动通过 | | 2 | 自动驳回 | | 3 | 转人工 | ### AuditRule.RuleType / Target(规则类型与目标) - `RuleType` 取值: `keyword`、`regex`、`domain` - `Target` 取值: `subject`、`body`、`to`、`from` ### MailAudit.AuditType(审核来源) | 值 | 含义 | |----|------| | 1 | 自动 | | 2 | 人工 | ### MailAudit.Action(审核动作) | 值 | 含义 | |----|------| | 1 | 通过 | | 2 | 驳回 | ### SendMailReq.ContentType 取值:`text/plain`(默认)、`text/html` --- ## API 参考 所有管理接口挂载在 `/api/v1` 下。以下按功能模块分组,并给出方法签名、HTTP 路径与关键参数说明。 ### 一、发送邮件(AppAuth) #### `SendMail(ctx, req SendMailReq) -> *SendMailResp` `POST /api/v1/mail/send` `SendMailReq` 字段: | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `To` | `[]string` | 是 | 收件人列表,至少 1 个 | | `Cc` | `[]string` | 否 | 抄送 | | `Bcc` | `[]string` | 否 | 密送 | | `Subject` | `string` | 是 | 主题 | | `Body` | `string` | 是 | 正文 | | `ContentType` | `string` | 否 | 默认 `text/html` | | `Channel` | `string` | 否 | 通道 `code`。为空时按 账号默认通道 → 账号允许通道首个可用 顺序自动解析 | | `SignatureID` | `*uint` | 否 | 指定签名 ID(用户必须拥有且已审核) | | `SignatureTitle` | `string` | 否 | 按 title + user_id 选择签名;未传则使用默认签名 | | `Attachments` | `[]AttachmentItem` | 否 | 附件(`filename` + base64 `content`) | `SendMailResp` 字段: | 字段 | 类型 | 说明 | |------|------|------| | `MailLogID` | `uint` | 创建的邮件日志 ID | | `Status` | `string` | `queued` / `pending_audit` / `rejected` | --- ### 二、账号管理(ServiceAuth) #### `CreateAccount(ctx, req CreateAccountReq) -> *CreateAccountResp` `POST /api/v1/accounts` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `UserID` | `int` | 是 | 关联用户 ID | | `Name` | `string` | 是 | 账号名称(≤100) | | `AuditMode` | `*int8` | 否 | 审核模式枚举,默认 `0` | | `RateLimit` | `*int` | 否 | 频率限制(封/分钟),`0` 表示不限 | | `DefaultChannelID` | `*uint` | 否 | 默认发件通道 ID | | `AllowedChannels` | `string` | 否 | 允许发件通道 ID 列表的 JSON 字符串,例如 `"[1,2]"`。为空时视作不限制 | | `Remark` | `string` | 否 | 备注 | 返回 `CreateAccountResp`(首次创建返回明文 `AppSecret`): ```json { "id": 1, "app_key": "...", "app_secret": "只在此次展示", "name": "..." } ``` #### `ListAccounts(ctx, q AccountListQuery) -> *PaginationResult[Account]` `GET /api/v1/accounts?page=&page_size=&user_id=&status=&keyword=` | 参数 | 类型 | 说明 | |------|------|------| | `Page`/`PageSize` | `int` | 分页,`PageSize` 默认 20 | | `UserID` | `*int` | 按用户筛选 | | `Status` | `*int8` | 账号状态(0/1) | | `Keyword` | `string` | 模糊匹配 `name` 或 `remark` | #### `GetAccount(ctx, id uint) -> *Account` `GET /api/v1/accounts/{id}` #### `UpdateAccount(ctx, id uint, req UpdateAccountReq) -> *Account` `PUT /api/v1/accounts/{id}` 所有字段均为可选指针,只更新已传字段。 | 字段 | 类型 | 说明 | |------|------|------| | `Name` | `*string` | | | `Status` | `*int8` | 启用/禁用 | | `AuditMode` | `*int8` | | | `RateLimit` | `*int` | | | `DefaultChannelID` | `*uint` | 默认发件通道 | | `AllowedChannels` | `*string` | 允许发件通道 ID 列表 JSON | | `DefaultSignatureID` | `*uint` | 默认签名 ID | | `Remark` | `*string` | | #### `DeleteAccount(ctx, id uint) error` `DELETE /api/v1/accounts/{id}` #### `ResetAccountSecret(ctx, id uint) -> *ResetSecretResp` `POST /api/v1/accounts/{id}/reset-secret` 返回新生成的明文 `AppSecret`。 --- ### 三、签名管理(ServiceAuth) #### `CreateSignature(ctx, req CreateSignatureReq) -> *Signature` `POST /api/v1/signatures` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `UserID` | `int` | 是 | | | `AccountID` | `*uint` | 否 | 绑定账号,为空表示用户全局签名 | | `Title` | `string` | 是 | 中文抬头 | | `EnglishName` | `string` | 是 | 英文标识(用于组装 From 地址) | | `Content` | `string` | 否 | HTML 签名内容 | | `Applicant` | `string` | 否 | 申请人 | | `ApplicantInfo` | `string` | 否 | 申请说明 | 新建默认为 `Status=0`(待审核)。 #### `ListSignatures(ctx, q SignatureListQuery) -> *PaginationResult[Signature]` `GET /api/v1/signatures?page=&page_size=&user_id=&account_id=&status=&keyword=` #### `GetSignature(ctx, id uint)` / `UpdateSignature` / `DeleteSignature` `GET|PUT|DELETE /api/v1/signatures/{id}` #### `AuditSignature(ctx, id uint, req AuditSignatureReq)` `POST /api/v1/signatures/{id}/audit` | 字段 | 类型 | 说明 | |------|------|------| | `Action` | `int8` | `1`=通过,`2`=驳回 | | `RejectReason` | `string` | 驳回时建议填写 | | `Auditor` | `string` | 审核人标识 | --- ### 四、邮件日志(ServiceAuth) #### `ListMailLogs(ctx, q MailLogListQuery) -> *PaginationResult[MailLog]` `GET /api/v1/mail-logs` | 参数 | 类型 | 说明 | |------|------|------| | `UserID` | `*int` | | | `AccountID` | `*uint` | | | `Status` | `*int8` | 见 `MailLog.Status` 枚举 | | `StartDate`/`EndDate` | `string` | `YYYY-MM-DD` | | `To` | `string` | 收件人精确匹配 | | `Keyword` | `string` | 模糊匹配主题或收件人 | #### `GetMailLog(ctx, id uint) -> *MailLogDetail` `GET /api/v1/mail-logs/{id}` 返回 `MailLog` + 正文 `Body`。 #### `GetMailStats(ctx) -> []MailStatItem` `GET /api/v1/mail-logs/stats` 按 `Status` 分组的邮件数统计。 --- ### 五、配额管理(ServiceAuth) #### `CreateQuota(ctx, req CreateQuotaReq) -> *MailQuota` `POST /api/v1/quotas` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `UserID` | `int` | 是 | | | `AccountID` | `uint` | 是 | | | `QuotaType` | `int8` | 是 | `1`=总量,`2`=周期 | | `Total` | `int` | 是 | 额度上限 | | `ExpireAt` | `string` | 否 | `YYYY-MM-DD HH:mm:ss` | | `CycleUnit` | `string` | 否 | `day` / `week` / `month` / `year` | | `CycleResetAt` | `string` | 否 | 周期起始时间 | #### `ListQuotas(ctx, q QuotaListQuery)` · `GetQuotaSummary(ctx, accountID)` · `UpdateQuota(ctx, id, req)` · `DeleteQuota(ctx, id)` `UpdateQuotaReq` 支持局部更新 `Total`、`Status`、`ExpireAt`、`CycleUnit`、`CycleResetAt`。 `QuotaListQuery` 过滤参数:`UserID`、`AccountID`、`QuotaType`、`Status`。 --- ### 六、通道与发信账号(ServiceAuth) #### 通道 `Channel` - `CreateChannel(ctx, req CreateChannelReq) -> *Channel` — `POST /api/v1/channels` - `ListChannels(ctx, q ChannelListQuery) -> *PaginationResult[Channel]` — `GET /api/v1/channels` - `UpdateChannel(ctx, id, req UpdateChannelReq) -> *Channel` — `PUT /api/v1/channels/{id}` - `DeleteChannel(ctx, id uint) error` — `DELETE /api/v1/channels/{id}` `CreateChannelReq` 字段: | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `Code` | `string` | 是 | 唯一标识(发送邮件用) | | `Name` | `string` | 是 | | | `Description` | `string` | 否 | | | `Strategy` | `string` | 否 | 见 `Channel.Strategy` 枚举 | #### 发信账号 `SenderAccount` - `CreateSender(ctx, channelID uint, req CreateSenderReq) -> *SenderAccount` — `POST /api/v1/channels/{channelID}/senders` - `ListSendersByChannel(ctx, channelID, q SenderListQuery) -> *PaginationResult[SenderAccount]` — `GET /api/v1/channels/{channelID}/senders` - `UpdateSender(ctx, id uint, req UpdateSenderReq) -> *SenderAccount` — `PUT /api/v1/senders/{id}` - `DeleteSender(ctx, id uint) error` — `DELETE /api/v1/senders/{id}` `CreateSenderReq` 关键字段:`Name`、`SmtpHost`、`SmtpPort`、`SmtpUser`、`SmtpPassword`、`SmtpSSL`、`FromName`、`FromAddress`、`DailyLimit`、`Weight`。 --- ### 七、审核(ServiceAuth) - `ListAuditPending(ctx, q AuditPendingQuery) -> *PaginationResult[MailLog]` — `GET /api/v1/audits/pending` - `GetAuditPendingDetail(ctx, id uint) -> *MailLogDetail` — `GET /api/v1/audits/pending/{id}` - `ApproveAudit(ctx, id uint) error` — `POST /api/v1/audits/{id}/approve` - `RejectAudit(ctx, id uint, req AuditRejectReq) error` — `POST /api/v1/audits/{id}/reject` - `BatchApproveAudit(ctx, req BatchAuditApproveReq) error` — `POST /api/v1/audits/batch/approve` - `BatchRejectAudit(ctx, req BatchAuditRejectReq) error` — `POST /api/v1/audits/batch/reject` - `ListAuditLogs(ctx, q AuditLogQuery) -> *PaginationResult[MailAudit]` — `GET /api/v1/audits/logs` - `GetAuditStats(ctx) -> *AuditStats` — `GET /api/v1/audits/stats` `AuditPendingQuery` 过滤:`AccountID`、`UserID`、`Keyword`。 `AuditLogQuery` 过滤:`AccountID`、`UserID`、`Action`、`AuditType`、`StartDate`、`EndDate`。 --- ### 八、审核规则(ServiceAuth) - `CreateAuditRule(ctx, req CreateAuditRuleReq) -> *AuditRule` — `POST /api/v1/audit-rules` - `ListAuditRules(ctx) -> []AuditRule` — `GET /api/v1/audit-rules` - `GetAuditRule(ctx, id uint) -> *AuditRule` — `GET /api/v1/audit-rules/{id}` - `UpdateAuditRule(ctx, id uint, req UpdateAuditRuleReq) -> *AuditRule` — `PUT /api/v1/audit-rules/{id}` - `DeleteAuditRule(ctx, id uint) error` — `DELETE /api/v1/audit-rules/{id}` - `UpdateAuditRuleStatus(ctx, id uint, status int8) -> *AuditRule` — `PUT /api/v1/audit-rules/{id}/status` - `TestAuditRule(ctx, req TestAuditRuleReq) -> *TestAuditRuleResp` — `POST /api/v1/audit-rules/test` `CreateAuditRuleReq` 字段: | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `Name` | `string` | 是 | | | `RuleType` | `string` | 是 | 见 `AuditRule.RuleType` 枚举 | | `Target` | `string` | 是 | 见 `AuditRule.Target` 枚举 | | `Condition` | `string` | 是 | 关键词或正则表达式 | | `Action` | `int8` | 是 | 见 `AuditRule.Action` 枚举 | | `Priority` | `int` | 否 | 数字越大优先级越高 | | `Remark` | `string` | 否 | | --- ### 九、队列(ServiceAuth) - `GetQueueStatus(ctx) -> *QueueStatusData` — `GET /api/v1/queue/status` - `ListQueuePending(ctx, q QueuePendingQuery) -> *PaginationResult[MailLog]` — `GET /api/v1/queue/pending` - `CancelQueueItem(ctx, mailLogID uint) error` — `POST /api/v1/queue/{mailLogID}/cancel` - `RetryQueueItem(ctx, mailLogID uint) error` — `POST /api/v1/queue/{mailLogID}/retry` `QueueStatusData` 返回各通道队列长度与延迟队列长度: ```go type QueueStatusData struct { Queues map[string]int64 `json:"queues"` // key = channel code DelayQueue int64 `json:"delay_queue"` } ``` --- ### 十、健康检查(ServiceAuth) - `ListCheckLogs(ctx, q CheckLogQuery) -> *PaginationResult[CheckLog]` — `GET /api/v1/check-logs` - `GetCheckSummary(ctx) -> []SenderHealth` — `GET /api/v1/check-logs/summary` - `TriggerCheck(ctx, senderAccountID uint) -> *TriggerCheckResp` — `POST /api/v1/check-logs/trigger/{senderAccountID}` `CheckLogQuery` 过滤:`SenderAccountID`、`Received`、`StartDate`、`EndDate`。 --- ## 统一返回结构 后端所有接口统一使用: ```json { "code": 200, "message": "ok", "data": { /* 业务数据 */ } } ``` SDK 内部会解包 `data` 并在非 200 时返回 `*APIError`: ```go resp, err := client.SendMail(ctx, req) if err != nil { var apiErr *emailcli.APIError if errors.As(err, &apiErr) { fmt.Printf("API error: code=%d message=%s\n", apiErr.Code, apiErr.Message) } } ``` 分页接口统一包装: ```go type PaginationResult[T any] struct { List []T `json:"list"` Total int64 `json:"total"` Page int `json:"page"` PageSize int `json:"page_size"` } ``` ## License MIT