← All ivy-nodes
IVYXSTUDIO · IVY NODE
E

Email Builder

ivy.node.email-builder · v0.2.0

ivyx✓

Builds an email message with a plain-text body and an optional attachment, given as bytes or as a file path, and returns it with its attachment count. The message is a handle for email-provider in the same run.

#Email

Inputs

FieldTypeDescription
from_emailrequiredstringSender address.
to_emailrequiredstringRecipient address.
subjectrequiredstringSubject line.
bodyrequiredstringPlain-text body.
file_bufferNot declaredAn attachment's bytes; give file_name with it.
file_namestringThe attachment's file name; give file_buffer with it, or leave it out with file_path to use the file's own name.
file_pathstringAn attachment as a file path, relative to the workspace; an alternative to file_buffer.

Outputs

FieldTypeDescription
email_messagerequiredNot declaredThe message, for email-provider.
attachment_countrequiredintegerHow many attachments the message carries.

Source

python

inp = __ivy_ctx__["nodes"][__ivy_node_id__]["input"]

import os
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

file_buffer = inp.get("file_buffer")
file_path = inp.get("file_path")
file_name = inp.get("file_name")
if file_buffer is not None and file_path:
    raise ValueError("Give file_buffer or file_path, not both.")
if (file_buffer is None) != (not file_name) and not file_path:
    raise ValueError("file_buffer and file_name must be provided together.")

email_message = MIMEMultipart()
email_message["From"] = inp["from_email"]
email_message["To"] = inp["to_email"]
email_message["Subject"] = inp["subject"]
email_message.attach(MIMEText(inp["body"], "plain"))

data = None
if file_path:
    if not os.path.isfile(file_path):
        raise FileNotFoundError(f"There is no file at {file_path!r} to attach.")
    with open(file_path, "rb") as handle_:
        data = handle_.read()
    file_name = file_name or os.path.basename(file_path)
elif file_buffer is not None:
    file_buffer.seek(0)
    data = file_buffer.getvalue()
if data is not None:
    attachment = MIMEBase("application", "octet-stream")
    attachment.set_payload(data)
    encoders.encode_base64(attachment)
    attachment.add_header("Content-Disposition", f'attachment; filename="{file_name}"')
    email_message.attach(attachment)

out = __ivy_ctx__["nodes"][__ivy_node_id__]["output"]
out["email_message"] = email_message
out["attachment_count"] = max(0, len(email_message.get_payload()) - 1)

Tests

Requires: python:3.9

  • plain

    A message with no attachment carries only its body.

  • with-attachment

    A buffer and a file name make one attachment.

  • with-path

    A file path makes one attachment, named after the file.

  • missing-path

    A path with no file behind it fails the step and names it.

  • buffer-without-name

    An attachment with no file name is refused.