TouchDesigner

Send emails from TouchDesigner projects using the Veil Mail Python SDK with a threaded wrapper that avoids blocking the cook thread.

Prerequisites

1. Install the Python SDK

Install veilmail into TouchDesigner's embedded Python. Open a terminal and run:

macOS

/Applications/TouchDesigner.app/Contents/Frameworks/Python.framework/Versions/Current/bin/python3 -m pip install veilmail

Windows

"C:\Program Files\Derivative\TouchDesigner\bin\python.exe" -m pip install veilmail

Alternatively, install from source:

pip install git+https://github.com/Resonia-Health/veilmail-python.git

2. Add the TD Wrapper

Download veilmail_td.py from the TouchDesigner integration package and place it in your project folder or TouchDesigner's Python path.

The wrapper runs HTTP calls on background threads so they never block the cook thread.

3. Send from a Script

The simplest way to send emails is from a Text DAT or Script CHOP callback:

send_email.py
from veilmail_td import VeilMailTD

def on_success(key, result):
    print(f"[VeilMail] {key}: {result}")

def on_error(key, error):
    print(f"[VeilMail] Error: {error}")

vm = VeilMailTD(
    api_key="veil_live_xxxxx",
    on_success=on_success,
    on_error=on_error,
)

# Non-blocking send (runs on background thread)
vm.send_email(
    from_addr="hello@yourdomain.com",
    to="user@example.com",
    subject="Hello from TouchDesigner!",
    html="<h1>Sent from TD</h1>",
)

4. Component Setup (Optional)

For a parameter-driven UI, create a Base COMP with custom parameters and the VeilMailExt extension class:

Create the Component

  1. Create a Base COMP and name it veilmail
  2. Open the component's parameters and add a custom page named VeilMail
  3. Add parameters: Apikey (Str), Baseurl (Str, default: https://api.veilmail.xyz), From (Str), To (Str), Subject (Str), Body (Str), Templateid (Str), Send (Pulse), Status (Str, read-only)
  4. Set the Extension to VeilMailExt.py with class VeilMailExt

Extension Class

The extension reads parameters from the component and sends emails when the Send pulse is triggered. See VeilMailExt.py in the integration package.

VeilMailExt.py
# In the VeilMailExt extension class:

def SendEmail(self):
    """Triggered by the Send pulse parameter."""
    client = self.Client  # Lazy-initializes from Apikey/Baseurl params
    if not client:
        return

    from_addr = self.ownerComp.par.From.eval()
    to = self.ownerComp.par.To.eval()
    subject = self.ownerComp.par.Subject.eval()
    body = self.ownerComp.par.Body.eval()

    self._set_status("Sending...")
    client.send_email(
        from_addr=from_addr,
        to=to,
        subject=subject or "(no subject)",
        html=body,
    )
    # Status updates automatically via callbacks

Template Emails

Send emails using pre-built templates with dynamic data:

template_email.py
vm.send_email(
    from_addr="show@yourdomain.com",
    to="audience@example.com",
    subject="Performance Tonight!",
    template_id="template_xxxxx",
    template_data={
        "show_name": "Interactive Light Show",
        "venue": "Gallery XYZ",
        "date": "2026-02-15",
    },
)

Synchronous Mode

For scripts that run outside the cook thread (e.g., startup scripts), you can use the synchronous API:

sync_send.py
# WARNING: Blocks the calling thread. Only use outside the cook thread.
result = vm.send_email_sync(
    from_addr="hello@yourdomain.com",
    to="user@example.com",
    subject="Sync send",
    html="<p>This blocks until complete.</p>",
)
print(f"Sent: {result['id']}")

Do not use synchronous methods during the cook cycle. They block the main thread and will cause frame drops. Use the default async methods instead.

Error Handling

Errors are delivered to your on_error callback:

error_handling.py
def on_error(key, error):
    if hasattr(error, 'status_code'):
        if error.status_code == 429:
            print(f"Rate limited. Retry after {error.retry_after}s")
        elif error.status_code == 422:
            print(f"PII detected: {error.pii_types}")
        else:
            print(f"API error ({error.status_code}): {error.message}")
    else:
        print(f"Network error: {error}")

vm = VeilMailTD(
    api_key="veil_live_xxxxx",
    on_error=on_error,
)

Security

Direct API key usage is safe in TouchDesigner

TouchDesigner installations are operator-controlled (not distributed to end users). The API key stays on your machine or controlled show hardware. There is no need for a server proxy.

  • Store API keys in environment variables or a local config file
  • Use test keys (veil_test_...) during development
  • Don't commit API keys to version control

Integration Package Files

FilePurpose
veilmail_td.pyThreaded wrapper with TD-friendly API
VeilMailExt.pyCOMP extension class for parameter-driven UI
VeilMailCallbacks.pyDAT execute callbacks for buttons
VeilMailPars.jsonParameter definitions for .tox recreation