Last active
March 21, 2025 02:15
-
-
Save ensean/f3d7940d55f3256b9ec5dbb964388006 to your computer and use it in GitHub Desktop.
send email via ses smtp endpoint
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# | |
# Script to send emails via AWS SES SMTP endpoint using curl | |
# Usage: ./send-ses-email.sh -f [email protected] -t [email protected] -s "Subject" -b "Body text" [-a attachment.pdf] | |
# Default AWS SES SMTP settings | |
AWS_REGION="us-east-1" | |
SES_SMTP_HOST="email-smtp.us-east-1.amazonaws.com" | |
SES_SMTP_PORT="587" | |
SES_SMTP_USERNAME="" | |
SES_SMTP_PASSWORD="" | |
# Function to display usage | |
usage() { | |
echo "Usage: $0 -f [email protected] -t [email protected] -s \"Subject\" -b \"Body text\" [-a attachment.pdf]" | |
echo " -f FROM_EMAIL : Sender email address" | |
echo " -t TO_EMAIL : Recipient email address (can be used multiple times)" | |
echo " -c CC_EMAIL : CC email address (can be used multiple times)" | |
echo " -s SUBJECT : Email subject" | |
echo " -b BODY : Email body text" | |
echo " -a ATTACHMENT : File to attach (can be used multiple times)" | |
echo " -r AWS_REGION : AWS region (default: us-east-1)" | |
echo " -u USERNAME : AWS SES SMTP username" | |
echo " -p PASSWORD : AWS SES SMTP password" | |
echo " -h : Display this help message" | |
exit 1 | |
} | |
# Check if curl command is available | |
if ! command -v curl &> /dev/null; then | |
echo "Error: 'curl' command not found. Please install curl." | |
echo " For macOS: brew install curl" | |
echo " For Ubuntu/Debian: sudo apt-get install curl" | |
echo " For CentOS/RHEL: sudo yum install curl" | |
exit 1 | |
fi | |
# Function to generate a random boundary string for MIME parts | |
generate_boundary() { | |
cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1 | |
} | |
# Function to generate a random message ID | |
generate_message_id() { | |
local random=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1) | |
echo "${random}@email.amazonses.com" | |
} | |
# Parse command line arguments | |
TO_EMAILS=() | |
CC_EMAILS=() | |
ATTACHMENTS=() | |
while getopts "f:t:c:s:b:a:r:u:p:h" opt; do | |
case $opt in | |
f) FROM_EMAIL="$OPTARG" ;; | |
t) TO_EMAILS+=("$OPTARG") ;; | |
c) CC_EMAILS+=("$OPTARG") ;; | |
s) SUBJECT="$OPTARG" ;; | |
b) BODY="$OPTARG" ;; | |
a) ATTACHMENTS+=("$OPTARG") ;; | |
r) AWS_REGION="$OPTARG" | |
SES_SMTP_HOST="email-smtp.$AWS_REGION.amazonaws.com" ;; | |
u) SES_SMTP_USERNAME="$OPTARG" ;; | |
p) SES_SMTP_PASSWORD="$OPTARG" ;; | |
h) usage ;; | |
\?) echo "Invalid option: -$OPTARG" >&2; usage ;; | |
esac | |
done | |
# Validate required parameters | |
if [ -z "$FROM_EMAIL" ] || [ ${#TO_EMAILS[@]} -eq 0 ] || [ -z "$SUBJECT" ] || [ -z "$BODY" ]; then | |
echo "Error: Missing required parameters." | |
usage | |
fi | |
if [ -z "$SES_SMTP_USERNAME" ] || [ -z "$SES_SMTP_PASSWORD" ]; then | |
echo "Error: AWS SES SMTP credentials are required." | |
usage | |
fi | |
# Create a temporary file for the email content | |
create_email_content() { | |
local boundary="$(generate_boundary)" | |
local mime_boundary="--${boundary}" | |
local mime_end="${mime_boundary}--" | |
local date_str=$(date -R) | |
local message_id=$(generate_message_id) | |
local temp_file=$(mktemp) | |
# Email headers | |
{ | |
echo "From: ${FROM_EMAIL}" | |
echo "To: $(IFS=,; echo "${TO_EMAILS[*]}")" | |
# Add CC if present | |
if [ ${#CC_EMAILS[@]} -gt 0 ]; then | |
echo "Cc: $(IFS=,; echo "${CC_EMAILS[*]}")" | |
fi | |
echo "Subject: ${SUBJECT}" | |
echo "Date: ${date_str}" | |
echo "Message-ID: <${message_id}>" | |
echo "MIME-Version: 1.0" | |
# If we have attachments, use multipart | |
if [ ${#ATTACHMENTS[@]} -gt 0 ]; then | |
echo "Content-Type: multipart/mixed; boundary=\"${boundary}\"" | |
echo "" | |
echo "${mime_boundary}" | |
echo "Content-Type: text/plain; charset=utf-8" | |
echo "Content-Transfer-Encoding: 7bit" | |
echo "" | |
echo "${BODY}" | |
# Add attachments | |
for attachment in "${ATTACHMENTS[@]}"; do | |
if [ -f "$attachment" ]; then | |
local filename=$(basename "$attachment") | |
local mime_type=$(file --mime-type -b "$attachment" 2>/dev/null || echo "application/octet-stream") | |
echo "" | |
echo "${mime_boundary}" | |
echo "Content-Type: ${mime_type}; name=\"${filename}\"" | |
echo "Content-Transfer-Encoding: base64" | |
echo "Content-Disposition: attachment; filename=\"${filename}\"" | |
echo "" | |
# Base64 encode with proper line breaks (76 chars per line) | |
if ! base64 < "$attachment" 2>/dev/null | fold -w 76; then | |
echo "Warning: Failed to encode attachment: $attachment" >&2 | |
continue | |
fi | |
else | |
echo "Warning: Attachment file not found: $attachment" >&2 | |
fi | |
done | |
# End of multipart message | |
echo "" | |
echo "${mime_end}" | |
else | |
# Simple text email | |
echo "Content-Type: text/plain; charset=utf-8" | |
echo "" | |
echo "${BODY}" | |
fi | |
} > "$temp_file" | |
echo "$temp_file" | |
} | |
# Prepare curl recipients arguments | |
prepare_recipients() { | |
local rcpt_args="" | |
# Add all To recipients | |
for email in "${TO_EMAILS[@]}"; do | |
# Strip any surrounding quotes and whitespace | |
email=$(echo "$email" | sed -e 's/^[[:space:]"'\'']*//g' -e 's/[[:space:]"'\'']*$//g') | |
# Ensure the email is properly formatted | |
if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then | |
rcpt_args="${rcpt_args} --mail-rcpt ${email}" | |
else | |
echo "Warning: Skipping invalid email format: $email" >&2 | |
fi | |
done | |
# Add all CC recipients | |
for email in "${CC_EMAILS[@]}"; do | |
# Strip any surrounding quotes and whitespace | |
email=$(echo "$email" | sed -e 's/^[[:space:]"'\'']*//g' -e 's/[[:space:]"'\'']*$//g') | |
# Ensure the email is properly formatted | |
if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then | |
rcpt_args="${rcpt_args} --mail-rcpt ${email}" | |
else | |
echo "Warning: Skipping invalid email format: $email" >&2 | |
fi | |
done | |
echo "$rcpt_args" | |
} | |
# Send the email | |
echo "Sending email via AWS SES SMTP ($SES_SMTP_HOST:$SES_SMTP_PORT)..." | |
# Create email content file | |
EMAIL_FILE=$(create_email_content) | |
# Prepare recipient arguments | |
RCPT_ARGS=$(prepare_recipients) | |
# Check if we have valid recipients | |
if [ -z "$RCPT_ARGS" ]; then | |
echo "Error: No valid recipient email addresses provided." | |
rm -f "$EMAIL_FILE" | |
exit 1 | |
fi | |
# Strip any surrounding quotes from the sender email | |
FROM_EMAIL=$(echo "$FROM_EMAIL" | sed -e 's/^[[:space:]"'\'']*//g' -e 's/[[:space:]"'\'']*$//g') | |
# Debug info | |
echo "From: $FROM_EMAIL" | |
echo "To: ${TO_EMAILS[*]}" | |
if [ ${#CC_EMAILS[@]} -gt 0 ]; then | |
echo "CC: ${CC_EMAILS[*]}" | |
fi | |
echo "Subject: $SUBJECT" | |
echo "Attachments: ${#ATTACHMENTS[@]}" | |
# Use eval to properly handle the recipient arguments | |
echo "Sending email..." | |
eval "curl --ssl-reqd \ | |
--url smtp://${SES_SMTP_HOST}:${SES_SMTP_PORT} \ | |
--user ${SES_SMTP_USERNAME}:${SES_SMTP_PASSWORD} \ | |
--mail-from ${FROM_EMAIL} \ | |
${RCPT_ARGS} \ | |
--upload-file ${EMAIL_FILE}" | |
RESULT=$? | |
# Clean up | |
rm -f "$EMAIL_FILE" | |
# Check result | |
if [ $RESULT -eq 0 ]; then | |
echo "Email sent successfully!" | |
else | |
echo "Failed to send email. Exit code: $RESULT" | |
echo "If you're having issues:" | |
echo " - Verify your AWS SES SMTP credentials are correct" | |
echo " - Ensure your sender email is verified in AWS SES" | |
echo " - Check if you're in sandbox mode and need to verify recipient emails" | |
echo " - Make sure port $SES_SMTP_PORT is not blocked by your firewall" | |
fi | |
exit $RESULT |
Author
ensean
commented
Mar 18, 2025
•
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment