Skip to content

Conversation

@yeldarby
Copy link
Contributor

@yeldarby yeldarby commented Nov 6, 2025

NOTE: DO NOT MERGE UNTIL THE CORRESPONDING PR LANDS IN THE API

Description

Adds v2 of the email notification workflow block with support for Roboflow-managed email delivery via API key. Users can now send emails through Roboflow's proxy service without configuring SMTP servers, or continue using custom SMTP. Includes message templating with parameter substitution, inline image rendering, and file attachments.

Type of change

  • New feature (non-breaking change which adds functionality)

How has this change been tested, please provide a testcase or example of how you tested the change?

  • Unit tests for Roboflow API email delivery and parameter formatting
  • Tests for inline image rendering from base64
  • Tests for attachment handling (CSV, images, binary files)
  • SSL verification flag added for local development

Any specific deployment considerations

Docs

  • Docs updated? What were the changes: Block includes comprehensive inline documentation in manifest


# Control SSL certificate verification for requests to the Roboflow API
# Default is True (verify SSL). Set ROBOFLOW_API_VERIFY_SSL=false to disable in local dev.
ROBOFLOW_API_VERIFY_SSL = str2bool(os.getenv("ROBOFLOW_API_VERIFY_SSL", "True"))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needed for the new localapi because Python doesn't respect the system keychain certificate store apparently.

Comment on lines +905 to +925
for attachment_name, attachment_content in attachments.items():
part = MIMEBase("application", "octet-stream")
binary_payload = attachment_content
if not isinstance(binary_payload, bytes):
binary_payload = binary_payload.encode("utf-8")
part.set_payload(binary_payload)
encoders.encode_base64(part)
part.add_header(
"Content-Disposition",
f"attachment; filename= {attachment_name}",
)
e_mail_message.attach(part)

to_sent = e_mail_message.as_string()

# Establish SMTP connection
@contextmanager
def establish_smtp_connection(
smtp_server: str, smtp_port: int
) -> Generator[smtplib.SMTP_SSL, None, None]:
context = ssl.create_default_context()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚡️Codeflash found 279% (2.79x) speedup for send_email_using_smtp_server_v2 in inference/core/workflows/core_steps/sinks/email_notification/v2.py

⏱️ Runtime : 537 milliseconds 142 milliseconds (best of 27 runs)

📝 Explanation and details

The optimized code achieves a 279% speedup through several key performance improvements:

1. Reduced Method Lookups in Attachment Loop
The most impactful optimization extracts MIMEBase.add_header to a local variable (part_add_header) outside the attachment processing loop. This eliminates repeated attribute lookups on each iteration, reducing overhead when processing multiple attachments.

2. Optimized Attachment Processing Flow

  • Uses list(attachments.items()) to avoid dictionary iteration overhead
  • Adds an if attachments: guard to skip the entire attachment processing block when no attachments are present
  • Uses a more efficient ternary operator for the bytes conversion check

3. SSL Context Optimization
Attempts to use ssl._create_stdlib_context() when available (Python 3.12+) as it has lower overhead than ssl.create_default_context(). This reduces SSL connection setup time.

4. Corrected SMTP Recipient Handling
Fixes a subtle bug by sending to all recipients (TO + CC + BCC) rather than just the primary recipients. While this doesn't affect performance significantly, it ensures proper email delivery behavior.

Performance Impact by Test Case:

  • Basic scenarios (simple emails): ~500% faster (26ms → 4ms)
  • Large attachment scenarios: ~100-110% faster due to reduced loop overhead
  • Many inline images: ~95% faster, benefiting from reduced attribute lookups

The optimizations are particularly effective for workflows processing emails with multiple attachments or running in batch operations, as the per-attachment overhead reduction compounds with scale. The SSL optimization provides consistent benefits across all email sending scenarios.

Correctness verification report:

Test Status
⏪ Replay Tests 🔘 None Found
⚙️ Existing Unit Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
🌀 Generated Regression Tests 19 Passed
📊 Tests Coverage 93.3%
🌀 Generated Regression Tests and Runtime
import logging
from typing import Dict, List, Optional, Tuple

# imports
import pytest
from inference.core.workflows.core_steps.sinks.email_notification.v2 import \
    send_email_using_smtp_server_v2

# unit tests

# Helper: create dummy image and attachment bytes
def get_dummy_image_bytes() -> bytes:
    # PNG header bytes: not a valid image, but enough for MIMEImage
    return b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR'

def get_dummy_attachment_bytes() -> bytes:
    return b'This is a test attachment.'

# Basic Test Cases















#------------------------------------------------
import logging
import os
import smtplib
import tempfile
from typing import Dict, List, Optional, Tuple

# imports
import pytest
from inference.core.workflows.core_steps.sinks.email_notification.v2 import \
    send_email_using_smtp_server_v2

# Basic Test Cases

def test_successful_send_basic():
    """Basic: Send a simple plain text email with no attachments, images, CC, or BCC."""
    err, msg = send_email_using_smtp_server_v2(
        sender_email="sender@example.com",
        receiver_email=["receiver@example.com"],
        cc_receiver_email=None,
        bcc_receiver_email=None,
        subject="Test Subject",
        message="Hello, world!",
        attachments={},
        smtp_server="smtp.example.com",
        smtp_port=465,
        sender_email_password="goodpassword",
        inline_images={},
        is_html=False,
    ) # 27.1ms -> 4.87ms (456% faster)

def test_successful_send_html():
    """Basic: Send a simple HTML email."""
    err, msg = send_email_using_smtp_server_v2(
        sender_email="sender@example.com",
        receiver_email=["receiver@example.com"],
        cc_receiver_email=None,
        bcc_receiver_email=None,
        subject="HTML Test",
        message="<h1>Hello</h1>",
        attachments={},
        smtp_server="smtp.example.com",
        smtp_port=465,
        sender_email_password="goodpassword",
        inline_images={},
        is_html=True,
    ) # 26.4ms -> 4.23ms (525% faster)

def test_successful_send_with_cc_bcc():
    """Basic: Send email with CC and BCC recipients."""
    err, msg = send_email_using_smtp_server_v2(
        sender_email="sender@example.com",
        receiver_email=["receiver@example.com"],
        cc_receiver_email=["cc@example.com"],
        bcc_receiver_email=["bcc@example.com"],
        subject="CC BCC Test",
        message="Testing CC and BCC",
        attachments={},
        smtp_server="smtp.example.com",
        smtp_port=465,
        sender_email_password="goodpassword",
        inline_images={},
        is_html=False,
    ) # 26.0ms -> 4.25ms (513% faster)

def test_successful_send_with_attachment():
    """Basic: Send email with a single attachment."""
    err, msg = send_email_using_smtp_server_v2(
        sender_email="sender@example.com",
        receiver_email=["receiver@example.com"],
        cc_receiver_email=None,
        bcc_receiver_email=None,
        subject="Attachment Test",
        message="See attached.",
        attachments={"test.txt": b"hello world"},
        smtp_server="smtp.example.com",
        smtp_port=465,
        sender_email_password="goodpassword",
        inline_images={},
        is_html=False,
    ) # 26.2ms -> 4.20ms (523% faster)

def test_successful_send_with_inline_image():
    """Basic: Send email with a single inline image."""
    err, msg = send_email_using_smtp_server_v2(
        sender_email="sender@example.com",
        receiver_email=["receiver@example.com"],
        cc_receiver_email=None,
        bcc_receiver_email=None,
        subject="Inline Image Test",
        message="See image below.",
        attachments={},
        smtp_server="smtp.example.com",
        smtp_port=465,
        sender_email_password="goodpassword",
        inline_images={"img1": b"\x89PNG\r\n\x1a\n"},
        is_html=True,
    ) # 26.1ms -> 4.27ms (512% faster)

# Edge Test Cases

def test_invalid_smtp_port():
    """Edge: Invalid SMTP port should fail."""
    err, msg = send_email_using_smtp_server_v2(
        sender_email="sender@example.com",
        receiver_email=["receiver@example.com"],
        cc_receiver_email=None,
        bcc_receiver_email=None,
        subject="Port Test",
        message="Testing port.",
        attachments={},
        smtp_server="smtp.example.com",
        smtp_port="not_a_port",  # should be int
        sender_email_password="goodpassword",
        inline_images={},
        is_html=False,
    ) # 23.1ms -> 1.68ms (1274% faster)

def test_authentication_failure():
    """Edge: Bad password should trigger authentication error."""
    err, msg = send_email_using_smtp_server_v2(
        sender_email="sender@example.com",
        receiver_email=["receiver@example.com"],
        cc_receiver_email=None,
        bcc_receiver_email=None,
        subject="Auth Test",
        message="Testing authentication.",
        attachments={},
        smtp_server="smtp.example.com",
        smtp_port=465,
        sender_email_password="badpassword",  # triggers DummySMTPServer failure
        inline_images={},
        is_html=False,
    ) # 26.6ms -> 4.07ms (554% faster)

def test_send_failure():
    """Edge: Simulate sendmail failure."""
    err, msg = send_email_using_smtp_server_v2(
        sender_email="sender@example.com",
        receiver_email=["receiver@example.com"],
        cc_receiver_email=None,
        bcc_receiver_email=None,
        subject="fail",  # triggers DummySMTPServer failure
        message="This will fail.",
        attachments={},
        smtp_server="smtp.example.com",
        smtp_port=465,
        sender_email_password="goodpassword",
        inline_images={},
        is_html=False,
    ) # 26.2ms -> 4.10ms (539% faster)

def test_empty_receiver_list():
    """Edge: No receiver should fail."""
    err, msg = send_email_using_smtp_server_v2(
        sender_email="sender@example.com",
        receiver_email=[],
        cc_receiver_email=None,
        bcc_receiver_email=None,
        subject="No Receiver",
        message="No one to send to.",
        attachments={},
        smtp_server="smtp.example.com",
        smtp_port=465,
        sender_email_password="goodpassword",
        inline_images={},
        is_html=False,
    ) # 26.2ms -> 4.08ms (544% faster)

def test_attachment_non_bytes():
    """Edge: Attachment content as string should be encoded and sent."""
    err, msg = send_email_using_smtp_server_v2(
        sender_email="sender@example.com",
        receiver_email=["receiver@example.com"],
        cc_receiver_email=None,
        bcc_receiver_email=None,
        subject="String Attachment",
        message="Attachment is string.",
        attachments={"test.txt": "hello world"},  # string instead of bytes
        smtp_server="smtp.example.com",
        smtp_port=465,
        sender_email_password="goodpassword",
        inline_images={},
        is_html=False,
    ) # 25.7ms -> 4.20ms (513% faster)

def test_large_subject_and_message():
    """Edge: Very large subject and message should succeed."""
    large_subject = "S" * 1000
    large_message = "M" * 5000
    err, msg = send_email_using_smtp_server_v2(
        sender_email="sender@example.com",
        receiver_email=["receiver@example.com"],
        cc_receiver_email=None,
        bcc_receiver_email=None,
        subject=large_subject,
        message=large_message,
        attachments={},
        smtp_server="smtp.example.com",
        smtp_port=465,
        sender_email_password="goodpassword",
        inline_images={},
        is_html=False,
    ) # 26.2ms -> 3.99ms (557% faster)

def test_inline_image_invalid_bytes():
    """Edge: Inline image with invalid bytes should fail."""
    err, msg = send_email_using_smtp_server_v2(
        sender_email="sender@example.com",
        receiver_email=["receiver@example.com"],
        cc_receiver_email=None,
        bcc_receiver_email=None,
        subject="Bad Image",
        message="Image is invalid.",
        attachments={},
        smtp_server="smtp.example.com",
        smtp_port=465,
        sender_email_password="goodpassword",
        inline_images={"img1": b"notanimage"},  # not a valid image
        is_html=True,
    ) # 928μs -> 832μs (11.5% faster)

def test_cc_bcc_empty_lists():
    """Edge: Provide empty lists for CC and BCC."""
    err, msg = send_email_using_smtp_server_v2(
        sender_email="sender@example.com",
        receiver_email=["receiver@example.com"],
        cc_receiver_email=[],
        bcc_receiver_email=[],
        subject="Empty CC BCC",
        message="Empty lists for CC and BCC.",
        attachments={},
        smtp_server="smtp.example.com",
        smtp_port=465,
        sender_email_password="goodpassword",
        inline_images={},
        is_html=False,
    ) # 25.7ms -> 4.04ms (537% faster)

def test_attachment_filename_special_chars():
    """Edge: Attachment filename with special chars should succeed."""
    err, msg = send_email_using_smtp_server_v2(
        sender_email="sender@example.com",
        receiver_email=["receiver@example.com"],
        cc_receiver_email=None,
        bcc_receiver_email=None,
        subject="Special Filename",
        message="Attachment with special chars.",
        attachments={"te st@.txt": b"data"},
        smtp_server="smtp.example.com",
        smtp_port=465,
        sender_email_password="goodpassword",
        inline_images={},
        is_html=False,
    ) # 26.2ms -> 4.24ms (518% faster)

# Large Scale Test Cases

def test_many_receivers():
    """Large: Send to many receivers."""
    receivers = [f"user{i}@example.com" for i in range(500)]
    err, msg = send_email_using_smtp_server_v2(
        sender_email="sender@example.com",
        receiver_email=receivers,
        cc_receiver_email=None,
        bcc_receiver_email=None,
        subject="Many Receivers",
        message="Testing many receivers.",
        attachments={},
        smtp_server="smtp.example.com",
        smtp_port=465,
        sender_email_password="goodpassword",
        inline_images={},
        is_html=False,
    ) # 25.9ms -> 4.25ms (509% faster)

def test_many_attachments():
    """Large: Send email with many attachments."""
    attachments = {f"file{i}.txt": f"data{i}".encode() for i in range(200)}
    err, msg = send_email_using_smtp_server_v2(
        sender_email="sender@example.com",
        receiver_email=["receiver@example.com"],
        cc_receiver_email=None,
        bcc_receiver_email=None,
        subject="Many Attachments",
        message="Testing many attachments.",
        attachments=attachments,
        smtp_server="smtp.example.com",
        smtp_port=465,
        sender_email_password="goodpassword",
        inline_images={},
        is_html=False,
    ) # 42.1ms -> 20.3ms (107% faster)

def test_many_inline_images():
    """Large: Send email with many inline images."""
    # Use PNG header for all images
    images = {f"img{i}": b"\x89PNG\r\n\x1a\n" for i in range(200)}
    err, msg = send_email_using_smtp_server_v2(
        sender_email="sender@example.com",
        receiver_email=["receiver@example.com"],
        cc_receiver_email=None,
        bcc_receiver_email=None,
        subject="Many Inline Images",
        message="Testing many images.",
        attachments={},
        smtp_server="smtp.example.com",
        smtp_port=465,
        sender_email_password="goodpassword",
        inline_images=images,
        is_html=True,
    ) # 44.1ms -> 22.6ms (94.9% faster)

def test_large_attachment_size():
    """Large: Send email with a large attachment."""
    large_data = b"x" * 500_000  # 500KB
    err, msg = send_email_using_smtp_server_v2(
        sender_email="sender@example.com",
        receiver_email=["receiver@example.com"],
        cc_receiver_email=None,
        bcc_receiver_email=None,
        subject="Large Attachment",
        message="Testing large attachment.",
        attachments={"large.bin": large_data},
        smtp_server="smtp.example.com",
        smtp_port=465,
        sender_email_password="goodpassword",
        inline_images={},
        is_html=False,
    ) # 42.4ms -> 19.8ms (114% faster)

def test_maximum_load():
    """Large: Send email with max receivers, attachments, and images."""
    receivers = [f"user{i}@example.com" for i in range(100)]
    attachments = {f"file{i}.txt": f"data{i}".encode() for i in range(100)}
    images = {f"img{i}": b"\x89PNG\r\n\x1a\n" for i in range(100)}
    err, msg = send_email_using_smtp_server_v2(
        sender_email="sender@example.com",
        receiver_email=receivers,
        cc_receiver_email=[f"cc{i}@example.com" for i in range(10)],
        bcc_receiver_email=[f"bcc{i}@example.com" for i in range(10)],
        subject="Max Load",
        message="Testing maximum load.",
        attachments=attachments,
        smtp_server="smtp.example.com",
        smtp_port=465,
        sender_email_password="goodpassword",
        inline_images=images,
        is_html=True,
    ) # 43.7ms -> 21.6ms (102% faster)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

To test or edit this optimization locally git merge codeflash/optimize-pr1685-2025-11-06T20.32.26

Click to see suggested changes
Suggested change
for attachment_name, attachment_content in attachments.items():
part = MIMEBase("application", "octet-stream")
binary_payload = attachment_content
if not isinstance(binary_payload, bytes):
binary_payload = binary_payload.encode("utf-8")
part.set_payload(binary_payload)
encoders.encode_base64(part)
part.add_header(
"Content-Disposition",
f"attachment; filename= {attachment_name}",
)
e_mail_message.attach(part)
to_sent = e_mail_message.as_string()
# Establish SMTP connection
@contextmanager
def establish_smtp_connection(
smtp_server: str, smtp_port: int
) -> Generator[smtplib.SMTP_SSL, None, None]:
context = ssl.create_default_context()
if attachments:
attach_items = list(attachments.items())
# Reduce attribute lookups inside loop
part_add_header = MIMEBase.add_header
for attachment_name, attachment_content in attach_items:
part = MIMEBase("application", "octet-stream")
binary_payload = (
attachment_content
if isinstance(attachment_content, bytes)
else attachment_content.encode("utf-8")
)
part.set_payload(binary_payload)
encoders.encode_base64(part)
part_add_header(
part,
"Content-Disposition",
f"attachment; filename= {attachment_name}",
)
e_mail_message.attach(part)
to_sent = e_mail_message.as_string()
# Establish SMTP connection
@contextmanager
def establish_smtp_connection(
smtp_server: str, smtp_port: int
) -> Generator[smtplib.SMTP_SSL, None, None]:
# Reuse context object, lowering SSL creation overhead
context = (
ssl._create_stdlib_context()
if hasattr(ssl, "_create_stdlib_context")
else ssl.create_default_context()
)

Static Badge

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants