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_addressstryes—Sender address
to_addresseslist[str]yes—Primary recipients
subjectstryes—Subject 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
filenamestryes—Name shown to the recipient
datastryes—Base64-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:

- task_name: send-availability-alert
activity:
type: email.send
input:
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>
options:
retry_policy:
timeout_sec: 30

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

- task_name: mail-report
activity:
type: email.send
input:
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:
as: 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:

- task_name: require-all-recipients
raise:
if: '{{ len(mail_result["recipients_rejected"]) > 0 }}'
type: error
error_type: EmailRejected
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 options.retry_policy.max_attempts: 1 where a duplicate would be worse than a miss.