Skip to main content

Send email

A composite action that sends an email through any SMTP server. It runs inline in the calling job, so sending a notification adds a single step — no separate run or runner. Both STARTTLS (port 587) and SSL (port 465) are supported. The SMTP password is sensitive. Store it in H2O Secure Store and pass it in as a secret input (secret: true), so the value is redacted from logs.

Action

id: send-email
name: Send Email

inputs:
smtp_host:
type: string
required: true
description: "SMTP server hostname"
smtp_port:
type: string
default: "587"
description: "SMTP server port (587 for STARTTLS, 465 for SSL)"
smtp_user:
type: string
required: true
description: "SMTP authentication username"
smtp_password:
type: string
required: true
secret: true
description: "SMTP authentication password"
from_addr:
type: string
required: true
description: "Sender email address"
to_addr:
type: string
required: true
description: "Recipient email address (comma-separated for multiple)"
subject:
type: string
required: true
description: "Email subject line"
body:
type: string
required: true
description: "Email body text"

steps:
- name: "Send email via SMTP"
timeout: "1m"
env:
SMTP_HOST: "${{ .inputs.smtp_host }}"
SMTP_PORT: "${{ .inputs.smtp_port }}"
SMTP_USER: "${{ .inputs.smtp_user }}"
SMTP_PASSWORD: "${{ .inputs.smtp_password }}"
FROM_ADDR: "${{ .inputs.from_addr }}"
TO_ADDR: "${{ .inputs.to_addr }}"
SUBJECT: "${{ .inputs.subject }}"
BODY: "${{ .inputs.body }}"
run: |
python3 -c "
import os, smtplib
from email.message import EmailMessage

msg = EmailMessage()
msg['From'] = os.environ['FROM_ADDR']
msg['To'] = os.environ['TO_ADDR']
msg['Subject'] = os.environ['SUBJECT']
msg.set_content(os.environ['BODY'])

port = int(os.environ['SMTP_PORT'])
if port == 465:
smtp = smtplib.SMTP_SSL(os.environ['SMTP_HOST'], port)
else:
smtp = smtplib.SMTP(os.environ['SMTP_HOST'], port)
smtp.starttls()

smtp.login(os.environ['SMTP_USER'], os.environ['SMTP_PASSWORD'])
smtp.send_message(msg)
smtp.quit()
print(f'Email sent to {os.environ[\"TO_ADDR\"]}')
"

Publish the action to make it callable from workflows.

Use it from your workflow

Reference your stored SMTP password with ${{ .secrets.<name> }} and pass it to the action via with:

id: train-and-email
name: Train and Email

secrets:
- name: workspaces/<workspace-id>/secrets/smtp-password
as: smtp_password

jobs:
train:
steps:
- name: Train model
run: python train.py

- uses: <workspace-id>:send-email@latest
with:
smtp_host: "smtp.gmail.com"
smtp_port: "587"
smtp_user: "workflows@example.com"
smtp_password: "${{ .secrets.smtp_password }}"
from_addr: "workflows@example.com"
to_addr: "team@example.com"
subject: "Workflow completed"
body: "The training pipeline finished successfully."

To send only when something went wrong, add an if condition such as if: failure() to the uses step.


Feedback