You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Studio(Kotlin)实现定时邮件发送msg.sentDate不生效如何解决

你设置的msg.sentDate不生效是因为这个属性仅作用于邮件头部的发件时间展示,不会控制邮件的实际发送时机,SMTP协议本身没有延迟发送的能力,只要你调用Transport.send(msg)接口,服务器就会立刻发送邮件。你需要先实现Android端的定时触发逻辑,到指定时间再调用发信接口。

实现方案

一、使用WorkManager实现定时触发(推荐)

WorkManager是Jetpack官方推出的异步任务调度组件,适配从API 14到最新版本的Android系统,支持任务持久化,即使用户重启设备或者应用退出,任务依然可以按预期执行,是后台定时任务的首选方案。

1. 引入依赖

在模块级build.gradle中添加WorkManager依赖:

dependencies {
    def work_version = "2.8.1"
    implementation "androidx.work:work-runtime-ktx:$work_version"
}

2. 定义发邮件Worker

创建继承自CoroutineWorker的任务类,在doWork方法中执行发信逻辑:

class SendEmailWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {
    override suspend fun doWork(): Result {
        // 从输入参数中获取邮件相关配置
        val smtpServer = inputData.getString("smtp_server") ?: return Result.failure()
        val smtpPort = inputData.getInt("smtp_port", 587)
        val fromEmail = inputData.getString("from_email") ?: return Result.failure()
        val fromPwd = inputData.getString("from_pwd") ?: return Result.failure()
        val toEmail = inputData.getString("to_email") ?: return Result.failure()
        val subject = inputData.getString("subject") ?: return Result.failure()
        val content = inputData.getString("content") ?: return Result.failure()

        return try {
            // 初始化BouncyCastle,避免SSL异常
            Security.addProvider(BouncyCastleProvider())
            val emailService = EmailService(smtpServer, smtpPort)
            val email = EmailService.Email(
                auth = EmailService.UserPassAuthenticator(fromEmail, fromPwd),
                toList = listOf(InternetAddress(toEmail)),
                from = InternetAddress(fromEmail),
                subject = subject,
                body = content
            )
            // IO线程执行发信
            withContext(Dispatchers.IO) {
                emailService.send(email)
            }
            Result.success()
        } catch (e: Exception) {
            e.printStackTrace()
            // 失败后重试3次
            if (runAttemptCount < 3) Result.retry()
            else Result.failure()
        }
    }
}

3. 调度定时任务

用户点击提交按钮时,计算目标时间与当前时间的时间差,构造任务并入队:

// 假设用户选择的目标时间戳为targetTimeMillis(毫秒级)
val delayMillis = targetTimeMillis - System.currentTimeMillis()
if (delayMillis <= 0) {
    // 目标时间已过,直接发送
    Toast.makeText(this, "目标时间已过,将立即发送邮件", Toast.LENGTH_SHORT).show()
}

// 构造输入参数
val inputData = workDataOf(
    "smtp_server" to "smtp.qq.com", // 替换为你的SMTP服务器地址,比如QQ邮箱是smtp.qq.com,网易是smtp.163.com
    "smtp_port" to 587, // SMTP端口,TLS一般是587,SSL是465
    "from_email" to "你的发件邮箱",
    "from_pwd" to "你的发件邮箱授权码", // 注意不是邮箱密码,是SMTP授权码
    "to_email" to 用户输入的收件邮箱,
    "subject" to 用户输入的邮件主题,
    "content" to 用户输入的邮件内容
)

// 构造一次性延时任务
val sendRequest = OneTimeWorkRequestBuilder<SendEmailWorker>()
    .setInitialDelay(delayMillis, TimeUnit.MILLISECONDS)
    .setInputData(inputData)
    .setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.MINUTES) // 失败重试策略
    .build()

// 任务入队
WorkManager.getInstance(this).enqueue(sendRequest)

二、原有发信代码修正

你的EmailService类需要调整以下细节:

class EmailService(private var server: String, private var port: Int) {
    data class Email(
        val auth: Authenticator,
        val toList: List<InternetAddress>,
        val from: Address,
        val subject: String,
        val body: String
    )

    class UserPassAuthenticator(
        private val username: String, 
        private val password: String
    ) : Authenticator() {
        override fun getPasswordAuthentication(): PasswordAuthentication {
            return PasswordAuthentication(username, password)
        }
    }

    fun send(email: Email) {
        val props = Properties()
        props["mail.smtp.auth"] = "true"
        props["mail.smtp.host"] = server
        props["mail.smtp.port"] = port.toString()
        props["mail.smtp.starttls.enable"] = "true"
        props["mail.smtp.ssl.trust"] = server
        props["mail.mime.charset"] = "UTF-8"

        val session = Session.getInstance(props, email.auth)
        val msg: Message = MimeMessage(session)
        msg.setFrom(email.from)
        msg.setRecipients(Message.RecipientType.TO, email.toList.toTypedArray())
        msg.replyTo = arrayOf(email.from)
        msg.addHeader("X-Mailer", CLIENT_NAME)
        msg.addHeader("Precedence", "bulk")
        msg.subject = email.subject

        val multipart = MimeMultipart()
        val bodyPart = MimeBodyPart()
        bodyPart.setText(email.body, "UTF-8") // 改为UTF-8避免中文乱码
        multipart.addBodyPart(bodyPart)
        msg.setContent(multipart)

        Transport.send(msg)
    }

    companion object {
        const val CLIENT_NAME = "Android Automatic Email Client"
    }
}

注意事项

  • 国内安卓厂商对后台任务有严格的限制,如果需要任务准时触发,需要引导用户给应用开启后台运行权限、自启动权限
  • 发件邮箱需要提前开启SMTP服务,登录邮箱后台即可开启,使用的密码是SMTP授权码不是邮箱登录密码
  • Android 13及以上版本需要申请POST_NOTIFICATIONS权限,WorkManager执行任务时会显示通知
  • 不要在客户端硬编码发件邮箱的授权码,避免反编译泄露,如果用户规模较大,建议把发信逻辑放到后端实现,客户端只需要把定时发信请求传给后端即可

内容的提问来源于stack exchange,提问作者ANDROIDDEV

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 15:45:02