TouchDesigner
Send emails from TouchDesigner projects using the Veil Mail Python SDK with a threaded wrapper that avoids blocking the cook thread.
Prerequisites
- TouchDesigner 2023.11000+ (Python 3.11)
- A Veil Mail API key
- A verified domain
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 veilmailWindows
"C:\Program Files\Derivative\TouchDesigner\bin\python.exe" -m pip install veilmailAlternatively, install from source:
pip install git+https://github.com/Resonia-Health/veilmail-python.git2. 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:
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
- Create a Base COMP and name it
veilmail - Open the component's parameters and add a custom page named VeilMail
- 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) - Set the Extension to
VeilMailExt.pywith classVeilMailExt
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.
# 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 callbacksTemplate Emails
Send emails using pre-built templates with dynamic data:
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:
# 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:
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
| File | Purpose |
|---|---|
veilmail_td.py | Threaded wrapper with TD-friendly API |
VeilMailExt.py | COMP extension class for parameter-driven UI |
VeilMailCallbacks.py | DAT execute callbacks for buttons |
VeilMailPars.json | Parameter definitions for .tox recreation |