#!/usr/bin/env python3 """ Email Generation and Delivery for OpenClaw Daily Digest Generates HTML/text emails from aggregated content and sends via SMTP """ import json import sys import os import subprocess from datetime import datetime from pathlib import Path def load_template(template_path: str) -> str: """Load an email template file""" with open(template_path, 'r') as f: return f.read() def generate_email_content(digest_data: dict) -> tuple: """Generate HTML and plain-text email content from digest data""" # Load templates - use Spark-safe version for better cross-client compatibility html_template = load_template('/home/openclaw/.openclaw/workspace/automations/openclaw-digest/templates/email-spark-safe.html') text_template = load_template('/home/openclaw/.openclaw/workspace/automations/openclaw-digest/templates/email.txt') # Get data meta = digest_data.get('meta', {}) stats = digest_data.get('stats', {}) formatted = digest_data.get('formatted', {}) # Replace template variables for HTML html_content = html_template html_content = html_content.replace('{{DATE}}', meta.get('date', datetime.utcnow().strftime('%A, %B %d, %Y'))) html_content = html_content.replace('{{REDDIT_COUNT}}', str(stats.get('reddit_count', 0))) html_content = html_content.replace('{{NEWS_COUNT}}', str(stats.get('news_count', 0))) html_content = html_content.replace('{{TWITTER_COUNT}}', str(stats.get('twitter_count', 0))) html_content = html_content.replace('{{TOP_TOPICS}}', formatted.get('top_topics_html', '')) html_content = html_content.replace('{{REDDIT_STORIES}}', formatted.get('reddit_html', '

No Reddit posts today.

')) html_content = html_content.replace('{{NEWS_STORIES}}', formatted.get('news_html', '

No news articles today.

')) html_content = html_content.replace('{{TWITTER_STORIES}}', formatted.get('twitter_html', '')) html_content = html_content.replace('{{GENERATED_AT}}', meta.get('generated_at', datetime.utcnow().isoformat())) # Replace template variables for text text_content = text_template text_content = text_content.replace('{{DATE}}', meta.get('date', datetime.utcnow().strftime('%A, %B %d, %Y'))) text_content = text_content.replace('{{REDDIT_COUNT}}', str(stats.get('reddit_count', 0))) text_content = text_content.replace('{{NEWS_COUNT}}', str(stats.get('news_count', 0))) text_content = text_content.replace('{{TWITTER_COUNT}}', str(stats.get('twitter_count', 0))) text_content = text_content.replace('{{REDDIT_STORIES_TEXT}}', formatted.get('reddit_text', 'No Reddit posts today.')) text_content = text_content.replace('{{NEWS_STORIES_TEXT}}', formatted.get('news_text', 'No news articles today.')) text_content = text_content.replace('{{TWITTER_STORIES_TEXT}}', formatted.get('twitter_text', '')) text_content = text_content.replace('{{GENERATED_AT}}', meta.get('generated_at', datetime.utcnow().isoformat())) return html_content, text_content def save_email_files(html_content: str, text_content: str, output_dir: str) -> tuple: """Save email content to files for sending""" timestamp = datetime.utcnow().strftime('%Y%m%d_%H%M%S') html_path = os.path.join(output_dir, f'email_{timestamp}.html') text_path = os.path.join(output_dir, f'email_{timestamp}.txt') with open(html_path, 'w') as f: f.write(html_content) with open(text_path, 'w') as f: f.write(text_content) return html_path, text_path def send_email(html_path: str, text_path: str, to_email: str, from_email: str) -> bool: """Send email using the imap-smtp-email skill""" skill_path = '/home/openclaw/.openclaw/workspace/skills/imap-smtp-email' smtp_script = os.path.join(skill_path, 'scripts/smtp.js') # Read content files with open(html_path, 'r') as f: html_content = f.read() with open(text_path, 'r') as f: text_content = f.read() # Create combined HTML email (with text fallback) subject = f"šŸ¦€ OpenClaw Daily Digest - {datetime.utcnow().strftime('%B %d, %Y')}" # Write HTML to temp file for --html-file temp_html = os.path.join(os.path.dirname(html_path), 'temp_email.html') with open(temp_html, 'w') as f: f.write(html_content) # Build the send command cmd = [ 'node', smtp_script, 'send', '--to', to_email, '--from', from_email, '--subject', subject, '--html-file', temp_html ] print(f"šŸ“§ Sending email to {to_email}...") print(f" Subject: {subject}") try: result = subprocess.run( cmd, capture_output=True, text=True, cwd=skill_path, timeout=60 ) if result.returncode == 0: print("āœ… Email sent successfully!") return True else: print(f"āŒ Email send failed:") print(f" stdout: {result.stdout}") print(f" stderr: {result.stderr}") return False except subprocess.TimeoutExpired: print("āŒ Email send timed out") return False except Exception as e: print(f"āŒ Error sending email: {e}") return False finally: # Clean up temp file if os.path.exists(temp_html): os.remove(temp_html) def main(): """Main function to generate and send digest email""" # Parse arguments digest_file = sys.argv[1] if len(sys.argv) > 1 else '/home/openclaw/.openclaw/workspace/automations/openclaw-digest/output/digest.json' to_email = sys.argv[2] if len(sys.argv) > 2 else 'anthony@martinwa.org' from_email = sys.argv[3] if len(sys.argv) > 3 else 'krillyclaw@gmail.com' output_dir = '/home/openclaw/.openclaw/workspace/automations/openclaw-digest/output' print("šŸ¦€ OpenClaw Daily Digest - Email Generator") print("=" * 50) # Load digest data print(f"\nšŸ“‚ Loading digest from: {digest_file}") try: with open(digest_file, 'r') as f: digest_data = json.load(f) except Exception as e: print(f"āŒ Error loading digest: {e}") return False # Generate email content print("\nāœļø Generating email content...") html_content, text_content = generate_email_content(digest_data) # Save to files html_path, text_path = save_email_files(html_content, text_content, output_dir) print(f" HTML: {html_path}") print(f" Text: {text_path}") # Send email print("\nšŸ“¤ Sending email...") success = send_email(html_path, text_path, to_email, from_email) # Save metadata metadata = { "sent_at": datetime.utcnow().isoformat(), "to": to_email, "from": from_email, "success": success, "html_file": html_path, "text_file": text_path, "stats": digest_data.get('stats', {}) } metadata_path = os.path.join(output_dir, 'last_send_metadata.json') with open(metadata_path, 'w') as f: json.dump(metadata, f, indent=2) print(f"\n{'āœ…' if success else 'āŒ'} Email delivery {'successful' if success else 'failed'}") return success if __name__ == '__main__': success = main() sys.exit(0 if success else 1)