【问题标题】:Grails: How to buffer outbound emails when SMTP server is temporarely down?Grails:当 SMTP 服务器暂时关闭时,如何缓冲出站电子邮件?
【发布时间】:2011-03-15 05:12:59
【问题描述】:

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

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

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

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

  • 插件代码注入mailService
  • 但是注入了我自己的代码而不是 Spring 默认的 mailService
  • 当插件发送电子邮件时,电子邮件对象被保存到数据库中
  • 定时任务唤醒,获取下 N 封电子邮件并尝试发送它们

任何想法如何解决这个问题?

附:我知道异步邮件插件。不幸的是,它的服务必须被显式调用,即它不是 mailService 的替代品。

【问题讨论】:

    标签: email grails asynchronous smtp


    【解决方案1】:

    一个简单的解决方案是使用本地安装的邮件服务器。有众所周知的成熟 MTA,如 Postfix、Sendmail 或 Exim,以及轻量级替代品,如 http://packages.qa.debian.org/s/ssmtp.html

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

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

    【讨论】:

    • 好点。除了本地 MTA 也可能下降。最终目标是将应用程序与外部服务分离。
    • 同意,但 MTA 大多是坚如磐石,多年来我从未观察过,例如一个后缀下降。
    • 并不是在每个环境中,开发人员都可以决定 MTA 的安装位置。即使是坚如磐石的本地 MTA 有时也会因操作人员错误或升级等原因而停机。
    【解决方案2】:

    异步邮件插件现在支持覆盖邮件插件 只需添加

    asynchronous.mail.override=true
    

    到你的配置。见http://grails.org/plugin/asynchronous-mail

    【讨论】:

    • 谢谢!迟到一年,但迟到总比没有好。
    【解决方案3】:

    好吧,毕竟这并不难。首先是简单的步骤:

    第一步:准备数据库表来存储待处理的电子邮件记录:

    class PendingEmail {
        Date sentAt = new Date()
        String fileName
    
        static constraints = {
            sentAt nullable: false
            fileName nullable: false, blank:false
        }
    }
    

    第二步:创建定期任务以发送待处理的电子邮件。注意mailSender 注入 - 它是原始 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 注入:这是来自 Mail Plugin 的 mailMessageBuilderFactory。该服务使用工厂将传入的 Closure 调用序列化为有效的 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
        }
    }
    

    第四步:将Mail Plugin的mailService替换为修改后的版本,用工厂注入。在grails-app/conf/spring/resources.groovy:

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

    完成!

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

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

    希望对某人有所帮助。

    【讨论】:

    • 是的,我认为代码会帮助我,谢谢,因为我将我的 Web 应用程序部署到 linux 托管环境。我可以问你,你对 SMTP 服务器的决定是什么。你有没有发现任何简单而轻便的东西......它可以在你的服务器上运行吗?
    • 我开发的 SMTP 是在我的开发盒上运行的 postfix; PROD 中的 SMTP 是我的托管公司使用的。我不在乎他们运行什么(我相信是 qmail,但可能是错误的)。
    猜你喜欢
    • 2011-04-14
    • 1970-01-01
    • 2018-06-29
    • 2018-06-25
    • 2014-11-14
    • 2016-09-28
    • 2011-11-13
    • 1970-01-01
    • 2012-07-15
    相关资源
    最近更新 更多