← All ivy-nodes
IVYXSTUDIO · IVY NODE
E

Email Builder

ivy.node.email-builder · v0.0.0

ivyx

Builds email messages with optional attachments using Python's email library. This node creates MIMEMultipart email objects with sender, recipient, subject, body, and optional file attachments. The output email_message is used with email-provider node to send emails via SMTP. Supports attachments from BytesIO buffers (e.g., from csv-from-data-frame or chart generators). Use this node to prepare emails before sending them.

#Email

Inputs

FieldTypeDescription
from_emailrequiredstringSender's email address.
to_emailrequiredstringRecipient's email address.
subjectrequiredstringSubject of the email.
bodyrequiredstringBody content of the email.
file_bufferNot declaredOptional buffer containing attachment bytes (e.g. io.BytesIO). (Node reference)
file_nameNot declaredOptional attachment file name (required if file_buffer is provided).
featuresobjectFeatures
labelsobjectLabels

Outputs

FieldTypeDescription
email_messagerequiredobjectCustom type: MIMEMultipart
featuresobjectFeatures
labelsobjectLabels

Source

python

# Input preparation
inp = __ivy_ctx__["nodes"][__ivy_node_id__]["input"]
from_email = inp["from_email"]
to_email = inp["to_email"]
subject = inp["subject"]
body = inp["body"]
file_buffer = inp.get("file_buffer")
file_name = inp.get("file_name")

# Compute
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
from typing import Optional
import io

class EmailBuilder:
    """A class to build and handle email messages with optional attachments."""

    def __init__(
        self,
        from_email: str,
        to_email: str,
        subject: str,
        body: str,
        file_buffer: Optional[io.BytesIO] = None,
        file_name: Optional[str] = None,
    ):
        self.from_email = from_email
        self.to_email = to_email
        self.subject = subject
        self.body = body
        self.file_buffer = file_buffer
        self.file_name = file_name
        self.email_message = MIMEMultipart()

    def build_email(self):
        self.email_message['From'] = self.from_email
        self.email_message['To'] = self.to_email
        self.email_message['Subject'] = self.subject
        self.email_message.attach(MIMEText(self.body, 'plain'))

        if (self.file_buffer is None) != (self.file_name is None):
            raise ValueError('file_buffer and file_name must be provided together (or both be None).')

        if self.file_buffer and self.file_name:
            self.file_buffer.seek(0)
            attachment = MIMEBase('application', 'octet-stream')
            attachment.set_payload(self.file_buffer.getvalue())
            encoders.encode_base64(attachment)
            attachment.add_header(
                'Content-Disposition',
                f'attachment; filename="{self.file_name}"'
            )
            self.email_message.attach(attachment)

    def get_email(self):
        return self.email_message

email_builder = EmailBuilder(
    from_email=from_email,
    to_email=to_email,
    subject=subject,
    body=body,
    file_buffer=file_buffer,
    file_name=file_name
)

email_builder.build_email()
email_message = email_builder.get_email()

# Output collection (runner reads __ivy_ctx__)
out = __ivy_ctx__["nodes"][__ivy_node_id__]["output"]
out["email_message"] = email_message

Tests

Requires: python:3.11

  • basic-no-attachment

    Build email without attachment (should succeed).

    emailbasic
  • error-mismatched-attachment-pair

    file_buffer without file_name should raise an error.

    error-handlingvalidation