Jenkins Email Notification: SMTP Setup, Templates & Production Debugging
Learn production-grade Jenkins email notification setup: SMTP config for Gmail/SendGrid/internal, Email Extension vs built-in mailer, token templates, attachment patterns, per-trigger config, and troubleshooting delivery failures..
20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.
- ✓Solid grasp of devops fundamentals
- ✓Comfortable reading and writing code examples independently
- ✓Basic understanding of production deployment concepts
- Use the Email Extension Plugin (2.100+) for per-trigger templates and Groovy scripting; avoid the built-in mailer for complex pipelines.
- For Gmail SMTP (smtp.gmail.com:587), generate an App Password from your Google Account (Security > 2-Step Verification > App Passwords) and use that as the password.
- SendGrid SMTP: server=smtp.sendgrid.net, port=587, username=apikey, password=your SendGrid API key.
- Internal SMTP (e.g., Postfix): set Jenkins URL in 'Manage Jenkins > Configure System' under 'Jenkins Location' — many delivery failures trace back to a misconfigured Jenkins URL.
- Use Extended Email Plugin's Groovy templates to dynamically set recipients, subject, and content based on build result and environment variables.
- Token references like ${BUILD_URL}, ${BUILD_NUMBER}, ${JOB_NAME} are case-sensitive and must match the exact token name from the plugin documentation.
- For email attachments, use the 'Attachments' field in the Email Extension post-build action; wildcards like '*.xml' work but be mindful of file size limits.
- Test SMTP connectivity directly with openssl s_client -connect smtp.gmail.com:587 -starttls smtp before debugging Jenkins config.
Imagine you're a project manager who needs to automatically email status updates to your team after each project milestone. You have a robot assistant (Jenkins) that finishes tasks and can send emails, but you need to give it the right instructions: which email server to use (like Gmail or your company's mail server), the password to log in, and what the email should say. This article teaches you how to configure that robot so it sends clear, useful emails with links to the project details, and how to fix it when emails don't arrive.
I once spent a weekend debugging why Jenkins wasn't sending failure notifications. The pipeline was failing, but the team only found out Monday morning when a customer complained. The root cause? I had used the built-in mailer with a default recipient list that didn't include the right people, and the SMTP server silently rejected the 'from' address because the Jenkins URL was set to localhost. That incident taught me that email notification setup is not just a 'nice to have'—it's a critical part of your CI/CD reliability. This article covers everything I learned: from choosing between the built-in mailer and the Email Extension Plugin, to configuring SMTP for Gmail, SendGrid, or your internal server, to writing Groovy templates that dynamically adapt emails to build results. You'll also get a production debugging guide for when emails mysteriously fail.
SMTP Configuration: Gmail, SendGrid, and Internal SMTP
Configuring SMTP in Jenkins is done under 'Manage Jenkins > Configure System' in the 'E-mail Notification' and 'Extended E-mail Notification' sections. For production, use the Extended Email Plugin's SMTP settings, which override the built-in mailer.
Gmail SMTP Setup - SMTP server: smtp.gmail.com - Port: 587 (TLS) or 465 (SSL) - Use TLS: yes - Username: your full Gmail address (e.g., you@gmail.com) - Password: an App Password (not your regular password). Generate one at https://myaccount.google.com/apppasswords after enabling 2-Step Verification. - Test connection by clicking 'Test Configuration' — this sends a test email to the address in 'Default Recipients'.
SendGrid SMTP Setup - SMTP server: smtp.sendgrid.net - Port: 587 (TLS) - Use TLS: yes - Username: apikey - Password: your SendGrid API key (starts with 'SG.') - Ensure your SendGrid account has verified the sender email or domain to avoid being flagged as spam.
Internal SMTP (Postfix/Exchange) - Use your internal SMTP server hostname or IP. - Port: 25 (no auth) or 587 (auth). For internal servers, you may need to whitelist the Jenkins server IP. - If using auth, store credentials in Jenkins credentials store and reference them via the 'Use Credentials' dropdown. - Common issue: internal SMTP servers reject emails with 'from' addresses that don't match the authenticated user. Set the 'Default user e-mail suffix' in 'Extended E-mail Notification' to append a domain (e.g., @example.com) if needed.
Email Extension Plugin vs Built-in Mailer: When to Use Which
The built-in mailer (plugin ID: mailer) is sufficient for simple notifications: send an email to a fixed list when a build fails. It's configured under 'E-mail Notification' and can be used in freestyle jobs via 'Post-build Actions > E-mail Notification'. However, it lacks per-trigger control, custom templates, and pipeline support.
The Email Extension Plugin (plugin ID: email-ext) is the production standard. It provides: - Per-trigger configuration: success, failure, unstable, regression, aborted, still failing, etc. - Custom subject and body templates using tokens like ${BUILD_URL}, ${BUILD_NUMBER}, ${JOB_NAME}, ${PROJECT_NAME}, ${CHANGES}, ${FAILED_TESTS}. - Groovy scripting for dynamic content. - Attachments via the 'Attachments' field. - Pipeline step 'emailext' for declarative and scripted pipelines.
Migration path: If you're using the built-in mailer, switch to Email Extension by installing the plugin and reconfiguring your jobs. The built-in mailer will still work but you'll lose flexibility. For new projects, always use Email Extension.
Pipeline example: ``groovy emailext( subject: "Build ${env.JOB_NAME} - ${env.BUILD_NUMBER} ${currentBuild.result}", body: "See ${env.BUILD_URL} for details.", to: "team@example.com", attachmentsPattern: "*/target/.xml", replyTo: "no-reply@example.com", recipientProviders: [[$class: 'DevelopersRecipientProvider'], [$class: 'RequesterRecipientProvider']] ) `` Note: recipientProviders can dynamically add developers and requesters.
Gmail App-Specific Password Setup: Step-by-Step
If you use Gmail SMTP, you must generate an App Password. Here's the exact process: 1. Go to your Google Account: https://myaccount.google.com/security 2. Under 'Signing in to Google', enable '2-Step Verification' if not already enabled. 3. Once 2FA is on, go to 'App passwords' (search in the Google Account search bar or directly: https://myaccount.google.com/apppasswords). 4. Select 'Mail' as the app and 'Other (Custom name)' as the device. Give it a name like 'Jenkins CI'. 5. Click 'Generate'. You'll see a 16-character password (e.g., 'abcd efgh ijkl mnop'). Copy it immediately—you won't see it again. 6. In Jenkins, under 'Manage Jenkins > Configure System > E-mail Notification', enter: - SMTP server: smtp.gmail.com - Port: 587 - Use TLS: checked - User name: your full Gmail address - Password: the App Password (remove spaces if present; some Jenkins versions require no spaces) 7. Click 'Test Configuration' to verify.
Important: If you have a Google Workspace account, you may need to allow less secure apps or use OAuth2. App Passwords work for personal Gmail accounts with 2FA. For Workspace, consider using SendGrid or an internal SMTP for better control.
Email Templates with Token References: ${BUILD_URL}, ${BUILD_NUMBER}, and More
The Email Extension Plugin supports tokens for dynamic content. Tokens are case-sensitive and must be enclosed in ${}. Common tokens: - ${BUILD_URL}: full URL to the build (e.g., http://jenkins.example.com/job/myjob/42/) - ${BUILD_NUMBER}: build number (e.g., 42) - ${JOB_NAME}: job name (e.g., myjob) - ${PROJECT_NAME}: same as JOB_NAME - ${CHANGES}: list of changes since last successful build - ${CHANGES_SINCE_LAST_SUCCESS}: detailed changes - ${FAILED_TESTS}: list of failed tests - ${ENV,var=MY_VAR}: environment variable value - ${SCRIPT,script="return jenkins.model.Jenkins.instance.displayName"}: Groovy script result
Customizing Subject and Body In the 'Extended E-mail Notification' section, you can set: - Default Subject: e.g., 'Build ${JOB_NAME} - ${BUILD_NUMBER} ${BUILD_STATUS}' - Default Body: e.g., '${BUILD_URL}
Changes: ${CHANGES}'
Per-Trigger Overrides For each trigger (Success, Failure, etc.), you can override the subject and body. This is useful for sending different messages based on result. Example for Failure trigger: - Subject: 'BUILD FAILED: ${JOB_NAME} #${BUILD_NUMBER}' - Body: 'Build failed at ${BUILD_URL}.
Failed tests: ${FAILED_TESTS}'
Token Reference Gotchas - Tokens are resolved at email-send time, not build time. So ${BUILD_URL} uses the Jenkins URL from configuration. - Some tokens like ${CHANGES} may be empty if the build is not a new build (e.g., rebuild). - Use ${BUILD_LOG_REGEX, regex='^ERROR:.*'} to include matching log lines.
Email Attachment Patterns: Including Build Artifacts
The Email Extension Plugin can attach files from the workspace or other directories. The 'Attachments' field accepts a comma-separated list of patterns (Ant-style globs). For example: - '*/target/.xml' attaches all XML files under target directories recursively. - '.log, reports/.html' attaches log files in the workspace root and HTML files in the reports subdirectory.
- File paths are relative to the workspace root. Use absolute paths if needed (e.g., '/var/log/jenkins/my.log') but ensure Jenkins has read permissions.
- Large attachments can cause email delivery failures. Most SMTP servers have a size limit (e.g., 25MB for Gmail). Compress or exclude large files.
- Use the 'Attachments Pattern' field in the 'Editable Email Notification' post-build action or in the pipeline step.
Pipeline example: ``groovy emailext( attachmentsPattern: '/target/surefire-report.xml, /target/failsafe-report.xml', subject: 'Test Reports', body: 'See attached.', to: 'qa@example.com' ) ``
Security: Be careful not to attach sensitive files like passwords or private keys. Use the 'Pre-send Script' (Groovy) to filter attachments if needed.
Per-Trigger Email Configuration: Success, Failure, Unstable, Regression, Aborted
The Email Extension Plugin allows you to configure different email behaviors for each build result. Under 'Post-build Actions > Editable Email Notification', click 'Add Trigger' to select from: - Success - Failure - Unstable - Regression - Aborted - Still Failing - Still Unstable - Fixed - Improvement - etc.
- Recipient List: who gets the email (overrides global list)
- Subject: tokenized subject
- Body: tokenized body
- Attachments Pattern: files to attach
- Send To: recipients (Developers, Requester, etc.)
- Failure: send to all developers and the build requester, include failed tests and console log excerpt.
- Success: send only to the requester with a short summary.
- Unstable: send to developers with test failure details.
- Aborted: send to requester only, no attachments.
- Recipient List: ${{DEFAULT_RECIPIENTS}}, ${{DEVELOPERS}}
- Subject: 'BUILD FAILED: ${JOB_NAME} #${BUILD_NUMBER}'
- Body: 'Build failed at ${BUILD_URL}.
Changes: ${CHANGES}
Failed tests: ${FAILED_TESTS}' - Attachments: '*/target/test-results/.xml'
Pipeline equivalent: ``groovy emailext( to: 'team@example.com', subject: "Build ${env.JOB_NAME} - ${env.BUILD_NUMBER} ${currentBuild.result}", body: "", recipientProviders: [[$class: 'DevelopersRecipientProvider']], attachBuildLog: true, compressBuildLog: true ) `` Note: attachBuildLog and compressBuildLog are useful for failure emails.
Extended Email Plugin Groovy Templates: Dynamic Content with Script
The Email Extension Plugin supports Groovy scripts for advanced customization. You can use a 'Pre-send Script' or a 'Groovy Template' for the email body.
Pre-send Script: This Groovy script runs before the email is sent. It can modify the MimeMessage object, add headers, or conditionally cancel sending. Example: ```groovy import javax.mail.Message.RecipientType import javax.mail.internet.InternetAddress
if (build.result == hudson.model.Result.SUCCESS) { // Only send to requester for success msg.setRecipients(RecipientType.TO, build.getCauses()[0].getUser()?.getProperty(hudson.model.UserProperty.class)?.getEmailAddress() ?: 'default@example.com') } ```
Groovy Template Body: Instead of plain text, you can write a Groovy template that returns a string. This is powerful for generating HTML emails with tables. Example: ``groovy def buildUrl = build.getAbsoluteUrl() def result = build.result.toString() def changes = build.getChangeSet()?.getItems()?.collect { "${it.author.displayName}: ${it.msg}" }?.join(' ') return """ <html> <body> <h2>Build ${result}</h2> <p>Job: ${build.parent.displayName} #${build.number}</p> <p><a href="${buildUrl}">${buildUrl}</a></p> <h3>Changes:</h3> <pre>${changes}</pre> </body> </html> """ ``
How to configure: 1. In 'Editable Email Notification', set 'Content Type' to 'text/html'. 2. In 'Default Body', enter your Groovy template or select 'Groovy Template File' to use a file from Jenkins home. 3. For Pre-send Script, check 'Advanced Settings' and paste the script.
Production use case: Dynamically set recipients based on build result and environment. Example script to not send emails for successful builds in a staging environment: ``groovy if (build.result == hudson.model.Result.SUCCESS && env.getEnvironment()['ENV'] == 'staging') { cancel = true } ``
Troubleshooting Email Delivery Failures: A Systematic Approach
When emails don't arrive, follow this systematic approach:
- Check Jenkins logs: Look at 'Manage Jenkins > System Log' for email-related messages. Also check 'jenkins.log' on disk. Search for 'email' or 'mail'.
- Test SMTP connectivity: From the Jenkins server, test the SMTP server directly:
- - Gmail:
openssl s_client -connect smtp.gmail.com:587 -starttls smtp - - SendGrid:
openssl s_client -connect smtp.sendgrid.net:587 -starttls smtp - - Internal:
telnet smtp.internal.com 25 - If the connection fails, it's a network issue (firewall, DNS).
- Verify credentials: Use 'Test Configuration' in Jenkins SMTP settings. If it fails, check username/password. For Gmail, ensure you're using an App Password.
- Check 'from' address: Many SMTP servers validate the 'from' address. Ensure it's a real address or one allowed by your server. Set 'Default user e-mail suffix' if needed.
- Check Jenkins URL: As mentioned, a misconfigured Jenkins URL can cause 'from' address issues. Set it to a public URL.
- Check trigger configuration: Ensure the trigger (e.g., Failure) is enabled and the recipient list is not empty. Use 'Trigger' dropdown in 'Editable Email Notification'.
- Check email logs: If your SMTP server provides logs (e.g., SendGrid Event Webhook), check them for delivery failures or bounces.
- Enable debug logging: In Jenkins, set the system property
mail.debug=trueby adding-Dmail.debug=trueto Jenkins startup arguments. This prints SMTP conversation to logs. - Use a test job: Create a simple freestyle job with a single 'emailext' step to isolate the issue.
- 'javax.mail.AuthenticationFailedException': wrong username/password.
- 'com.sun.mail.smtp.SMTPSendFailedException: 550 5.1.1 ... User unknown': recipient address invalid.
- 'com.sun.mail.smtp.SMTPSendFailedException: 554 5.7.1 ... Relay access denied': SMTP server doesn't allow relaying; check authentication and 'from' address.
Securing Email Credentials: Using Jenkins Credentials Store
Never hardcode SMTP passwords in Jenkins configuration. Use the Jenkins Credentials Store to manage secrets securely.
Steps: 1. Go to 'Manage Jenkins > Manage Credentials'. 2. Add a new credential of type 'Username with password' or 'Secret text'. 3. For SMTP, 'Username with password' is ideal: username is the SMTP user, password is the App Password or API key. 4. In 'Extended E-mail Notification' SMTP settings, check 'Use Credentials' and select the credential you created.
Pipeline binding: ``groovy withCredentials([usernamePassword(credentialsId: 'smtp-cred', usernameVariable: 'SMTP_USER', passwordVariable: 'SMTP_PASS')]) { emailext( subject: 'Build', body: 'Done', to: 'team@example.com', mimeType: 'text/plain' ) } `` Note: The emailext step does not directly use the variables; you need to configure the SMTP server in Jenkins global config to use the credential ID. The withCredentials is useful for other steps that need SMTP credentials.
- Use separate credentials for each environment (dev, staging, prod).
- Rotate credentials periodically, especially if a team member leaves.
- Audit credential usage in Jenkins logs (check 'com.cloudbees.plugins.credentials' log level).
Avoid: Storing passwords in plain text in config.xml files. If you must use the built-in mailer, at least use credentials binding in the SMTP config.
Email Notification in Pipelines: Declarative vs Scripted
In Jenkins pipelines, email notification can be integrated at various stages. The 'emailext' step is the recommended way.
Declarative Pipeline Example: ``groovy pipeline { agent any stages { stage('Build') { steps { echo 'Building...' } } } post { success { emailext( subject: "SUCCESS: ${env.JOB_NAME} #${env.BUILD_NUMBER}", body: "Build succeeded. See ${env.BUILD_URL}.", to: 'team@example.com' ) } failure { emailext( subject: "FAILURE: ${env.JOB_NAME} #${env.BUILD_NUMBER}", body: "Build failed. See ${env.BUILD_URL}.", to: 'team@example.com', attachLog: true ) } } } ``
Scripted Pipeline Example: ``groovy node { try { stage('Build') { echo 'Building...' } currentBuild.result = 'SUCCESS' } catch (Exception e) { currentBuild.result = 'FAILURE' throw e } finally { emailext( subject: "${currentBuild.result}: ${env.JOB_NAME} #${env.BUILD_NUMBER}", body: "Build ${currentBuild.result}. URL: ${env.BUILD_URL}", to: 'team@example.com' ) } } ``
Important: In declarative pipelines, the 'post' section is evaluated after the build result is set. Use 'always' to send email regardless of result, or use conditions like 'success', 'failure', 'unstable', 'changed'.
Gotcha: The 'emailext' step inherits global SMTP settings. If you need to override recipients or subject per pipeline, pass them as parameters.
Advanced: Custom Email Triggers and Content with Extended Email
Beyond the standard triggers, the Email Extension Plugin offers advanced triggers like 'Still Failing', 'Still Unstable', 'Fixed', 'Improvement', and 'Regression'. These are useful for reducing noise.
- 'Still Failing': Send an email only on the first failure, then on every nth failure (using 'Failure' trigger with 'Send to' set to 'Only individuals who wrote code' and 'Still Failing' with a different subject).
- 'Fixed': When a previously failing build succeeds, send a 'Fixed' notification with a summary of what was fixed.
- 'Regression': When a build introduces new failures compared to the previous build.
Custom Content with Groovy: You can use a Groovy script to generate the email body dynamically. For example, to include the console log only for failures: ``groovy if (build.result == hudson.model.Result.FAILURE) { return build.getLog(100) } else { return 'Build succeeded.' } ``
Token Expansion in Attachments: You can use tokens in the attachments pattern, e.g., '*/target/${BUILD_NUMBER}/.xml'. However, this is rarely needed.
Multiple Email Notifications: You can add multiple 'Editable Email Notification' post-build actions to send different emails to different audiences. For example, one for developers (with details) and one for management (with summary).
Testing Email Configuration: Sandbox and Validation Techniques
Before rolling out email notifications to production, test thoroughly.
1. Use a test SMTP server: Tools like MailHog (https://github.com/mailhog/MailHog) or FakeSMTP (https://github.com/Nilhcem/FakeSMTP) run locally and capture all emails. Configure Jenkins to use localhost:1025 (MailHog default). This lets you inspect emails without sending real ones.
2. Create a test job: A simple freestyle job with a single build step that echoes 'hello' and an 'Editable Email Notification' post-build action. Use different triggers to verify each one.
3. Validate tokens: Use the 'Preview' feature in the job configuration (requires 'Email Extension Template Preview' plugin). It shows the rendered subject and body.
4. Check attachment patterns: Create a test workspace with dummy files and verify the pattern matches correctly.
5. Use Jenkins CLI to test: You can trigger a build and check logs with: ``bash java -jar jenkins-cli.jar -s http://localhost:8080 build test-job -s -v `` Then check the console output for email-related messages.
6. Automate testing: Write a Jenkins pipeline that builds a test job and asserts that an email was sent (by checking MailHog API). Example: ``groovy stage('Test Email') { steps { build job: 'test-email-job', wait: true // Use MailHog API to check if email was received sh 'curl -s http://mailhog:8025/api/v2/messages | jq ".items[0].Content.Headers.Subject"' } } ``
The Silent SMTP Rejection: How a Misconfigured Jenkins URL Killed Notifications
- Always verify the Jenkins URL is a valid, reachable domain—it affects not only emails but also links in notifications and integration with other tools.
echo | openssl s_client -connect smtp.gmail.com:587 -starttls smtp 2>/dev/null | grep '250-AUTH'Check that password is an App Password for GmailPrint-friendly master reference covering all topics in this track.
Key takeaways
Common mistakes to avoid
6 patternsUsing regular Gmail password instead of App Password
Setting Jenkins URL to localhost
Using built-in mailer for complex pipelines
Hardcoding SMTP password in config.xml
Not testing SMTP connectivity before configuring Jenkins
Using wrong token syntax (e.g., $BUILD_URL instead of ${BUILD_URL})
Interview Questions on This Topic
How do you configure Jenkins to send email notifications using Gmail SMTP?
Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.
That's Jenkins. Mark it forged?
10 min read · try the examples if you haven't