SonarSource/sonarqube · warning
Failed to send quality gate change email notification for…
Error message
Failed to send quality gate change email notification for project {} What it means
EmailQGChangeEventListener reacts to Quality Gate status change events by sending notification emails. If notifyUsers throws for any reason (SMTP problems, missing project/branch data, template rendering errors), the listener logs this warning with the project key and continues, so notification failure never breaks the compute engine event pipeline.
Solutions
- Check the logged exception for the root cause (stack trace follows the project key).
- Verify email settings (smtp.host, smtp.port, smtp.username/password) in sonar.properties and test connectivity to the SMTP server.
- Confirm the project and branch still exist and users' notification subscriptions resolve to valid email addresses.
- Re-run the analysis if the notification was critical; the event itself succeeded.
Example fix
// before: unreachable SMTP sonar.email.smtp.host=mail.internal.example.com sonar.email.smtp.port=25 // after: corrected reachable SMTP with auth sonar.email.smtp.host=smtp.example.com sonar.email.smtp.port=587 sonar.email.smtp.username=sonar@example.com sonar.email.smtp.secure.connection=STARTTLS
Defensive patterns
Strategy: try-catch
Validate before calling
// pre-check SMTP reachability before enabling QG notifications
const net = require('net');
const s = net.createConnection(587, 'smtp.example.com');
s.on('connect', () => { console.log('SMTP reachable'); s.end(); }); Try / catch
try {
await mailer.send(qgChangeEmail);
} catch (e) {
logger.warn('Failed to send quality gate change email notification for project ' + projectKey, e);
// never rethrow: notifications must not break analysis pipeline
} Prevention
- Validate SMTP host/port/credentials in sonar.properties before production
- Ensure notification subscribers have valid email addresses
- Avoid deleting projects while analyses/notifications are in flight
- Monitor mail server availability
When it happens
Trigger: onIssueChanges -> notifyUsers raising an exception while building or sending the quality-gate-change email for the event's project (e.g. mail server unreachable, no recipients resolved, persistence lookup failure on project/branch DTOs).
Common situations: SMTP host misconfigured or down; mail credentials rejected; project deleted between event creation and notification; corrupted branch data; permission/credential lookup failures for subscribed users.
Related errors
- Email configuration doesn't exist.
- Email configuration with id
- Unknown type of SMTP secure connection:
- Unknown type of SMTP secure connection:
- Address contains invalid character: 0x%02x
AI-assisted analysis of SonarSource/sonarqube@184c821202 (2026-09-09).
Data as JSON: /api/errors/a9eb18b77826bf72.
Report an issue: GitHub.
Appendix: source
Thrown at server/sonar-webserver-core/src/main/java/org/sonar/server/qualitygate/notification/EmailQGChangeEventListener.java:74
@Override
public void onIssueChanges(QGChangeEvent event, Set<ChangedIssue> changedIssues) {
Optional<EvaluatedQualityGate> evaluatedQG = event.getQualityGateSupplier().get();
if (evaluatedQG.isEmpty()) {
return;
}
Metric.Level newStatus = evaluatedQG.get().getStatus();
Optional<Metric.Level> previousStatus = event.getPreviousStatus();
if (previousStatus.isPresent() && previousStatus.get() == newStatus) {
return;
}
try {
notifyUsers(event, newStatus, previousStatus.orElse(null));
} catch (Exception e) {
LOGGER.warn("Failed to send quality gate change email notification for project {}", event.getProject().getKey(), e);
}
}
private void notifyUsers(QGChangeEvent event, Metric.Level newStatus, @Nullable Metric.Level previousStatus) {
ProjectDto project = event.getProject();
BranchDto branch = event.getBranch();
EvaluatedQualityGate evaluatedQG = event.getQualityGateSupplier().get().orElseThrow();
String statusLabel = toStatusLabel(newStatus);
boolean isNewAlert = previousStatus == null;
try (DbSession dbSession = dbClient.openSession(false)) {
Map<String, MetricDto> metricsByKey = dbClient.metricDao().selectAll(dbSession).stream()
.collect(Collectors.toMap(MetricDto::getKey, Function.identity()));
String alertText = conditionFormatter.buildAlertText(evaluatedQG, metricKey -> {
MetricDto metric = metricsByKey.get(metricKey);
return new QualityGateConditionFormatter.MetricInfo(View on GitHub (pinned to 184c821202)