给鸿蒙 App 增加唤起系统邮件发送能力 —— flutter_email_sender 的鸿蒙使用指南
给鸿蒙 App 增加唤起系统邮件发送能力 —— flutter_email_sender 的鸿蒙使用指南
本文配套仓库:https://atomgit.com/oh-flutter/flutter_email_sender(TAG:
10.0.1-ohos-1.0.0-beta.1,分支:feat/ohos_flutter_email_sender_10.0.1)。本文讲怎么用:引入依赖、调接口、跑通发送与附件流程;适配是怎么一步步做出来的。
一、最终运行效果
先看跑起来是什么样子。下面是示例工程的实测截图,主验证环境为 HUAWEI ADA-AL10U 真机(HarmonyOS 6.1.1.120 / API 24),Flutter 3.41.10-ohos-1.0.1。
应用启动后是标准的邮件编辑表单:收件人、主题、正文三个输入框,右上角发送按钮,底部附件区,界面上方还会按当前平台能力动态隐藏不支持的功能开关:

图一:example 启动后的主界面
点击发送按钮,插件经 startAbilityByType('mail') 拉起系统邮件应用选择面板,面板按设备上已安装并声明邮件能力的应用列出可选方式(真机实测列出了「电子邮件」):

图二:点击发送后系统弹出邮件应用选择面板
选择「电子邮件」,进入写邮件界面,收件人、主题、正文已按 Email 对象自动填充:

图三:选定邮件应用后,写邮件界面字段自动填充
点击附件按钮添加附件,附件行出现在底部,路径为应用临时目录下的文件:

图四:添加附件后附件行出现
带附件再次发送并选定邮件应用,写邮件界面顶部显示「1 个附件 (373 B)」——附件已随邮件真实传入邮件应用:

图五:带附件发送,写邮件界面列出附件
设备上没有已安装并声明邮件能力的应用时(例如未配置邮件应用的模拟器),面板会给出兜底提示「暂无可用邮件发送方式」,send() 同样正常返回——拉起链路本身仍然可用(见 FAQ Q1)。接入成本如何?往下看。
二、flutter_email_sender 是什么
flutter_email_sender 是 pub.dev 上的一个老牌 Flutter 插件(作者 sidlatau,Apache-2.0 协议,月下载量超过十三万),职责单一:把一封待发送的邮件交给系统,拉起平台原生的邮件界面。它不直接联网发信,发送动作始终由用户在系统邮件界面里确认完成,这也是各平台邮件类插件的标准做法。
上游支持 Android、iOS、macOS 与 Web,适配鸿蒙后新增 OpenHarmony / HarmonyOS 支持:插件在鸿蒙上通过 startAbilityByType('mail') 拉起系统级邮件应用选择面板,由用户选择已安装的邮件应用完成发送。
典型的使用场景:意见反馈入口、邀请好友、错误日志上报、把分享内容转成邮件发送——只要业务里出现"把一段内容发给某个邮箱",这个插件就能覆盖。
三、环境准备
本文所有实测均在以下环境完成:
| 项 | 版本 | 说明 |
|---|---|---|
| Flutter(ohos 版) | 3.41.10-ohos-1.0.1 | 主验证环境 |
| 编译 SDK | 26.0.0(26) | example 宿主工程 compatibleSdkVersion 为 6.1.0(23)(真机安装所需,见适配教程 4.1 Q5) |
| 设备 | DevEco 模拟器 + HUAWEI 真机 | 模拟器 OpenHarmony 7.0.0.105 / API 26;真机 ADA-AL10U,HarmonyOS 6.1.1.120 / API 24 |
两点提醒:
- 编译鸿蒙目标需要使用 ohos 版 Flutter SDK,普通 Flutter SDK 无法编译 ohos 产物,环境搭建见官方指南;
- 设备上需要装有邮件应用才能走完"选择应用并进入写邮件界面"的完整链路;没有邮件应用时面板提示"暂无可用邮件发送方式",拉起链路本身仍然可用(见 FAQ Q1)。本文真机装有华为邮件,完整链路已实测走通。
四、引入依赖
进入工程目录,在 pubspec.yaml 中添加 git 依赖:
dependencies:
flutter_email_sender:
git:
url: https://atomgit.com/oh-flutter/flutter_email_sender.git
# ref: 根据下方表格选择不同框架适配的 TAG 版本
ref: 10.0.1-ohos-1.0.0-beta.1
执行命令拉取依赖:
flutter pub get
TAG 命名规则:原库版本-ohos-版本号-beta.x。
| Flutter 框架版本 | TAG 名称 | 分支名 |
|---|---|---|
| 3.41 | 10.0.1-ohos-1.0.0-beta.1 | feat/ohos_flutter_email_sender_10.0.1 |
该 TAG 已在 3.41.10-ohos-1.0.1 搭配 DevEco 模拟器(OpenHarmony 7.0.0.105 / API 26)与 HUAWEI ADA-AL10U 真机(HarmonyOS 6.1.1.120 / API 24)上实测通过,真机上带附件发送已验证附件进入邮件编辑界面。不同 TAG 之间的变更详见仓库中的 CHANGELOG.OpenHarmony.md。
五、代码接入
5.1 导入库
import 'package:flutter_email_sender/flutter_email_sender.dart';
5.2 发送一封最简单的邮件
final Email email = Email(
body: '这是邮件正文',
subject: '这是邮件主题',
recipients: ['to@example.com'],
);
await FlutterEmailSender.send(email);
在鸿蒙设备上运行,系统弹出邮件应用选择面板,收件人、主题与正文自动填充,用户选择邮件应用后进入写邮件界面确认发送。整个过程一行平台判断都不需要写。
5.3 补全抄送、密送与附件
Email 是一个不可变的数据类,构造时按需填字段:
final Email email = Email(
subject: '项目周报',
body: '各位好,本周进展见附件。',
recipients: ['boss@example.com'],
cc: ['pm@example.com'],
bcc: ['archive@example.com'],
attachmentPaths: ['/data/storage/el2/base/haps/entry/cache/report.pdf'],
isHTML: false,
);
各字段说明与鸿蒙支持情况:
| 字段 | 类型 | 说明 | 鸿蒙支持 |
|---|---|---|---|
| subject | String | 主题 | 支持 |
| recipients | List<String> | 收件人列表 | 支持 |
| cc | List<String> | 抄送列表 | 支持 |
| bcc | List<String> | 密送列表 | 支持 |
| body | String | 正文 | 支持(纯文本) |
| attachmentPaths | List<String>? | 附件本地绝对路径 | 支持 |
| isHTML | bool | 正文是否按 HTML 渲染 | 不支持,传 true 会前置抛异常(见 FAQ Q3) |
附件路径要求是应用可读的本地绝对路径,比如应用沙箱内的缓存目录、临时目录下的文件。插件内部会把路径转换成系统 fileUri 并授予目标邮件应用读权限,不需要声明任何权限。沙箱外或应用无权读取的路径会导致目标应用无法打开附件。
5.4 发送前查询平台能力
getCapabilities() 返回当前平台的邮件能力,返回值 EmailCapabilities 包含 canSend、supportsCc、supportsBcc、supportsSubject、supportsPlainTextBody、supportsHtmlBody、supportsAttachments 七个布尔字段。鸿蒙平台的返回值:支持发送、收件人、抄送、密送、主题、纯文本正文与附件,不支持 HTML 正文。
推荐的姿势是让界面跟着能力走,而不是写死平台判断:
final capabilities = await FlutterEmailSender.getCapabilities();
// canSend 为 false 时禁用发送入口
if (capabilities.canSend) {
await FlutterEmailSender.send(email);
}
// 没有该能力时自动隐藏对应的功能开关
if (capabilities.supportsHtmlBody) { /* 显示 HTML 正文选项 */ }
if (capabilities.supportsAttachments) { /* 显示附件添加入口 */ }
这样同一份代码在 iOS(支持 HTML)与鸿蒙(不支持 HTML)上各自呈现合理的能力开关,上游 example 就是这个写法:鸿蒙上 HTML 开关会自动隐藏,发送按钮只在 canSend 为 true 时可用。
5.5 跨平台一套代码
send 与 getCapabilities 是仅有的两个接口,各平台行为如下:
| 平台 | send 行为 |
|---|---|
| Android | 拉起系统邮件 Intent,无邮件应用时抛 FlutterEmailSenderNotAvailableException |
| iOS | 弹出系统邮件编辑界面 |
| macOS | 弹出邮件编辑窗口 |
| Web | 打开 mailto: 链接 |
| OpenHarmony / HarmonyOS | 拉起系统邮件应用选择面板,用户选择邮件应用后进入写邮件界面 |
调用侧零分支:能力差异通过 getCapabilities() 呈现,失败语义各端对齐(无可用邮件应用一律抛 FlutterEmailSenderNotAvailableException),业务代码不需要为鸿蒙写 if-else。
5.6 实战:给应用加一个"意见反馈"入口
把前面的内容拼起来,做一个真实的业务场景:设置页里的"意见反馈"按钮,点击后预填反馈邮箱与版本信息正文,设备无邮件应用时给出兜底提示:
import 'package:flutter/material.dart';
import 'package:flutter_email_sender/flutter_email_sender.dart';
Future<void> openFeedback(BuildContext context) async {
// 先查能力,无邮件环境时直接降级,不发起无效拉起
final capabilities = await FlutterEmailSender.getCapabilities();
if (capabilities.canSend != true) {
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('当前设备没有可用的邮件应用')),
);
return;
}
final email = Email(
subject: '【应用反馈】问题反馈',
body: '请描述您遇到的问题:\n\n版本:1.0.0\n设备:',
recipients: ['support@example.com'],
);
try {
await FlutterEmailSender.send(email);
} on FlutterEmailSenderNotAvailableException {
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('未找到可用的邮件应用,请先安装')),
);
} on FlutterEmailSenderUnsupportedFeatureException {
// 邮件包含当前平台不支持的能力(如鸿蒙上的 HTML 正文)
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('当前平台不支持该邮件内容格式,请使用纯文本')),
);
}
}
在按钮的 onPressed 里调用 openFeedback(context) 即可。能力预查加异常兜底双保险后,这个入口在 Android、iOS、鸿蒙上行为一致:有邮件环境就拉起编辑界面,没有就提示降级。
六、运行与验证
以下为 example 工程的实测记录。仓库中 example 的 Bundle Name 为 com.example.demo,自建工程验证时替换为你自己的包名即可。
| 设备项 | 值 |
|---|---|
| 设备 | DevEco 模拟器;HUAWEI ADA-AL10U 真机 |
| 系统版本 | 模拟器 OpenHarmony 7.0.0.105(SP6DEVC00E999R4P11);真机 HarmonyOS 6.1.1.120 |
| API 版本 | 模拟器 26;真机 24 |
6.1 验证一:发送无附件邮件
在 example 目录构建、安装并启动:
cd packages/flutter_email_sender/example
flutter pub get
flutter build hap --debug
# 安装(产物位于 ohos/entry/build/default/outputs/default/)
hdc install ohos/entry/build/default/outputs/default/entry-default-signed.hap
# 启动
aa start -a EntryAbility -b com.example.demo
保持输入框默认内容(收件人 example@example.com、主题与正文已预填),点击右上角发送按钮:
# 观察日志中的插件与系统行为
hdc hilog | grep -iE "flutter|appselector"
预期现象:系统弹出邮件应用选择面板(有邮件应用时列出可选方式,无邮件应用时为兜底提示),SnackBar 显示 success,日志中可见系统应用选择器进程被拉起,无插件异常。对应效果见第一章图二。
6.2 验证二:带附件发送
点击右下角附件按钮。鸿蒙上 example 从打包进应用的内置资源复制一份样例附件到应用临时目录(上游的选附件库暂无鸿蒙实现,example 按 TargetPlatform.ohos 分支做了这个处理,业务工程里替换为你自己的文件路径即可),附件行出现在底部(见第一章图四)。
带附件点击发送,预期现象与验证一一致,附件经 fileUri 机制传入系统层,面板弹出、SnackBar 显示 success。真机实测:面板中选择「电子邮件」进入写邮件界面,顶部显示「1 个附件 (373 B)」,附件已随邮件带入(见第一章图五)。
七、工作原理
整个调用链路如下:
Dart: FlutterEmailSender.send(Email)
→ MethodChannelFlutterEmailSender(能力校验 validateEmail)
→ MethodChannel('flutter_email_sender') / 'send'
→ ArkTS: FlutterEmailSenderPlugin.handleSend
→ 组装 wantParam 并 context.startAbilityByType('mail', ...)
→ 系统弹出邮件应用选择面板
Dart 侧按 defaultTargetPlatform 分发:鸿蒙上命中 TargetPlatform.ohos,使用鸿蒙能力集(supportsHtmlBody: false)并在通道调用前完成能力校验,不支持的请求(如 isHTML: true)直接抛异常,不会到达原生层。
ArkTS 侧把 Email 各字段映射进 wantParam:
| wantParam 键 | 含义 | 来源 |
|---|---|---|
| sceneType | 1,标准邮件发送场景 | 固定值 |
| 收件人列表 | recipients | |
| cc / bcc | 抄送 / 密送列表 | cc / bcc |
| subject / body | 主题 / 正文 | encodeURI 编码后传入 |
| ability.params.stream | 附件 fileUri 列表 | fileUri.getUriFromPath(attachmentPaths) |
| ability.want.params.uriPermissionFlag | 授予目标应用读权限 | FLAG_AUTH_READ_URI_PERMISSION |
发起拉起并回传结果的核心代码:
// MethodResult 只允许回复一次,replyOnce 保证成功/失败路径双向互斥
let replied: boolean = false;
const replySuccess = (): void => {
if (!replied) { replied = true; result.success(null); }
};
const replyNotAvailable = (message: string): void => {
if (!replied) { replied = true; result.error('not_available', message, null); }
};
const abilityStartCallback: common.AbilityStartCallback = {
onError: (code, name, message) => { replyNotAvailable(...); },
onResult: (abilityResult) => { replySuccess(); }
};
context.startAbilityByType('mail', wantParam, abilityStartCallback, (err: BusinessError): void => {
if (err && err.code != 0) { replyNotAvailable(...); } else { replySuccess(); }
});
成功路径回 success(null)(Dart 侧 send() 正常返回),失败路径回错误码 not_available(映射为 FlutterEmailSenderNotAvailableException),错误码与上游 Android / iOS 语义对齐。
一个值得了解的机制差异:Android 的 resolveActivity 与 iOS 的 canSendMail 都能在发送前探测设备上有没有邮件应用,鸿蒙目前没有等价预查询 API,邮件面板由系统按已安装应用自动兜底。因此鸿蒙上 getCapabilities() 固定返回 canSend: true,"设备上没有邮件应用"这一情况会在拉起阶段经 onError 回 not_available 暴露。业务上对可用性敏感的,按 5.6 的双保险写法处理即可。
八、常见问题
Q1:点击发送提示「暂无可用邮件发送方式」是怎么回事?
设备上没有已安装并声明邮件能力的应用。这不是故障:拉起请求已到达系统层并正确回调(SnackBar success)。安装任意一个邮件应用并登录后再次调用,面板会列出该应用并进入写邮件界面。模拟器环境可在应用市场安装邮件类应用后复测。

Q2:send() 正常返回就代表邮件发出去了吗?
正常返回代表"系统面板拉起成功、用户已进入选择与发送流程"。最终发送由用户在选定的邮件应用里确认,插件与系统都不代替用户做这个决定。需要感知最终发送结果的场景,邮件类系统面板没有提供回执,建议在业务侧用其他方式确认。
Q3:isHTML: true 为什么直接抛 FlutterEmailSenderUnsupportedFeatureException?
鸿蒙邮件扩展面板是纯文本语义,插件在鸿蒙能力集里声明 supportsHtmlBody: false,能力校验在拉起之前拦截不支持的请求。鸿蒙上使用纯文本正文;界面上的 HTML 开关可按 supportsHtmlBody 自动隐藏(见 5.4)。
Q4:附件路径有什么要求?
应用可读的本地绝对路径,例如应用沙箱缓存目录、临时目录下的文件。插件内部经 fileUri 转换并授予目标应用读权限,无需声明权限;但应用无权读取的路径(如其他应用的沙箱文件)会导致目标邮件应用拿不到附件内容。另外路径中不能出现连续分隔符(如 cache//a.txt):系统面板的沙箱策略校验会拒绝这类路径,邮件应用会静默丢弃附件——鸿蒙上 Directory.systemTemp.path 以 / 结尾,手动拼接路径时注意先去掉尾部分隔符,根因分析见姊妹篇《flutter_email_sender 的鸿蒙适配教程》4.2 Q3。
Q5:构建或安装报错怎么排查?
编译需使用 ohos 版 Flutter SDK;安装报"此应用暂不支持在当前设备安装"通常是宿主工程 compatibleSdkVersion 高于设备 API;hap 装不上先检查签名配置与 bundleName 是否匹配。这些环境类问题的定位过程见姊妹篇《flutter_email_sender 的鸿蒙适配教程》第四章。
九、结语
回顾一下:在 pubspec.yaml 中以 git TAG 引入 flutter_email_sender,构造 Email 对象调用 FlutterEmailSender.send(),即可在鸿蒙 App 内唤起系统邮件应用选择面板,收件人、抄送、密送、主题、正文与附件自动带入;发送前可用 getCapabilities() 让界面跟着平台能力走。同一段代码在 Android、iOS、macOS 与 Web 上也各自生效,异常语义各端对齐。插件零权限、不绑定单一邮件应用,已在 DevEco 模拟器(OpenHarmony 7.0.0.105 / API 26)与 HUAWEI 真机(HarmonyOS 6.1.1.120 / API 24)完整实测,真机上带附件发送已验证附件进入邮件编辑界面。
使用中发现任何问题,欢迎到配套仓库提 Issue,也欢迎发 PR 共建。
相关链接
欢迎加入 CPF-Flutter 鸿蒙社区,社区入口、环境搭建指南和本文相关链接统一放在这里:
更多推荐




所有评论(0)