O serviço SMTP do QQ Mail impõe uma restrição rigorosa de frequência, permitindo apenas o envio de aproximadamente 40 mensagens por minuto por conta. Quando há necessidade de disparar volumes maiores de notificações, essa limitação torna-se um gargalo significativo. A estratégia mais eficaz para mitigar esse problema consiste na implementação de um pool de contas de e-mail, alternando entre elas durante o processo de envio.
Estratégia de Rotação de Contas
A lógica central envolve manter uma lista de credenciais SMTP válidas. O sistema deve gerenciar conexões ativas para cada conta e alternar o uso assim que um limite seguro é atingido ou quando ocorre um erro de taxa limite. Abaixo apresenta-se uma implementação robusta utilizando JavaMail integrado ao Spring Framework, focando em segurança de threads e reutilização de conexões.
Implementação do Serviço de Envio
Para evitar a complexidade de manter listas praalelas de objetos (sessões, transportes, propriedades), encapsula-se todos os recursos relacionados a uma única conta em uma classe de contexto. Isso melhora a legibilidade e a manutenção do código.
Primeiro, define-se a estrutura de dados para as credenciais:
package com.example.mail.core;
import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.MimeMessage;
import java.util.Properties;
public class SmtpConnectionContext {
private final String address;
private final String authCode;
private final String nickname;
private Session session;
private Transport transport;
private MimeMessage message;
public SmtpConnectionContext(String address, String authCode, String nickname) {
this.address = address;
this.authCode = authCode;
this.nickname = nickname;
}
public void initialize() throws Exception {
Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.host", "smtp.qq.com");
props.put("mail.smtp.port", "587");
props.put("mail.smtp.timeout", "5000");
props.put("mail.user", address);
props.put("mail.password", authCode);
Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(address, authCode);
}
};
this.session = Session.getInstance(props, authenticator);
this.message = new MimeMessage(this.session);
this.transport = session.getTransport();
this.transport.connect();
}
public boolean isActive() {
return transport != null && transport.isConnected();
}
public void reconnect() throws Exception {
if (transport != null) {
transport.close();
}
initialize();
}
// Getters
public String getAddress() { return address; }
public Transport getTransport() { return transport; }
public MimeMessage getMessage() { return message; }
public String getNickname() { return nickname; }
}
Em seguida, implementa-se o serviço gerencial que controla a rotação e o envio assíncrono. Utiliza-se um lock para garantir que a alternação de contas seja segura em ambientes concorrentes.
package com.example.mail.service;
import com.example.mail.core.SmtpConnectionContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.mail.internet.InternetAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
@Service
public class BulkEmailService {
private final List<SmtpConnectionContext> connectionPool = new ArrayList<>();
private int currentIndex = 0;
private final ReentrantLock lock = new ReentrantLock(true);
private static final int ROTATION_THRESHOLD = 20;
private int sendCount = 0;
public void registerAccount(String email, String code, String name) {
try {
SmtpConnectionContext context = new SmtpConnectionContext(email, code, name);
context.initialize();
connectionPool.add(context);
} catch (Exception e) {
throw new RuntimeException("Falha ao inicializar conta SMTP: " + email, e);
}
}
@Async
public void dispatchEmail(String subject, String content, String recipient) {
lock.lock();
try {
ensureConnection();
SmtpConnectionContext context = connectionPool.get(currentIndex);
try {
context.getMessage().setFrom(new InternetAddress(context.getAddress(), context.getNickname(), "UTF-8"));
context.getMessage().setRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress(recipient));
context.getMessage().setSubject(subject, "UTF-8");
context.getMessage().setContent(content, "text/html;charset=UTF-8");
context.getMessage().saveChanges();
context.getTransport().sendMessage(context.getMessage(), new InternetAddress[]{new InternetAddress(recipient)});
sendCount++;
if (sendCount >= ROTATION_THRESHOLD) {
rotateAccount();
}
} catch (Exception e) {
handleSendFailure(context);
}
} finally {
lock.unlock();
}
}
private void ensureConnection() throws Exception {
SmtpConnectionContext context = connectionPool.get(currentIndex);
if (!context.isActive()) {
context.reconnect();
}
}
private void rotateAccount() {
sendCount = 0;
if (currentIndex == connectionPool.size() - 1) {
currentIndex = 0;
} else {
currentIndex++;
}
}
private void handleSendFailure(SmtpConnectionContext context) {
// Lógica de retry ou troca imediata em caso de erro crítico
try {
context.reconnect();
} catch (Exception e) {
rotateAccount();
}
}
}
Configuração no Spring Context
Para inicializar o pool de contas, configura-se um bean que registra as credenciais disponíveis durante a subida da aplicação. É crucial utilizar endereços válidos e códigos de autorização SMTP gerados nas configurações de segurança do QQ Mail.
package com.example.mail.config;
import com.example.mail.service.BulkEmailService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MailPoolConfig {
@Bean
public BulkEmailService initEmailPool(BulkEmailService service) {
// Registrar múltiplas contas para distribuir a carga
service.registerAccount("conta01@qq.com", "CODIGO_AUT_01", "Remetente Principal");
service.registerAccount("conta02@qq.com", "CODIGO_AUT_02", "Remetente Secundário");
service.registerAccount("conta03@qq.com", "CODIGO_AUT_03", "Remetente Terciário");
return service;
}
}
Esta arquitetura permite que a aplicação distribua o volume de envios entre várias identidades, evitando que uma única conta atinja o limite de frequência imposto pelo provedor. O uso de locks garante a consistência do índice de rotação, enquanto a reconexão automática trata falhas temporárias de rede ou sessões expiradas.