This is a template ability that demonstrates how to send emails programmatically through OpenHome using the built-in send_email() function in CapabilityWorker. Perfect for building abilities that need to send notifications, reports, or automated emails.
Every OpenHome Agent already has an LLM that can draft emails conversationally. But the LLM can't actually send emails on its own. This template shows you how to:
- Actually send emails — not just draft them in conversation
- Automate email workflows — send reports, alerts, confirmations
- Integrate with external systems — trigger emails based on API data or user actions
- Attach files — send documents, images, or generated reports
Key insight: If all you need is help writing an email, use the Agent's LLM. If you need to actually send an email automatically, build an ability.
Examples of abilities you could create with this template:
- Daily digest emailer — Send morning briefings or summaries
- Alert system — Email notifications when conditions are met
- Report generator — Create and email weekly reports
- Reminder system — Send scheduled reminders via email
- File sharer — Email documents or attachments on command
- Multi-recipient broadcasts — Send emails to groups with CC
- Form submission handler — Email form responses to recipients
For Gmail accounts, you'll need an app-specific password:
- Enable 2-Factor Authentication on your Gmail account
- Go to Google Account → Security → App passwords
- Generate a new app password for "Mail"
- Copy the 16-character password (spaces don't matter)
- Use this password in
SENDER_PASSWORD(not your regular Gmail password)
Why? Gmail doesn't allow regular passwords for SMTP access from apps. App passwords are safer and can be revoked independently.
The template is configured for Gmail by default:
- Host:
smtp.gmail.com - Port:
465(SSL)
For other email providers:
| Provider | SMTP Host | Port | Security |
|---|---|---|---|
| Gmail | smtp.gmail.com | 465 | SSL |
| Outlook | smtp-mail.outlook.com | 587 | TLS |
| Yahoo | smtp.mail.yahoo.com | 465 | SSL |
| Office 365 | smtp.office365.com | 587 | TLS |
| Custom | Your SMTP server | Varies | Check provider |
Place attachment files in your ability's directory. The template looks for testfile.txt — replace with your own files.
Update these hardcoded values in email_sender():
# ── Hardcoded config ──────────────────────────────────────
HOST = "smtp.gmail.com" # Your SMTP server
PORT = 465 # 465 for SSL, 587 for TLS
SENDER_EMAIL = "your_email@gmail.com" # Your email address
SENDER_PASSWORD = "xxxx xxxx xxxx xxxx" # Your app password
RECEIVER_EMAIL = "recipient@example.com" # Recipient's email
SUBJECT = "Test Email" # Email subject line
BODY = "Hello! This is a test." # Email body text
ATTACHMENTS = ["testfile.txt"] # Files to attach (optional)
# ─────────────────────────────────────────────────────────The template uses CapabilityWorker.send_email() — OpenHome's built-in email function.
Function Signature:
def send_email(
self,
host: str,
port: int,
sender_email: str,
sender_password: str,
receiver_email: str,
cc_emails: list,
subject: str,
body: str,
attachment_paths: list = []
) -> boolParameters:
host(str): SMTP server address (e.g., "smtp.gmail.com")port(int): SMTP port (465 for SSL, 587 for TLS)sender_email(str): Your email addresssender_password(str): Your email password or app passwordreceiver_email(str): Recipient's email addresscc_emails(list): List of CC email addresses (empty list[]for none)subject(str): Email subject linebody(str): Email body text (plain text)attachment_paths(list): List of filenames to attach (files must be in ability directory)
Returns: bool — True if email sent successfully, False if failed
Template Usage:
status = self.capability_worker.send_email(
host="smtp.gmail.com",
port=465,
sender_email="sender@gmail.com",
sender_password="app_password_here",
receiver_email="recipient@example.com",
cc_emails=[],
subject="Subject Line",
body="Email body text",
attachment_paths=["file.txt"]
)
if status:
await self.capability_worker.speak("Email sent successfully!")
else:
await self.capability_worker.speak("Failed to send email.")- User triggers the ability with configured trigger words
- Ability loads hardcoded email configuration
- Calls
send_email()with all parameters - Checks return status (
True= success,False= failure) - Speaks confirmation or error message
- Calls
resume_normal_flow()to return control to Agent
1. Initialize Workers:
def call(self, worker: AgentWorker):
self.worker = worker
self.capability_worker = CapabilityWorker(self.worker)
self.worker.session_tasks.create(self.email_sender())- Sets up ability infrastructure
- Creates async task using
session_tasks(not raw asyncio)
2. Configure Email:
async def email_sender(self):
HOST = "smtp.gmail.com"
PORT = 465
SENDER_EMAIL = "test@gmail.com"
SENDER_PASSWORD = "test 1234 5678 9121"
RECEIVER_EMAIL = "receiver_test@gmail.com"
SUBJECT = "Test Email"
BODY = "Hello! This is a test email."
ATTACHMENTS = ["testfile.txt"]- Hardcoded for simplicity in template
- In production, load from file storage or user input
3. Send Email:
status = self.capability_worker.send_email(
host=HOST,
port=PORT,
sender_email=SENDER_EMAIL,
sender_password=SENDER_PASSWORD,
receiver_email=RECEIVER_EMAIL,
cc_emails=[],
subject=SUBJECT,
body=BODY,
attachment_paths=ATTACHMENTS,
)- Calls built-in send_email function
- Returns boolean status
4. Confirm & Resume:
if status:
await self.capability_worker.speak("Email has been sent successfully.")
else:
await self.capability_worker.speak("Failed to send email")
self.capability_worker.resume_normal_flow() # ← CRITICAL: Always call thisasync def email_with_generated_report(self):
# Generate report content
report_content = generate_report() # Your logic
# Write to file in ability directory
with open("report.txt", "w") as f:
f.write(report_content)
# Send email with attachment
status = self.capability_worker.send_email(
host="smtp.gmail.com",
port=465,
sender_email="reports@company.com",
sender_password="your_app_password",
receiver_email="manager@company.com",
cc_emails=[],
subject="Weekly Report",
body="Please find the attached weekly report.",
attachment_paths=["report.txt"]
)
if status:
await self.capability_worker.speak("Report emailed successfully.")
else:
await self.capability_worker.speak("Failed to email report.")
self.capability_worker.resume_normal_flow()async def send_configured_email(self):
# Load email configuration from persistent storage
if await self.capability_worker.check_if_file_exists("email_settings.json", in_ability_directory=False):
raw = await self.capability_worker.read_file("email_settings.json", in_ability_directory=False)
config = json.loads(raw)
else:
await self.capability_worker.speak("Email not configured. Please set up your email first.")
self.capability_worker.resume_normal_flow()
return
# Send using stored config
status = self.capability_worker.send_email(
host=config["host"],
port=config["port"],
sender_email=config["sender_email"],
sender_password=config["sender_password"],
receiver_email=config["default_recipient"],
cc_emails=config.get("cc_emails", []),
subject="Automated Email",
body="This email was sent from your OpenHome ability.",
attachment_paths=[]
)
if status:
await self.capability_worker.speak("Email sent using saved configuration.")
else:
await self.capability_worker.speak("Failed to send email.")
self.capability_worker.resume_normal_flow()async def send_ai_composed_email(self):
# Ask user for email purpose
await self.capability_worker.speak("What should the email be about?")
topic = await self.capability_worker.user_response()
# Generate email body using LLM
email_prompt = f"""Write a professional email about: {topic}
Keep it concise (2-3 paragraphs).
Use a friendly but professional tone."""
email_body = self.capability_worker.text_to_text_response(email_prompt)
# Send the AI-generated email
status = self.capability_worker.send_email(
host="smtp.gmail.com",
port=465,
sender_email="your_email@gmail.com",
sender_password="your_app_password",
receiver_email="recipient@example.com",
cc_emails=[],
subject=f"Re: {topic}",
body=email_body,
attachment_paths=[]
)
if status:
await self.capability_worker.speak("AI-composed email sent successfully.")
else:
await self.capability_worker.speak("Failed to send email.")
self.capability_worker.resume_normal_flow()# ❌ BAD — Password visible in code
SENDER_PASSWORD = "mypassword123"
# ✅ GOOD — Load from secure file storage
if await self.capability_worker.check_if_file_exists("email_creds.json", in_ability_directory=False):
raw = await self.capability_worker.read_file("email_creds.json", in_ability_directory=False)
creds = json.loads(raw)
SENDER_PASSWORD = creds["app_password"]import re
def is_valid_email(email: str) -> bool:
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return re.match(pattern, email) is not None
# Use before sending
if not is_valid_email(recipient_email):
await self.capability_worker.speak("Invalid email address. Please try again.")
returntry:
status = self.capability_worker.send_email(...)
if status:
await self.capability_worker.speak("Email sent successfully.")
else:
await self.capability_worker.speak(
"Failed to send email. Check your SMTP settings and credentials."
)
except Exception as e:
self.worker.editor_logging_handler.error(f"Email error: {e}")
await self.capability_worker.speak("An error occurred while sending the email.")
finally:
self.capability_worker.resume_normal_flow()# Read back email details before sending
await self.capability_worker.speak(
f"I'll send an email to {recipient_email} with subject '{subject}'. "
"Should I proceed?"
)
confirmed = await self.capability_worker.run_confirmation_loop("Confirm sending?")
if confirmed:
status = self.capability_worker.send_email(...)
else:
await self.capability_worker.speak("Email cancelled.")from datetime import datetime
# ✅ GOOD — Descriptive with context
subject = f"Daily Report - {datetime.now().strftime('%B %d, %Y')}"
subject = f"Alert: System Status Changed"
subject = f"Re: {user_topic}"
# ❌ BAD — Generic
subject = "Email"
subject = "Test"Possible causes:
- Wrong SMTP host or port
- Invalid credentials (use app password for Gmail, not regular password)
- 2FA not enabled (required for Gmail app passwords)
- Firewall blocking SMTP port
- Incorrect sender email address
Solutions:
- Verify SMTP settings for your provider
- Regenerate app password
- Check
editor_logging_handlerfor detailed error messages
Problem: Email sends but attachment is missing
Solution: Files must be in your ability's directory:
# Place files in: /your-ability-folder/testfile.txt
ATTACHMENTS = ["testfile.txt"] # Just filename, not full pathProblem: Gmail blocks login
Solution: Don't use "Allow less secure apps" (deprecated). Use app passwords instead:
- Enable 2-Factor Authentication
- Generate app password at myaccount.google.com/apppasswords
- Use 16-character app password (not your regular password)
Solutions:
- Add a professional email signature
- Use descriptive subject lines (avoid "Test", "Hi", etc.)
- Don't send too many emails rapidly
- Consider using a custom domain (not Gmail) for bulk sending
- Never commit passwords to GitHub — Use file storage or environment variables
- Use app passwords — Never use your main account password
- Limit recipient access — Don't allow users to specify arbitrary recipients without validation
- Rate limit sending — Prevent spam/abuse by limiting emails per session
- Validate all inputs — Check email addresses, file paths, subject lines
# Don't store plain text passwords in files
# Use encryption or OpenHome's secure storage (if available)
# At minimum, use file permissions to restrict access- Get SMTP credentials from your email provider
- For Gmail: Enable 2FA and generate app password
- Update
HOST,PORT,SENDER_EMAIL,SENDER_PASSWORDin template - (Optional) Add attachment files to ability directory
- Test with a simple email to yourself
- Customize trigger words in the OpenHome dashboard
- Replace hardcoded values with dynamic inputs or file storage
- Add email address validation
- Implement error handling with try-catch
- Add confirmation prompts for important sends
- Test with various recipients and attachments
- Consider loading config from persistent storage
Email Provider SMTP Docs:
OpenHome:
Use it to learn how to:
- ✅ Send emails programmatically
- ✅ Attach files to emails
- ✅ Handle CC recipients
- ✅ Check send status and handle errors
Then build something useful with this foundation! 🚀
Remember: Email is powerful — use it responsibly. Don't spam, always validate recipients, and respect rate limits.