189 lines
7.2 KiB
Python
189 lines
7.2 KiB
Python
#!/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', '<p>No Reddit posts today.</p>'))
|
|
html_content = html_content.replace('{{NEWS_STORIES}}', formatted.get('news_html', '<p>No news articles today.</p>'))
|
|
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)
|