Email not sent in @Async annotated method



I am trying to implement a function for user to download a file. The general workflow is as follows:

  1. User click the download button at frontend
  2. The backend will receive the download information from request, get data from database, and then generate zip file
  3. The file will be uploaded into cloud storage (Google in this case).
  4. An email with a signed URL will be sent to user to download the zip file from cloud.

All steps 2, 3 and 4 will be executed in a method annotated with @Async. The problem is if I restart the backend server and send only one download request, everything is fine which means the email can be received. However, it does not work after sending more download requests. No more emails will be sent. No errors, no warnings, but all the data required are received correctly, just no email was sent out.

Anyone knows what is the problem of it?

My Email Sender:

public class AbstractEmailSender {

    public MimeMessage mimeMessage;
    public MimeMessageHelper mimeMessageHelper;
    public JavaMailSender javaMailSender;
    private final String SENDER = MY_SEND_EMAIL_ADDRESS;
    public final String USER_NAME_KEY = "username";

    public AbstractEmailSender(JavaMailSender javaMailSender) throws MessagingException {
//        this.javaMailSender = new JavaMailSenderImpl();
        this.javaMailSender = javaMailSender;
        this.doInitialization();
    }

    public void doInitialization() throws MessagingException {
        this.mimeMessage = this.javaMailSender.createMimeMessage();
        this.mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
    }

    public void setEmailContext(String receiver, String subject) throws MessagingException {
        this.mimeMessageHelper.setSubject(subject);
        this.mimeMessageHelper.setFrom(SENDER);
        this.mimeMessageHelper.setTo(receiver);
    }
}

@Component
public class DownloadDataSuccessEmailSender extends AbstractEmailSender implements MailService{

    private final TemplateEngine templateEngine;
    private final static String DOWNLOAD_DATA_SUCCESS_EMAIL_SUBJECT = XXXXXX;
    private final static String SIGNED_URL_KEY = "signedUrl";
    private final static String DOWNLOAD_EMAIL_TEMPLATE_NAME = "downloadDataSuccessEmail.html";
    private static final Logger logger = LogManager.getLogger(DownloadDataSuccessEmailSender.class);


    public DownloadDataSuccessEmailSender(JavaMailSender javaMailSender, TemplateEngine templateEngine) throws MessagingException {
        super(javaMailSender);
        this.templateEngine = templateEngine;
    }

    @Override
    public void sendEmailWithSignedUrlToDownloadFile(URL signedUrl, String username, String receiver) {
        // print the result to make sure all data are processed correctly, nothing wrong with 
        //this step
        System.out.println(signedUrl);
        System.out.println(username);
        System.out.println(receiver);
        try{
            super.setEmailContext(receiver, DOWNLOAD_DATA_SUCCESS_EMAIL_SUBJECT);
            Context context = new Context();
            context.setVariable(this.USER_NAME_KEY, username);
            context.setVariable(SIGNED_URL_KEY, signedUrl);
            String email = this.templateEngine.process(DOWNLOAD_EMAIL_TEMPLATE_NAME, context);
            this.mimeMessageHelper.setText(email, true);
            this.javaMailSender.send(mimeMessage);
        }catch (MailException | MessagingException e) {
            logger.error("Email with download data error: ", e);
            throw new EmailSendException(ErrorInfo.EMAIL_SEND_EXCEPTION.getCode(), ErrorInfo.EMAIL_SEND_EXCEPTION.getMessage());
        }
    }
}

The email configuration file:

spring:
  mail:
    host: smtp.gmail.com
    username: MY_EMAIL_ADDRESS
    password: MY_PASSWORD
    properties.mail.smtp:
      auth: true
      connectiontimeout: 60000
      timeout: 60000
      writetimeout: 50000
      starttls.enable: true
    port: 587
    protocol: smtp
  thymeleaf:
    prefix: classpath:/templates/

The async method to handle all logic

@Override
    @Async
    @Transactional(timeout = DOWNLOAD_DATA_TRANSACTION_TIME_LIMIT)
    public void downloadFile(SearchQuery query, DownloadRequestRecord downloadRecord){
        String username = downloadRecord.getUsername();
        String emailAddress = this.userService.getUserEmailAddressByUsername(username);
        try{
            // here ignore the parts to get data from database and generate zip file and check if the file is uploaded to cloud space successfully here
            // ......
            if (uploadFile == null) { // check if file exists
                logger.error("File: {} does not exist in cloud!", zipFileName);
                downloadRecord.setSuccess(0);
                this.downloadDataFailMessageEmailSender.sendEmailWithDownloadDataFail(emailAddress, username, downloadRecord.getQuery(), downloadRecord.getDownloadTime()); // send email to inform user the download is fail
            } else {
// file exists, generate signed URL and send email with download link
               this.downloadDataSuccessEmailSender.sendEmailWithSignedUrlToDownloadFile(signedUrl, username, emailAddress);
                downloadRecord.setSuccess(1);
            }
        } catch (IOException | EmailSendException ex) {
            logger.error("Exception from downloading data: ", ex);
            downloadRecord.setSuccess(0);
        } finally {
            this.userService.recordDownloadHistoryOfUser(downloadRecord);
        }
    }

My thread pool configuration

@Configuration
public class AsyncConfig implements AsyncConfigurer {

    private static final int CORE_POOL_SIZE = 6;
    private static final int MAX_POOL_SIZE = 10;
    private static final int QUEUE_CAPACITY = 100;
    private static final String THREAD_NAME_PREFIX = "ThreadPoolTaskExecutor-";
    private static final Logger logger = LogManager.getLogger(AsyncConfig.class);

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(CORE_POOL_SIZE);
        executor.setMaxPoolSize(MAX_POOL_SIZE);
        executor.setQueueCapacity(QUEUE_CAPACITY);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncUncaughtExceptionHandler() {
            @Override
            public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
                logger.error("ERROR: ", throwable);
            }
        };
    }
}

Answer

in sendEmailWithSignedUrlToDownloadFile create the MimeMessage using

MimeMessage mimeMessage = mailSender.createMimeMessage();

instead of having a single instance of MimeMessage as member of the super class



Source: stackoverflow