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

  1. Inspect the wrapped cause exception (getCause()) for the real SMTP or NPE message
  2. Attach an error-handling step to the Mail step so failures go to the error stream instead of aborting
  3. Verify SMTP host, port, authentication and use of authentication settings in the Mail step dialog
  4. 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

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


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 image

View on GitHub (pinned to f3058517a1)