Install
openclaw skills install @zkeviny/smtp-sender-secureSend emails securely without exposing SMTP passwords. Users store SMTP credentials and email scripts in MGC; AI executes scripts via mgc_run blackbox. AI never sees credentials, script content, or email body (when stored in MGC). Adapted to MGC 1.4.10.
openclaw skills install @zkeviny/smtp-sender-secureZero-Exposure SMTP Mail Sender is a documentation skill that teaches how to send emails securely without exposing SMTP credentials.
This skill uses MGC Blackbox 1.4.10 to achieve true zero-exposure:
mgc_run (1.4.7+ blackbox)After reading this documentation, you will understand how to:
argparse + parse_known_args)mgc_run (1.4.7+ blackbox); AI sees only {pid, status} + result file pathThis skill does not provide executable code, only documentation.
pip install mgc-blackbox>=1.4.10
mgc (API at http://127.0.0.1:57219, WebUI at 57218)mgc_save, mgc_run, mgc_list, mgc_find, mgc_open_webui~/.mgc/database/mgc_black_box/.mgc_tokenSandbox mode (1.4.9+): When running inside a sandbox Agent (Trae Work / Workbuddy), install MGC in the system environment; otherwise MCP operations may be limited — in that case, call FastAPI directly at
/api/mgc/sensitive/run.
AI → receives credentials → sends email
↓
Credentials exposed to AI
User → stores credentials (WebUI) → stores email script (mgc_save)
↓
AI → executes script (mgc_run) → script reads credentials locally
↓
AI only sees {pid, status} + result file
AI never sees credentials, script content, or email body.
Important: Credentials must be stored by the user manually via MGC WebUI (or AI via
mgc_saveon explicit user instruction).
mgcinfo_type: "config"
info_owner: "smtp_gmail" # You choose this name
content:
{
"address": "your@gmail.com",
"password": "app_specific_password",
"smtp_server": "smtp.gmail.com",
"smtp_port": 587
}
Remember: Tell AI the credential name (
info_type=config, info_owner=smtp_gmail) but NEVER share the actual content.
Rotating credentials:
mgc_saveagain with the sameinfo_type/info_ownerANDupdate_if_exists=true. Scripts will pick up the new credentials automatically.
Create an email script with argparse (literal defaults only) and store it in MGC:
import smtplib
import os
import json
import argparse
import requests
import datetime
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
# ========== Configuration ==========
# Replace these with your credential names (info_owner)
SMTP_CREDENTIAL_NAME = "smtp_gmail" # SMTP credentials
EMAIL_CONTENT_NAME = "email_template_001" # (Optional) Email content stored in MGC
def get_credential(cred_name):
"""Read credential from MGC local API. Script-internal only; AI never calls this."""
token_path = os.path.expanduser("~/.mgc/database/mgc_black_box/.mgc_token")
if not os.path.exists(token_path):
return None
with open(token_path) as f:
token = f.read().strip()
resp = requests.post(
"http://127.0.0.1:57219/api/mgc/sensitive/get",
headers={"X-MGC-Token": token, "Content-Type": "application/json"},
json={"info_type": "config", "info_owner": cred_name, "action": "run"},
timeout=10,
)
if resp.status_code == 200:
data = resp.json()
data_field = data.get("data")
if isinstance(data_field, str):
return json.loads(data_field)
elif isinstance(data_field, dict):
content = data_field.get("content", "")
if content:
return json.loads(content)
return None
def get_email_content(content_name):
"""Read email content from MGC (optional privacy feature)."""
token_path = os.path.expanduser("~/.mgc/database/mgc_black_box/.mgc_token")
if not os.path.exists(token_path):
return None
with open(token_path) as f:
token = f.read().strip()
resp = requests.post(
"http://127.0.0.1:57219/api/mgc/sensitive/get",
headers={"X-MGC-Token": token, "Content-Type": "application/json"},
json={"info_type": "config", "info_owner": content_name, "action": "run"},
timeout=10,
)
if resp.status_code == 200:
data = resp.json()
data_field = data.get("data")
if isinstance(data_field, str):
return data_field
elif isinstance(data_field, dict):
return data_field.get("content", "")
return None
def send_email(to_address, subject, body, use_stored_content=False):
"""Send email via SMTP."""
cred = get_credential(SMTP_CREDENTIAL_NAME)
if not cred:
return {"success": False, "error": "Failed to get SMTP credentials"}
if use_stored_content:
stored_content = get_email_content(EMAIL_CONTENT_NAME)
if stored_content:
try:
content_data = json.loads(stored_content)
subject = content_data.get("subject", subject)
body = content_data.get("body", body)
except Exception:
body = stored_content
try:
msg = MIMEMultipart()
msg['From'] = cred['address']
msg['To'] = to_address
msg['Subject'] = subject
msg.attach(MIMEText(body, 'plain', 'utf-8'))
with smtplib.SMTP(cred["smtp_server"], cred["smtp_port"]) as server:
server.starttls()
server.login(cred["address"], cred["password"])
server.sendmail(cred["address"], [to_address], msg.as_string())
return {"success": True, "message": "Email sent successfully"}
except Exception as e:
return {"success": False, "error": str(e)}
def main():
# ✅ Literal defaults only — MGC 1.4.10 auto-parses into ext02
parser = argparse.ArgumentParser()
parser.add_argument("--to", default="")
parser.add_argument("--subject", default="")
parser.add_argument("--body", default="")
parser.add_argument("--use_stored_content", action="store_true")
args, _ = parser.parse_known_args() # ✅ parse_known_args avoids exit on unknown params
result = send_email(
to_address=args.to,
subject=args.subject,
body=args.body,
use_stored_content=args.use_stored_content,
)
# Write result to file so AI can read it (mgc_run returns pid+status)
out_dir = os.path.expanduser("~/mgc_outputs")
os.makedirs(out_dir, exist_ok=True)
out_path = os.path.join(
out_dir, f"email_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
)
with open(out_path, "w", encoding="utf-8") as f:
json.dump(result, f, ensure_ascii=False, indent=2)
print(f"RESULT_FILE:{out_path}")
if __name__ == "__main__":
main()
Store the script:
mgc_save(
info_type="script",
info_owner="send_email_script",
ext01="python",
content="<paste your script here>",
update_if_exists=True
)
# MGC 1.4.10 auto-fills ext02 = '["--to", "", "--subject", "", "--body", ""]'
# (literal argparse defaults become a JSON array string)
When user wants to send an email, AI only needs to know:
send_email_scriptsmtp_gmail (provided by user)AI invokes mgc_run (1.4.7+ blackbox):
import json
# Build ext02 as JSON array string (1.4.10 contract)
ext02 = json.dumps([
"--to", "recipient@example.com",
"--subject", "Hello",
"--body", "Message content",
])
result = mgc_run(
info_type="script",
info_owner="send_email_script",
diff_1="send_email_script", # schema 必填的区分字段;多条同 owner 时消歧,单条时任意非空字符串均可
ext02=ext02 # JSON array string, NOT dict
)
# Returns: {"pid": 12345, "status": "started"}
# AI reads the RESULT_FILE path from the script's stdout (via mgc output convention)
AI never sees:
For maximum privacy, store email body in MGC so AI never even sees it:
Tool: mgc_save
Parameters:
info_type: "config"
info_owner: "email_template_001"
content: "{\"subject\": \"Project Update\", \"body\": \"Confidential quarterly report...\"}"
Then AI invokes with --use_stored_content:
ext02 = json.dumps([
"--to", "recipient@example.com",
"--use_stored_content",
])
result = mgc_run(
info_type="script",
info_owner="send_email_script",
diff_1="send_email_script",
ext02=ext02
)
# Script reads body from MGC; AI only knows there's "some email" being sent to recipient
mgc_runinfo_type/info_owner, never the contentmgc_run, not mgc_get: mgc_get is deprecated for AI-driven script executionStore email script:
mgc_save(
info_type="script",
info_owner="send_email_script",
ext01="python",
content="<script body>",
update_if_exists=True # required to overwrite same info_owner
)
Store SMTP credentials:
mgc_save(
info_type="config",
info_owner="smtp_gmail",
content='{"address": "...", "password": "...", "smtp_server": "...", "smtp_port": 587}',
update_if_exists=True
)
Execute email script — ext02 MUST be a JSON array string:
import json
result = mgc_run(
info_type="script",
info_owner="send_email_script",
diff_1="send_email_script",
ext02=json.dumps([
"--to", "to@example.com",
"--subject", "Test",
"--body", "Hello",
])
)
# Returns: {"pid": 12345, "status": "started"}
List stored entries (metadata only).
# Fuzzy search for SMTP-related scripts/credentials
scripts = mgc_find(info_owner="smtp", match_mode="substring", limit=10)
# match_mode: substring / prefix / suffix / exact
Opens WebUI for user to manage credentials and scripts.
| Issue | Solution |
|---|---|
mgc_run returns HTTP 422 | ext02 MUST be a JSON array string like '["--to","x@y.com"]'; use json.dumps(list) |
dynamic_args_detected warning | Script uses dynamic argparse defaults (datetime.now() etc.). Switch to literal defaults or pass ext02 manually |
args_not_recognized error | Source script's argparse did not recognize the args. Check add_argument names and ext02 array |
| Script execution fails | Check if credentials stored correctly in WebUI |
| Credential not found | Verify info_owner matches exactly (case-sensitive) |
| SMTP error | Check SMTP server/port settings; for Gmail use app-specific password |
| MGC not running | Run mgc in a terminal |
| Update not allowed | Add update_if_exists=true to mgc_save |
| Sandbox MCP unavailable | Install MGC in system environment, or call FastAPI at /api/mgc/sensitive/run |