Skip to main content

Overview

The notification system sends automated email alerts for SyftBox events:
  • New job submissions
  • Job approvals and completions
  • Peer connection requests
  • Peer request approvals

Gmail Integration

Authentication

from syft_client.notifications import GmailAuth

auth = GmailAuth()
credentials = auth.setup_auth('~/.syft-creds/credentials.json')
OAuth Scopes:
  • https://www.googleapis.com/auth/gmail.send - Send emails on behalf of user

GmailAuth

setup_auth
method
One-time OAuth2 authentication setupParameters:
  • credentials_path: Path - Path to Google Cloud credentials.json
Returns: Credentials - Google OAuth credentials objectOpens browser for OAuth consent flow, saves token for future use.
load_credentials
method
Load and refresh existing credentialsParameters:
  • token_path: Path - Path to saved Gmail token
Returns: Credentials - Valid credentials (auto-refreshes if expired)

Obtaining credentials.json

  1. Go to Google Cloud Console
  2. Create a new project or select existing
  3. Enable Gmail API
  4. Create OAuth 2.0 Client ID (Desktop app type)
  5. Download credentials as credentials.json
  6. Place at ~/.syft-creds/credentials.json

Email Sender

GmailSender

from syft_client.notifications import GmailSender
from syft_client.notifications import GmailAuth

auth = GmailAuth()
creds = auth.load_credentials('~/.syft-creds/gmail_token.json')
sender = GmailSender(creds, use_html=True)
__init__
constructor
Initialize Gmail senderParameters:
  • credentials: Credentials - Google OAuth credentials
  • use_html: bool = True - Send HTML emails (falls back to plain text)

Job Notifications

notify_new_job
method
Notify Data Owner about new job submissionParameters:
  • do_email: str - Data Owner email
  • job_name: str - Name of the job
  • submitter: str - Data Scientist email
  • timestamp: Optional[datetime] - Submission time
  • job_url: Optional[str] - URL to view job
Returns: bool - True if sent successfully
sender.notify_new_job(
    do_email="[email protected]",
    job_name="income_prediction",
    submitter="[email protected]"
)
notify_job_approved
method
Notify Data Scientist that their job was approvedParameters:
  • ds_email: str - Data Scientist email
  • job_name: str - Name of approved job
  • job_url: Optional[str] - URL to view job
Returns: bool - True if sent successfully
notify_job_executed
method
Notify Data Scientist that their job completedParameters:
  • ds_email: str - Data Scientist email
  • job_name: str - Name of completed job
  • duration: Optional[int] - Execution time in seconds
  • results_url: Optional[str] - URL to download results
Returns: bool - True if sent successfully

Peer Notifications

notify_new_peer_request_to_do
method
Notify Data Owner about new peer requestParameters:
  • do_email: str - Data Owner email
  • ds_email: str - Data Scientist email
  • peer_url: Optional[str] - URL to review request
Returns: bool - True if sent successfully
notify_peer_request_sent
method
Notify Data Scientist that their request was sentParameters:
  • ds_email: str - Data Scientist email
  • do_email: str - Data Owner email
Returns: bool - True if sent successfully
notify_peer_request_granted
method
Notify Data Scientist that their peer request was acceptedParameters:
  • ds_email: str - Data Scientist email
  • do_email: str - Data Owner email
Returns: bool - True if sent successfully

Generic Email

send_email
method
Send custom email with HTML and plain textParameters:
  • to_email: str - Recipient email
  • subject: str - Email subject
  • body_text: str - Plain text body (fallback)
  • body_html: Optional[str] - HTML body (preferred)
Returns: bool - True if sent successfully
sender.send_email(
    to_email="[email protected]",
    subject="Custom Alert",
    body_text="This is a plain text message",
    body_html="<h1>This is HTML</h1>"
)

Email Templates

TemplateRenderer

Renders Jinja2 templates for HTML emails.
from syft_client.notifications import TemplateRenderer

renderer = TemplateRenderer()
html = renderer.render('emails/new_job.html', {
    'job_name': 'analysis_job',
    'submitter': '[email protected]',
    'timestamp': datetime.now()
})
__init__
constructor
Parameters:
  • templates_dir: Optional[Path] - Custom templates directory (defaults to built-in)
render
method
Render template with context dataParameters:
  • template_name: str - Template file name (e.g., “emails/new_job.html”)
  • context: dict - Template variables
Returns: str - Rendered HTML

Built-in Templates

  • emails/new_job.html - New job notification
  • emails/job_approved.html - Job approval notification
  • emails/job_executed.html - Job completion notification
  • emails/new_peer_request.html - Peer request notification
  • emails/peer_granted.html - Peer approval notification

Template Filters

{{ timestamp|datetime }}  {# Format: January 15, 2024 at 03:30 PM #}
{{ duration|duration }}    {# Format: 2 hours 15 minutes 30 seconds #}

State Management

JsonStateManager

Tracks notification history to prevent duplicate emails.
from syft_client.notifications import JsonStateManager

state = JsonStateManager('~/.syft-creds/state.json')

# Check if already notified
if not state.was_notified('job_123', 'new'):
    sender.notify_new_job(...)
    state.mark_notified('job_123', 'new')
__init__
constructor
Parameters:
  • state_file: Path - Path to JSON state file
was_notified
method
Check if entity was already notifiedParameters:
  • entity_id: str - Unique identifier (job name, peer email)
  • event_type: str - Event type (“new”, “approved”, “executed”)
Returns: bool - True if already notified
mark_notified
method
Mark entity as notifiedParameters:
  • entity_id: str - Unique identifier
  • event_type: str - Event type
get_data
method
Get arbitrary data from stateParameters:
  • key: str - Data key
  • default: Optional[Any] - Default if key doesn’t exist
Returns: Any - Stored value
set_data
method
Store arbitrary data in stateParameters:
  • key: str - Data key
  • value: Any - JSON-serializable value

Configuration

syft-bg Configuration

Stored at ~/.syft-creds/config.yaml:
do_email: [email protected]
syftbox_root: ~/SyftBox

notify:
  interval: 30  # Check interval in seconds
  monitor_jobs: true
  monitor_peers: true

CLI Commands

# Initialize notification service
syft-bg init --email [email protected]

# Enable specific notifications
syft-bg init --notify-jobs --notify-peers

# Start background service
syft-bg start

# View service status
syft-bg status

# View logs
syft-bg logs notify
syft-bg logs notify -f  # Follow logs

# Stop service
syft-bg stop

Timeout Configuration

Gmail API requests have a 2-minute timeout:
# From gmail_sender.py
GOOGLE_API_TIMEOUT = 120  # seconds
Prevents indefinite hangs on network issues.

Error Handling

try:
    success = sender.notify_new_job(
        do_email="[email protected]",
        job_name="analysis",
        submitter="[email protected]"
    )
    
    if not success:
        print("Failed to send notification")
except Exception as e:
    print(f"Error: {e}")
Notification methods return False on failure rather than raising exceptions.

Best Practices

  1. Always use state management - Prevents duplicate notifications
  2. Enable HTML emails - Better user experience with templates
  3. Set reasonable intervals - 30s for notifications is recommended
  4. Monitor logs - Use syft-bg logs notify -f to debug issues
  5. Handle OAuth expiry - Credentials auto-refresh if refresh token is valid

See Also

Build docs developers (and LLMs) love