pentaho/pentaho-kettle · error · KettleException
MAIL001
MAIL001
Error message
Mail.Error.General
What it means
Mail.Error.General is the generic failure message thrown when the Mail step fails during processRow and no error-handling (putError) is configured on the transformation. The original exception is wrapped in a KettleException as the cause. It signals any send-mail failure (SMTP errors, missing fields, connection problems) that was not routed to the error stream.
Solutions
- Inspect the wrapped cause exception (getCause()) for the real SMTP or NPE message
- Attach an error-handling step to the Mail step so failures go to the error stream instead of aborting
- Verify SMTP host, port, authentication and use of authentication settings in the Mail step dialog
- Test connectivity to the SMTP server from the machine running the transformation
Example fix
// before: no error handling, exception aborts the transformation mailStep.doingErrorHandling = false; // after: route failing rows to an error stream mailStep.doingErrorHandling = true; // configure error handling target step
Defensive patterns
Strategy: try-catch
Validate before calling
// before running: verify SMTP reachability and config
Properties props = System.getProperties();
props.put("mail.smtp.host", smtpHost);
props.put("mail.smtp.port", String.valueOf(smtpPort));
try (Transport ignored = null) {
Session s = Session.getInstance(props);
Transport t = s.getTransport("smtp");
t.connect(smtpHost, user, password);
t.close();
} Try / catch
try {
mailStep.processRow();
} catch (KettleException e) {
Throwable root = e.getCause(); // inspect real SMTP error
logError("Mail step failed: " + root.getMessage(), e);
} Prevention
- Always attach an error-handling step so failures land on the error stream instead of aborting
- Validate SMTP host/port/credentials in the step dialog before production runs
- Log the wrapped cause, not just the generic message
When it happens
Trigger: processRow caught an Exception while processing a row (e.g. SMTP send failed, null field value, misconfigured mail settings) AND the step meta reports isDoingErrorHandling()==false, so the catch block throws KettleException('Mail.Error.General', e) instead of calling putError.
Common situations: Running a transformation without an error-handling step attached to the Mail step; SMTP host/port/credentials wrong; test harness (testSendMailWithPassword) executing the step directly where error handling is not set up; network unreachable to the mail server.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
Related errors
- Mail.Error.MailServerEmpty
- Mail.Exception.CouldnotFindServerField
- ColumnExists.Log.ErrorInStep
- Errors encountered (first 10): ...
- FixedTimeStreamWindow.SubtransFailed
AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13).
Data as JSON: /api/errors/10deba0bd1e4980c.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/mail/impl/src/main/java/org/pentaho/di/trans/steps/mail/Mail.java:199
comment = data.previousRowMeta.getString( r, data.indexOfComment );
}
// send email...
sendMail( r, serverName, port, mailSenderAddress, mailSenderName, mailDestination, mailDestinationCc,
mailDestinationBCc, contactPerson, contactPhone, authUser, authPass, subject, comment, mailReplyToAddresses );
putRow( getInputRowMeta(), r );
if ( log.isRowLevel() ) {
logRowlevel( BaseMessages.getString( PKG, "Mail.Log.LineNumber", getLinesRead()
+ " : " + getInputRowMeta().getString( r ) ) );
}
} catch ( Exception e ) {
if ( getStepMeta().isDoingErrorHandling() ) {
// Simply add this row to the error row
putError( getInputRowMeta(), r, 1, e.toString(), null, "MAIL001" );
} else {
throw new KettleException( BaseMessages.getString( PKG, "Mail.Error.General" ), e );
}
}
return true;
}
private void checkEmbeddedImages( MailMeta meta, MailData data ) {
if ( meta.getEmbeddedImages() != null && meta.getEmbeddedImages().length > 0 ) {
FileObject image = null;
data.embeddedMimePart = new HashSet<>();
try {
for ( int i = 0; i < meta.getEmbeddedImages().length; i++ ) {
String imageFile = environmentSubstitute( meta.getEmbeddedImages()[i] );
String contentID = environmentSubstitute( meta.getContentIds()[i] );
image = KettleVFS.getInstance( getTransMeta().getBowl() ).getFileObject( imageFile );
if ( image.exists() && image.getType() == FileType.FILE ) {
// Create part for the imageView on GitHub (pinned to f3058517a1)