Skip to main content

Email Activities

One activity, email.send, delivers a message over SMTP. Use it for notifications and alerts at the end of a workflow.

Setup

Every connection setting can come from the activity, from the worker's configuration, or from an environment variable, resolved in that order:

SettingInput fieldEnvironment variableFallback
Hostsmtp_hostMOCO_SMTP_HOST
Portsmtp_portMOCO_SMTP_PORT465 with use_ssl, otherwise 587
UsernameusernameMOCO_SMTP_USERNAME
Passwordpassword_secret_keyMOCO_SMTP_PASSWORD

So a deployment with SMTP configured on the worker only needs the message fields: from_address, to_addresses, subject and a body.

password_secret_key is the name of a secret, not the password. Authentication is attempted only when a username and a password both resolve.


email.send

Sends one message, optionally with an HTML body, CC/BCC recipients and attachments.

Input

FieldTypeRequiredDefaultDescription
from_addressstryesSender address
to_addresseslist[str]yesPrimary recipients
subjectstryesSubject line
body_textstrnonullPlain-text body
body_htmlstrnonullHTML body. Set both bodies to send a multipart message
cc_addresseslist[str]nonullCC recipients
bcc_addresseslist[str]nonullBCC recipients
reply_tostrnonullReply-To address
custom_headersdict[str, str]nonullExtra message headers
attachmentslist[EmailAttachment]nonullFiles to attach
smtp_hoststrnoMOCO_SMTP_HOSTSMTP server host
smtp_portintnosee SetupSMTP server port
usernamestrnoMOCO_SMTP_USERNAMESMTP username
password_secret_keystrnoMOCO_SMTP_PASSWORDSecret name holding the SMTP password
use_tlsboolnotrueSTARTTLS on a plain connection
use_sslboolnofalseImplicit TLS from the first byte (usually port 465)
timeoutintno30Socket timeout in seconds, independent of the activity timeout

EmailAttachment

Workers have no shared filesystem with the caller, so attachment content travels inline as base64.

FieldTypeRequiredDefaultDescription
filenamestryesName shown to the recipient
datastryesBase64-encoded file content
content_typestrno"application/octet-stream"MIME type

Output

FieldTypeDescription
successboolTrue when at least one recipient was accepted — see the caution below
message_idstr | nullMessage-ID of the sent message
recipients_acceptedlist[str]Addresses the server accepted
recipients_rejecteddictAddresses the server refused, mapped to the server's reason

Examples

An alert with both bodies, from moco-examples/web-crawler-demo/src/web-crawler-demo.yaml:

- activity:
type: email.send
name: send_availability_alert
retry_policy:
timeout_sec: 30
input_data:
smtp_host: "{{ smtp_host }}"
smtp_port: "{{ smtp_port }}"
username: "{{ smtp_username }}"
password_secret_key: "{{ smtp_password_key }}"
from_address: "{{ alert_from }}"
to_addresses: "{{ alert_to }}"
subject: "NYU tour availability found ({{ len(found_dates) }} date(s))"
body_text: >-
Tour availability was found for NYU in-person visits.
body_html: >-
<h2>NYU Visit Tour availability found</h2>

Attaching a generated report, relying on the worker's SMTP configuration:

- activity:
name: mail-report
type: email.send
input_data:
from_address: "reports@example.com"
to_addresses: ["ops@example.com"]
subject: "Daily report {{ run_date }}"
body_text: "The report for {{ run_date }} is attached."
attachments:
- filename: "report-{{ run_date }}.csv"
content_type: "text/csv"
data: "{{ base64.b64encode(report_csv.encode()).decode() }}"
output_name: mail_result # -> success, message_id, recipients_accepted, recipients_rejected
success does not mean every recipient got it

success is true when the server accepted any recipient. If you need all-or-nothing delivery, check that recipients_rejected is empty:

- abort:
condition: "{{ len(mail_result['recipients_rejected']) > 0 }}"
type: raise
message: "Rejected recipients: {{ mail_result['recipients_rejected'] }}"
Retries re-send the message

email.send defaults to 2 attempts and there is no deduplication — a retry after a timeout can deliver the message twice. Set max_attempts: 1 where a duplicate would be worse than a miss.