send-email-programmatically — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited send-email-programmatically (Agent Skill) and scored it 96/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 1 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Send emails via SMTP, free APIs (Mailgun, SendGrid free tier), or privacy-focused services. Essential for notifications, alerts, automated reports, and user communication.
Install options:
# Ubuntu/Debian
sudo apt-get install -y curl msmtp msmtp-mta
# macOS (postfix is pre-installed, or use msmtp)
brew install msmtp
# Node.js
npm install nodemailerSend email using SMTP via curl (works with Gmail, Outlook, custom SMTP servers).
# Gmail SMTP example (requires app password)
SMTP_SERVER="smtp.gmail.com:587"
FROM_EMAIL="[email protected]"
TO_EMAIL="[email protected]"
SUBJECT="Test Email"
BODY="This is a test email sent via SMTP."
APP_PASSWORD="your-app-password"
# Send email
curl -v --url "smtp://${SMTP_SERVER}" \
--mail-from "${FROM_EMAIL}" \
--mail-rcpt "${TO_EMAIL}" \
--user "${FROM_EMAIL}:${APP_PASSWORD}" \
--upload-file - <<EOF
From: ${FROM_EMAIL}
To: ${TO_EMAIL}
Subject: ${SUBJECT}
${BODY}
EOF
# Outlook/Office365 SMTP
curl --url "smtp://smtp.office365.com:587" \
--mail-from "[email protected]" \
--mail-rcpt "[email protected]" \
--user "[email protected]:your-password" \
--ssl-reqd \
--upload-file - <<EOF
From: [email protected]
To: [email protected]
Subject: Hello from Outlook
This is an automated email.
EOFSend email using Mailgun free tier (5,000 emails/month, no credit card required for sandbox).
# Set your Mailgun credentials
MAILGUN_API_KEY="your-mailgun-api-key"
MAILGUN_DOMAIN="sandbox123.mailgun.org" # or your verified domain
# Send email
curl -s --user "api:${MAILGUN_API_KEY}" \
"https://api.mailgun.net/v3/${MAILGUN_DOMAIN}/messages" \
-F from="Sender Name <mailgun@${MAILGUN_DOMAIN}>" \
-F to="[email protected]" \
-F subject="Hello from Mailgun" \
-F text="This is the plain text body" \
-F html="<h1>HTML Email</h1><p>This is the HTML body</p>"
# Send with attachment
curl -s --user "api:${MAILGUN_API_KEY}" \
"https://api.mailgun.net/v3/${MAILGUN_DOMAIN}/messages" \
-F from="notifications@${MAILGUN_DOMAIN}" \
-F to="[email protected]" \
-F subject="Report Attached" \
-F text="Please find the report attached." \
-F attachment=@./report.pdfNode.js:
async function sendEmailMailgun(options) {
const { apiKey, domain, from, to, subject, text, html } = options;
const formData = new URLSearchParams({
from,
to,
subject,
text: text || '',
html: html || ''
});
const auth = 'Basic ' + Buffer.from(`api:${apiKey}`).toString('base64');
const res = await fetch(`https://api.mailgun.net/v3/${domain}/messages`, {
method: 'POST',
headers: {
'Authorization': auth,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: formData
});
if (!res.ok) {
const error = await res.text();
throw new Error(`Mailgun API error: ${error}`);
}
return await res.json();
}
// Usage
// sendEmailMailgun({
// apiKey: 'your-mailgun-api-key',
// domain: 'sandbox123.mailgun.org',
// from: 'Sender <[email protected]>',
// to: '[email protected]',
// subject: 'Test Email',
// text: 'Plain text body',
// html: '<h1>HTML body</h1>'
// }).then(result => console.log('Email sent:', result));Send email using SendGrid free tier (100 emails/day).
# Set your SendGrid API key
SENDGRID_API_KEY="your-sendgrid-api-key"
# Send email
curl -s --request POST \
--url "https://api.sendgrid.com/v3/mail/send" \
--header "Authorization: Bearer ${SENDGRID_API_KEY}" \
--header "Content-Type: application/json" \
--data '{
"personalizations": [{
"to": [{"email": "[email protected]"}],
"subject": "Hello from SendGrid"
}],
"from": {"email": "[email protected]", "name": "Sender Name"},
"content": [{
"type": "text/plain",
"value": "This is the email body."
}]
}'
# Send HTML email with attachment
curl -s --request POST \
--url "https://api.sendgrid.com/v3/mail/send" \
--header "Authorization: Bearer ${SENDGRID_API_KEY}" \
--header "Content-Type: application/json" \
--data '{
"personalizations": [{
"to": [{"email": "[email protected]"}]
}],
"from": {"email": "[email protected]"},
"subject": "Weekly Report",
"content": [{
"type": "text/html",
"value": "<h1>Weekly Report</h1><p>Attached is your report.</p>"
}],
"attachments": [{
"content": "'"$(base64 -w 0 report.pdf)"'",
"filename": "report.pdf",
"type": "application/pdf"
}]
}'Node.js:
async function sendEmailSendGrid(options) {
const { apiKey, from, to, subject, text, html, attachments = [] } = options;
const payload = {
personalizations: [{
to: [{ email: to }],
subject
}],
from: { email: from },
content: [{
type: html ? 'text/html' : 'text/plain',
value: html || text
}]
};
if (attachments.length > 0) {
payload.attachments = attachments.map(att => ({
content: att.content, // base64 string
filename: att.filename,
type: att.type || 'application/octet-stream'
}));
}
const res = await fetch('https://api.sendgrid.com/v3/mail/send', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (!res.ok) {
const error = await res.text();
throw new Error(`SendGrid API error: ${error}`);
}
return { success: true, status: res.status };
}
// Usage
// sendEmailSendGrid({
// apiKey: 'your-sendgrid-api-key',
// from: '[email protected]',
// to: '[email protected]',
// subject: 'Test Email',
// html: '<h1>Hello</h1><p>This is a test.</p>'
// }).then(result => console.log('Email sent:', result));Send email using msmtp (lightweight SMTP client, good for automation).
# Configure msmtp (one-time setup)
cat > ~/.msmtprc <<EOF
defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile ~/.msmtp.log
# Gmail account
account gmail
host smtp.gmail.com
port 587
from [email protected]
user [email protected]
password your-app-password
# Set default account
account default : gmail
EOF
chmod 600 ~/.msmtprc
# Send email
echo -e "Subject: Test Email\nFrom: [email protected]\nTo: [email protected]\n\nThis is the email body." | msmtp [email protected]
# Send email with file content
cat report.txt | msmtp -t <<EOF
To: [email protected]
From: [email protected]
Subject: Daily Report
$(cat report.txt)
EOF
# Send HTML email
msmtp [email protected] <<EOF
To: [email protected]
From: [email protected]
Subject: HTML Email
Content-Type: text/html
<html>
<body>
<h1>Hello</h1>
<p>This is an HTML email.</p>
</body>
</html>
EOFSend email using Node.js nodemailer library (supports all SMTP servers).
Node.js:
const nodemailer = require('nodemailer');
async function sendEmail(config) {
const {
smtpHost,
smtpPort,
smtpUser,
smtpPassword,
from,
to,
subject,
text,
html,
attachments = []
} = config;
// Create transporter
const transporter = nodemailer.createTransport({
host: smtpHost,
port: smtpPort,
secure: smtpPort === 465, // true for 465, false for other ports
auth: {
user: smtpUser,
pass: smtpPassword
},
connectionTimeout: 10000
});
// Send email
const info = await transporter.sendMail({
from,
to,
subject,
text,
html,
attachments: attachments.map(att => ({
filename: att.filename,
path: att.path || undefined,
content: att.content || undefined
}))
});
return {
success: true,
messageId: info.messageId,
response: info.response
};
}
// Usage - Gmail
// sendEmail({
// smtpHost: 'smtp.gmail.com',
// smtpPort: 587,
// smtpUser: '[email protected]',
// smtpPassword: 'your-app-password',
// from: '"Sender Name" <[email protected]>',
// to: '[email protected]',
// subject: 'Hello',
// text: 'Plain text body',
// html: '<b>HTML body</b>',
// attachments: [
// { filename: 'report.pdf', path: './report.pdf' }
// ]
// }).then(result => console.log('Email sent:', result));
// Usage - Outlook
// sendEmail({
// smtpHost: 'smtp.office365.com',
// smtpPort: 587,
// smtpUser: '[email protected]',
// smtpPassword: 'your-password',
// from: '[email protected]',
// to: '[email protected]',
// subject: 'Test',
// text: 'This is a test email'
// });Production-ready email sending with retry logic and error handling.
#!/bin/bash
send_email_with_retry() {
local TO="$1"
local SUBJECT="$2"
local BODY="$3"
local MAX_RETRIES=3
local RETRY_DELAY=5
for i in $(seq 1 $MAX_RETRIES); do
if curl -fsS --max-time 30 \
--url "smtp://smtp.gmail.com:587" \
--mail-from "[email protected]" \
--mail-rcpt "$TO" \
--user "[email protected]:app-password" \
--upload-file - <<EOF
From: [email protected]
To: $TO
Subject: $SUBJECT
$BODY
EOF
then
echo "Email sent successfully to $TO"
return 0
else
echo "Attempt $i failed, retrying in ${RETRY_DELAY}s..." >&2
sleep $RETRY_DELAY
fi
done
echo "Failed to send email after $MAX_RETRIES attempts" >&2
return 1
}
# Usage
send_email_with_retry "[email protected]" "Alert" "System CPU usage is high"Node.js:
async function sendEmailWithRetry(config, maxRetries = 3) {
const { provider, ...emailConfig } = config;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
let result;
if (provider === 'mailgun') {
result = await sendEmailMailgun(emailConfig);
} else if (provider === 'sendgrid') {
result = await sendEmailSendGrid(emailConfig);
} else {
throw new Error(`Unknown provider: ${provider}`);
}
return { success: true, attempt, result };
} catch (err) {
console.error(`Attempt ${attempt} failed:`, err.message);
if (attempt === maxRetries) {
throw new Error(`Failed to send email after ${maxRetries} attempts: ${err.message}`);
}
// Exponential backoff
const delayMs = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
}
// Usage
// sendEmailWithRetry({
// provider: 'mailgun',
// apiKey: 'your-api-key',
// domain: 'sandbox123.mailgun.org',
// from: '[email protected]',
// to: '[email protected]',
// subject: 'Critical Alert',
// text: 'System requires attention'
// }, 3).then(result => console.log('Email sent:', result));You have email sending capability via SMTP and free APIs. When a user asks to send an email:
1. Choose the best method based on requirements:
- **curl + SMTP** — For simple emails with Gmail/Outlook (requires app password)
- **Mailgun API** — For higher volume (5,000/month free, requires API key)
- **SendGrid API** — For moderate volume (100/day free, requires API key)
- **msmtp** — For automated scripts and cron jobs
- **nodemailer** — For Node.js applications with full SMTP support
2. For Gmail/Outlook SMTP:
- Gmail: smtp.gmail.com:587 (requires app password from Google Account settings)
- Outlook: smtp.office365.com:587
- Always use app passwords, never account passwords
3. For Mailgun:
- Free sandbox domain: 5,000 emails/month to authorized recipients
- Verified domain: Unlimited recipients (within free tier limits)
- No credit card required for sandbox
4. For SendGrid:
- Free tier: 100 emails/day
- Requires account signup and API key
5. Always:
- Validate recipient email format
- Use environment variables for credentials
- Implement retry logic (3 attempts with exponential backoff)
- Handle errors gracefully with clear messages
- Never log credentials or sensitive data
6. Security:
- Store SMTP passwords in ~/.msmtprc (chmod 600) or environment variables
- Use TLS/SSL for all SMTP connections
- Validate and sanitize email content to prevent injectionError: "authentication failed" (Gmail)
Error: "530 5.7.0 Must issue a STARTTLS command first"
--ssl-reqd to curl command or use port 587/465Error: "554 5.7.1 Relay access denied"
Mailgun: "Free accounts are for test purposes only"
SendGrid: "403 Forbidden"
Emails going to spam:
Timeout errors:
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.