无法在短时间内发送太多的邮件

问题描述:

我有一个通信应用程序,其中每个用户创建一个通信并将其发送给多个用户(一般在2-30个用户之间发送给我们),每次发送我打开一个新线程并发送电子邮件在以下流的用户组(连接到邮件服务器>发送>关闭连接)如下:无法在短时间内发送太多的邮件

public class EmailService { 

    private String emailProtocol = null; 
    private String emailHostSMTP = null; 
    private String senderEmail = null; 
    private String senderUser = null; 
    private String senderPassword = null; 
    private String senderDisplayName = null; 
    private String emailPort = null; 

    public void initConfig() { 
     emailProtocol = GeneralServices.getConfig("emailProtocol"); 
     emailHostSMTP = GeneralServices.getConfig("emailHostSMTP"); 
     senderEmail = GeneralServices.getConfig("senderEmail"); 
     senderUser = GeneralServices.getConfig("senderUser"); 
     senderPassword = GeneralServices.getConfig("senderPassword"); 
     senderDisplayName = GeneralServices.getConfig("senderDisplayName"); 
     emailPort = GeneralServices.getConfig("emailPort"); 
     if (StringUtils.isBlank(emailPort)) 
      emailPort = "587"; 
    } 

    public void setProps(Properties props) { 

     props.put("mail.transport.protocol", emailProtocol); 
     props.put("mail.smtp.host", emailHostSMTP); 
     props.put("mail.smtp.auth", "true"); 
     props.put("mail.smtp.port", emailPort); 
     if (ConfigurationUtils.isEnableStartTlsInEmail()) 
      props.put("mail.smtp.starttls.enable", "true"); 
     if (ConfigurationUtils.isEnableDebugInEmail()) 
      props.put("mail.debug", "true"); 

    } 

    public void sendEmail(String toUser, String subject, String emailHtmlBody, String bannerPath) throws Exception { 
     try { 
      if (StringUtils.isBlank(toUser)) { 
       return; 
      } 
      List<String> toUsers = new ArrayList<String>(1); 
      toUsers.add(toUser); 
      sendEmail(toUsers, null, null, subject, emailHtmlBody, bannerPath); 
     } catch (Exception e) { 
      throw e; 
     } 
    } 

    public void sendEmail(String fromEmail, String fromDisplayName, List<String> toList, List<String> ccList, 
      String subject, String emailBody, String filePhysicalPath, String fileName, String fileContentType) 
      throws Exception { 

     Transport transport = null; 

     try { 

      initConfig(); 
      MimeMultipart multipart = new MimeMultipart(); 
      Authenticator authenticator = new SMTPAuthenticator(); 
      MailSSLSocketFactory sslSocketFactory = new MailSSLSocketFactory(); 
      MimeBodyPart bodyPart = new MimeBodyPart(); 

      String html = ""; 

      Properties props = System.getProperties(); 
      setProps(props); 

      sslSocketFactory.setTrustAllHosts(true); 
      props.put("mail.smtp.ssl.socketFactory", sslSocketFactory); 

      Session session = Session.getInstance(props, authenticator); 
      // session.setDebug(true); 

      emailBody = emailBody + "<br/><br/>مرسل بواسطة : " + fromDisplayName; 
      html = "<html><body style='text-align:right'> " + emailBody + " </body></html>"; 
      bodyPart.setContent(html, "text/html; charset=UTF-8"); 
      multipart.addBodyPart(bodyPart); 

      MimeMessage message = new MimeMessage(session); 

      message.setFrom(new InternetAddress(senderEmail, fromDisplayName)); 
      message.setReplyTo(new Address[] { new InternetAddress(fromEmail) }); 

      if (toList != null && toList.size() > 0) { 
       for (String to : toList) { 
        message.addRecipient(Message.RecipientType.TO, new InternetAddress(to)); 
       } 
      } else { 
       throw new Exception("List of users to send email to is empty"); 
      } 

      if (ccList != null && ccList.size() > 0) { 
       for (String cc : ccList) { 
        message.addRecipient(Message.RecipientType.CC, new InternetAddress(cc)); 
       } 
      } 

      // attach file 
      BodyPart mimeBodyPart = new MimeBodyPart(); 
      DataSource source = new FileDataSource(filePhysicalPath); 
      mimeBodyPart.setDataHandler(new DataHandler(source)); 
      mimeBodyPart.setFileName(MimeUtility.encodeText(fileName, "utf-8", "B")); 

      multipart.addBodyPart(mimeBodyPart); 
      // end of file attach 

      message.setSubject(subject, "UTF-8"); 
      message.setContent(multipart); 
      message.setSentDate(new Date()); 

      transport = session.getTransport(emailProtocol); 
      transport.connect(senderEmail, senderPassword); 
      transport.sendMessage(message, message.getAllRecipients()); 

     } catch (Exception ex) { 
      throw ex; 
     } finally { 
      if (transport != null) 
       transport.close(); 
     } 

    } 

    public void sendEmail(List<String> toList, List<String> ccList, List<String> bccList, String subject, 
      String emailHtmlBody, String bannerPath) throws Exception { 

     if ((toList == null || toList.size() == 0) && (ccList == null || ccList.size() == 0) 
       && (bccList == null || bccList.size() == 0)) { 
      return; 
     } 

     Transport transport = null; 
     try { 

      initConfig(); 
      MimeMultipart multipart = new MimeMultipart(); 
      Authenticator authenticator = new SMTPAuthenticator(); 
      MailSSLSocketFactory sslSocketFactory = new MailSSLSocketFactory(); 
      MimeBodyPart bodyPart = new MimeBodyPart(); 

      String html = ""; 

      Properties props = System.getProperties(); 
      setProps(props); 

      sslSocketFactory.setTrustAllHosts(true); 
      props.put("mail.smtp.ssl.socketFactory", sslSocketFactory); 

      Session session = Session.getInstance(props, authenticator); 

      html = "<html><body> " + emailHtmlBody + " </body></html>"; 
      bodyPart.setContent(html, "text/html; charset=UTF-8"); 
      multipart.addBodyPart(bodyPart); 

      // add banner path 
      bodyPart = new MimeBodyPart(); 
      DataSource ds = new FileDataSource(bannerPath); 
      bodyPart.setDataHandler(new DataHandler(ds)); 
      bodyPart.setHeader("Content-ID", "<MOAMALAT_LOGO>"); 
      multipart.addBodyPart(bodyPart); 

      MimeMessage message = new MimeMessage(session); 

      message.setFrom(new InternetAddress(senderEmail, senderDisplayName)); 
      message.setReplyTo(new Address[] { new InternetAddress(senderEmail) }); 

      if (toList != null && toList.size() > 0) { 
       for (String email : toList) 
        message.addRecipient(Message.RecipientType.TO, new InternetAddress(email)); 
      } 

      if (ccList != null && ccList.size() > 0) { 
       for (String email : ccList) 
        message.addRecipient(Message.RecipientType.CC, new InternetAddress(email)); 
      } 

      if (bccList != null && bccList.size() > 0) { 
       for (String email : bccList) 
        message.addRecipient(Message.RecipientType.BCC, new InternetAddress(email)); 
      } 

      message.setSubject(subject, "UTF-8"); 
      message.setContent(multipart); 
      message.setSentDate(new Date()); 

      transport = session.getTransport(emailProtocol); 
      transport.connect(senderEmail, senderPassword); 
      transport.sendMessage(message, message.getAllRecipients()); 

     } catch (Exception ex) { 
      throw ex; 
     } finally { 
      if (transport != null) 
       transport.close(); 
     } 

    } 

    private class SMTPAuthenticator extends javax.mail.Authenticator { 

     @Override 
     public PasswordAuthentication getPasswordAuthentication() { 
      String username = senderUser; 

      String password = senderPassword; 
      return new PasswordAuthentication(username, password); 
     } 
    } 
} 

有时我得到的错误:

com.sun.mail.smtp.SMTPSendFailedException: 421 4.4.2 Message submission rate for this client has exceeded the configured limit 

,但与Exchange Server审核后管理员,他说我没有发送超过限制的电子邮件。

有时也出现错误:

java.net.SocketException: Connection reset 

也有的时候我:

javax.mail.MessagingException: Can't send command to SMTP host,Caused by: java.net.SocketException: Connection closed by remote host 

你提出解决上述问题,有什么解决办法?

我读过一些人将传输对象设为静态,并且只与交换服务器建立连接,然后重用它,这是否有助于解决问题?连接将打开多久?

此外我还想过一个解决方案,将电子邮件详细信息保存在数据库表中,并创建一个工作班级定期发送批量电子邮件。

请指教如何解决此问题。

+0

Exchange Server上MessageRateLimit的值是多少? –

+0

@OnurAktaş管理员告诉我,每分钟最多的电子邮件是200,我的电子邮件没有发送超过200,他证实了这一点! –

+0

你直接连接到交换服务器吗? – Hannes

根据该错误消息:

Message submission rate for this client has exceeded the configured limit

1) 这可以通过其通常不被Exchange管理员已知的(如所提到的hereExchange trotting policy引起的)。

因此,让您的Exchange管理员控制MessageRateLimittrotting policy通过内部:

Get-ThrottlingPolicy | select Name,MessageRateLimit 

由于微软文档此参数为:

The MessageRateLimit parameter specifies the number of messages per minute that can be submitted to transport by POP3 or IMAP4 clients that use SMTP. Clients receive a transient error if they submit messages at a rate that exceeds the value of this parameter. Exchange attempts to connect and send the messages at a later time.

而且这是低可能会导致这样的值的问题。 Exchange管理员还可以创建一个new trotting policy,其值更高,仅用于您正在使用的此任务用户,并且不会对现有用户造成任何问题(有关示例,请参见here)。所以不需要改变默认的小跑策略。

更新: 2.) 正如您现在检查小跑策略,另一个解决方案可能是一个SMTP解决方案,它控制/监视SMTP流量。这可能与客户端相关或服务器端相关(取决于您的环境)。可能的选项有:防火墙& AntivirusClient

+0

以及我试过上面的命令和价值是无限的,所以我不认为这是问题 –

+0

好吧,另一个选项可能是监控/控制SMTP端口的防病毒解决方案。 – BastianW

我编辑你的第二个申报sendEmail方法(如下)。如果您喜欢它,请将相同的技术应用于其他sendEmail方法。

作为一个解决方案,我抓取sendMessage部分到cc列表的循环。因此,对于每个“收件人”电子邮件和每个对应的“cc”电子邮件,电子邮件都会发送。 cc列表的缺失也必须由您评估。代码可能不会编译,但您应该看到这一点。

public void sendEmail(String fromEmail, String fromDisplayName, List<String> toList, List<String> ccList, 
     String subject, String emailBody, String filePhysicalPath, String fileName, String fileContentType) 
     throws Exception { 

    Transport transport = null; 

    try { 

     initConfig(); 
     MimeMultipart multipart = new MimeMultipart(); 
     Authenticator authenticator = new SMTPAuthenticator(); 
     MailSSLSocketFactory sslSocketFactory = new MailSSLSocketFactory(); 
     MimeBodyPart bodyPart = new MimeBodyPart(); 

     String html = ""; 

     Properties props = System.getProperties(); 
     setProps(props); 

     sslSocketFactory.setTrustAllHosts(true); 
     props.put("mail.smtp.ssl.socketFactory", sslSocketFactory); 

     Session session = Session.getInstance(props, authenticator); 
     // session.setDebug(true); 

     emailBody = emailBody + "<br/><br/>مرسل بواسطة : " + fromDisplayName; 
     html = "<html><body style='text-align:right'> " + emailBody + " </body></html>"; 
     bodyPart.setContent(html, "text/html; charset=UTF-8"); 
     multipart.addBodyPart(bodyPart); 

     MimeMessage message = new MimeMessage(session); 

     // attach file 
     BodyPart mimeBodyPart = new MimeBodyPart(); 
     DataSource source = new FileDataSource(filePhysicalPath); 
     mimeBodyPart.setDataHandler(new DataHandler(source)); 
     mimeBodyPart.setFileName(MimeUtility.encodeText(fileName, "utf-8", "B")); 

     multipart.addBodyPart(mimeBodyPart); 
     // end of file attach 

     message.setSubject(subject, "UTF-8"); 
     message.setContent(multipart); 



     message.setFrom(new InternetAddress(senderEmail, fromDisplayName)); 
     message.setReplyTo(new Address[] { new InternetAddress(fromEmail) }); 

     transport = session.getTransport(emailProtocol); 
     transport.connect(senderEmail, senderPassword); 

     if (toList != null && toList.size() > 0) { 
      for (String to : toList) { 
       message.addRecipient(Message.RecipientType.TO, new InternetAddress(to)); 

       if (ccList != null && ccList.size() > 0) { 
        for (String cc : ccList) { 
         message.addRecipient(Message.RecipientType.CC, new InternetAddress(cc)); 
         message.setSentDate(new Date()); 
         transport.sendMessage(message, message.getAllRecipients()); 
        } 
       } 


      } 
     } else { 
      throw new Exception("List of users to send email to is empty"); 
     } 

    } catch (Exception ex) { 
     throw ex; 
    } finally { 
     if (transport != null) 
      transport.close(); 
    } 

} 

下面的代码必须只运行一次,而不是内的每一个sendEmail电话:

initConfig() 

transport = session.getTransport(emailProtocol); 
    transport.connect(senderEmail, senderPassword); 

我想创建一个工厂类,这将初始化并返回运输情况,所以我可以使用和重复使用,以保持多个连接到MX,

设置会话和传输必须在任何sendEmail调用之前完成,因此构造函数是适合您的地方。

您可以利用会话和传输对象的小对象池,并且每次调用sendEmail方法都必须从池中获取一个传输实例,并在完成后将其返回给池,这将确保在并发请求环境中轻松加载, 进一步推导批量推送限制,首先在推入之间添加时间间隔并缩短时间间隔,以便知道接近精确限制的MX允许在推送之间进行,并将推送大小与推送大小关联以确定最终限制。

+0

实际上,在您发布之前,我对重复使用传输对象进行了更改,但问题在于传输对象在少量时间后被邮件服务器断开连接。 –

+0

另外我做了另一项改变,我做了一个自定义的队列类,每3分钟运行一次,并将所有电子邮件发送到一个传输器,以便减少可能导致服务器上的防病毒服务器重新启动连接的邮件服务器连接数 –