Email Grails:当SMTP服务器暂时关闭时,如何缓冲出站电子邮件?

Email Grails:当SMTP服务器暂时关闭时,如何缓冲出站电子邮件?,email,grails,asynchronous,smtp,Email,Grails,Asynchronous,Smtp,Grails使用Spring的mailService。该服务是同步的,这意味着如果SMTP暂时关闭,应用程序的功能将受到严重影响(HTTP 500) 我想将应用程序与SMTP服务器分离 计划是将准备发送的电子邮件保存到出站队列中,并通过计时器发送,然后重试。对于我自己的代码,当我直接调用mailService时,它相当简单——创建一个包装器服务并调用它。但我的应用程序使用的一些插件(例如EmailConfirmation插件)使用相同的邮件服务,但仍然失败,例如,有效地阻止了注册过程 我想知道如

Grails使用Spring的mailService。该服务是同步的,这意味着如果SMTP暂时关闭,应用程序的功能将受到严重影响(HTTP 500)

我想将应用程序与SMTP服务器分离

计划是将准备发送的电子邮件保存到出站队列中,并通过计时器发送,然后重试。对于我自己的代码,当我直接调用mailService时,它相当简单——创建一个包装器服务并调用它。但我的应用程序使用的一些插件(例如EmailConfirmation插件)使用相同的邮件服务,但仍然失败,例如,有效地阻止了注册过程

我想知道如何替换/包装mailService的定义,使所有代码(我自己的代码和插件)透明地使用我自己的服务

  • 插件代码注入邮件服务
  • 但不是Spring默认邮件服务,而是注入了我自己的代码
  • 当插件发送电子邮件时,电子邮件对象将保存到数据库中
  • 在计时器上,一个作业醒来,收到下N封电子邮件并尝试发送它们
有没有办法解决这个问题


另外,我知道AsynchronousMail插件。不幸的是,它的服务必须被明确地调用,也就是说,它不是mailService的替代品

一个简单的解决方案是使用本地安装的邮件服务器。有众所周知的、成熟的MTA,如Postfix、Sendmail或Exim,也有轻量级的替代品,如

将使用过的MTA包配置为将其所有电子邮件中继到域的真实SMTP服务器。Grails应用程序将简单地使用127.0.0.1作为SMTP主机


这还具有提高应用程序响应时间的优势,因为发送电子邮件首先不再需要任何非本地IP流量。

好的,毕竟这并不难。简单的第一步:

第一步:准备数据库表以存储挂起的电子邮件记录:

class PendingEmail {
    Date sentAt = new Date()
    String fileName

    static constraints = {
        sentAt nullable: false
        fileName nullable: false, blank:false
    }
}
第二步:创建定期任务以发送待处理的电子邮件。注意
mailssender
injection-它是原始Grails邮件插件的一部分,因此发送(及其配置!)是通过邮件插件完成的:

import javax.mail.internet.MimeMessage

class BackgroundEmailSenderJob {

    def concurrent = false
    def mailSender

    static triggers = {
        simple startDelay:15000l, repeatInterval: 30000l, name: "Background Email Sender"
    }

    def execute(context){
        log.debug("sending pending emails via ${mailSender}")

        // 100 at a time only
        PendingEmail.list(max:100,sort:"sentAt",order:"asc").each { pe ->

            // FIXME: do in transaction
            try {
                log.info("email ${pe.id} is to be sent")

                // try to send
                MimeMessage mm = mailSender.createMimeMessage(new FileInputStream(pe.fileName))
                mailSender.send(mm)

                // delete message
                log.info("email ${pe.id} has been sent, deleting the record")
                pe.delete(flush:true)

                // delete file too
                new File(pe.fileName).delete();
            } catch( Exception ex ) {
                log.error(ex);
            }
        }
    }
}
第三步:创建一个可供任何Grails代码(包括插件)使用的mailService的插入式替代品。注意mmbf注入:这是来自邮件插件的
mailMessageBuilderFactory
。该服务使用工厂将传入的闭包调用序列化为有效的MIME消息,然后将其保存到文件系统:

import java.io.File;

import org.springframework.mail.MailMessage
import org.springframework.mail.javamail.MimeMailMessage

class MyMailService {
    def mmbf

    MailMessage sendMail(Closure callable) {
        log.info("sending mail using ${mmbf}")

        if (isDisabled()) {
            log.warn("No mail is going to be sent; mailing disabled")
            return
        } 

        def messageBuilder = mmbf.createBuilder(mailConfig)
        callable.delegate = messageBuilder
        callable.resolveStrategy = Closure.DELEGATE_FIRST
        callable.call()
        def m = messageBuilder.finishMessage()

        if( m instanceof MimeMailMessage ) {
            def fil = File.createTempFile("mail", ".mime")
            log.debug("writing content to ${fil.name}")
            m.mimeMessage.writeTo(new FileOutputStream(fil))

            def pe = new PendingEmail(fileName: fil.absolutePath)
            assert pe.save(flush:true)
            log.debug("message saved for sending later: id ${pe.id}")
        } else {
            throw new IllegalArgumentException("expected MIME")
        }
    }

    def getMailConfig() {
        org.codehaus.groovy.grails.commons.ConfigurationHolder.config.grails.mail
    }

    boolean isDisabled() {
        mailConfig.disabled
    }
}
第四步:用修改后的版本替换邮件插件的邮件服务,将其注入工厂。在
grails-app/conf/spring/resources.groovy
中:

beans = {
    mailService(MyMailService) {
        mmbf = ref("mailMessageBuilderFactory")
    }
}
完成了

从现在起,任何使用/注入mailService的插件或Grails代码都将获得对MyMailService的引用。该服务将接受发送电子邮件的请求,但不会发送电子邮件,而是将其序列化到磁盘上,并将记录保存到数据库中。定期任务将每隔30秒加载一组此类记录,并尝试使用原始邮件插件服务发送这些记录

我已经测试过了,它似乎工作正常。我需要在这里和那里进行清理,围绕发送添加事务范围,使参数可配置等等,但是框架已经是一个可行的代码了


希望这对其他人有所帮助。

异步邮件插件现在支持覆盖邮件插件 加上

asynchronous.mail.override=true

到您的配置。见

好的观点。除了当地的MTA也可能被关闭。最终目标是将应用程序与外部服务分离。同意,但MTA大多坚如磐石,多年来我从未观察到过,例如后缀出现问题。只是不是在每个环境中,开发人员都可以决定MTA的安装位置。即使是坚如磐石的本地MTA有时也会因为操作人员的错误或(例如)升级而下降。是的,我认为代码会帮助我,谢谢,因为我将我的web应用程序部署到linux托管环境。我可以问你,你决定为SMTP服务器。你有没有发现一些简单而轻巧的东西。。它是否在您的服务器上运行?我开发的SMTP是否在我的开发设备上运行postfix;PROD中的SMTP是我的托管公司使用的SMTP。我不在乎他们跑什么(我相信是qmail,但可能弄错了)。谢谢!迟了一年,但迟做总比不做好。