Add email and SMS capabilities to your app. Compare providers, understand pricing, and implement sending and receiving messages.
When building an app that sends transactional emails (welcome emails, password resets, notifications, receipts), you need a reliable email delivery service. The two most popular options are SendGrid and Amazon SES.
Both can handle high volumes and provide good deliverability, but they differ significantly in pricing, ease of setup, and features.
| Plan | Price | Emails/Month |
|---|---|---|
| Free | $0 | 100/day (3,000/month) |
| Essentials | $19.95/mo | 50,000 |
| Pro | $89.95/mo | 100,000 |
The free tier covers up to 100 emails/day (3,000/month), so 1,000 emails costs nothing.
SENDGRID_API_KEY=SG.xxxx...SendGrid requires you to verify the email address you'll send from. You cannot send emails until this is done.
For better deliverability (less spam folder), set up Domain Authentication instead. Go to Settings → Sender Authentication → "Authenticate Your Domain". This requires adding DNS records but significantly improves delivery rates.
# Install the SDK
npm install @sendgrid/mail
# Or with pip
pip install sendgrid
// Node.js example
const sgMail = require('@sendgrid/mail');
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
const msg = {
to: '[email protected]',
from: '[email protected]', // Must be verified sender
subject: 'Welcome to MyApp!',
text: 'Thanks for signing up.',
html: '<strong>Thanks for signing up!</strong>',
};
await sgMail.send(msg);
| Tier | Price | Notes |
|---|---|---|
| From EC2 | $0 for first 62,000/mo | Then $0.10 per 1,000 |
| Outside EC2 | $0.10 per 1,000 | No free tier |
| Attachments | $0.12 per GB | Additional charge |
At $0.10 per 1,000 emails, sending 1,000 emails costs just ten cents. If you're on EC2, your first 62,000/month are free.
If you don't have an AWS account yet:
You MUST verify the email address you send FROM. In sandbox mode, you also need to verify recipient emails.
For production, verify your entire domain instead. This requires adding DNS records but lets you send from any address @yourdomain.com. Select "Domain" instead of "Email address" when creating an identity.
AWS uses Access Keys (not API keys) for authentication. You need to create an IAM user:
Store these as environment variables:
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=abc123...
AWS_REGION=us-east-1
In sandbox mode, you can only send to verified emails (max 200/day). To send to anyone, you must request production access.
# Install AWS SDK
npm install @aws-sdk/client-ses
# Or with pip
pip install boto3
// Node.js example
const { SESClient, SendEmailCommand } = require('@aws-sdk/client-ses');
// SDK automatically uses AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION env vars
const client = new SESClient({ region: 'us-east-1' });
const command = new SendEmailCommand({
Source: '[email protected]', // Must be verified!
Destination: {
ToAddresses: ['[email protected]'],
},
Message: {
Subject: { Data: 'Welcome to MyApp!' },
Body: {
Html: { Data: '<strong>Thanks for signing up!</strong>' },
},
},
});
await client.send(command);
| Feature | SendGrid | Amazon SES |
|---|---|---|
| Setup Time | 5-10 minutes | 30-60 minutes |
| Cost at 100K emails/mo | $89.95/mo | ~$10/mo |
| Cost at 1M emails/mo | ~$450/mo | ~$100/mo |
| Template Editor | Yes (drag & drop) | No (use your own) |
| Analytics | Excellent | Basic |
| Best For | Quick start, features | High volume, cost savings |
Start with SendGrid if you're building a new app. The free tier and easy setup let you focus on your product. Switch to SES when you're sending 100K+ emails/month and want to reduce costs.
SMS is perfect for two-factor authentication, order notifications, appointment reminders, and alerts. The two most popular options are Twilio (market leader) and Plivo (cost-effective alternative).
| Item | Cost |
|---|---|
| Phone Number (US) | $1.15/month |
| Outbound SMS (US) | $0.0079/message |
| Inbound SMS (US) | $0.0079/message |
| International SMS | Varies ($0.01-$0.15) |
# Install the SDK
npm install twilio
# Or with pip
pip install twilio
// Node.js example
const twilio = require('twilio');
const client = twilio(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN
);
await client.messages.create({
body: 'Your verification code is 123456',
from: '+15551234567', // Your Twilio number
to: '+15559876543' // User's number
});
| Item | Cost |
|---|---|
| Phone Number (US) | $0.80/month |
| Outbound SMS (US) | $0.0055/message |
| Inbound SMS (US) | $0.0050/message |
| International SMS | Varies ($0.008-$0.12) |
# Install the SDK
npm install plivo
# Or with pip
pip install plivo
// Node.js example
const plivo = require('plivo');
const client = new plivo.Client(
process.env.PLIVO_AUTH_ID,
process.env.PLIVO_AUTH_TOKEN
);
await client.messages.create({
src: '+15551234567', // Your Plivo number
dst: '+15559876543', // User's number
text: 'Your verification code is 123456'
});
| Feature | Twilio | Plivo |
|---|---|---|
| Cost per SMS (US) | $0.0079 | $0.0055 |
| Cost at 100K SMS/mo | ~$790 | ~$550 |
| Setup Difficulty | Easy | Easy |
| Documentation | Excellent | Good |
| Additional Features | Voice, Video, WhatsApp | Voice, SIP |
| Best For | Full features, support | Cost savings |
Start with Twilio for the best documentation and community support. Consider Plivo if you're cost-sensitive and sending high volumes. Both are reliable for production use.
Sometimes you need to receive emails programmatically - for support tickets, parsing receipts, or handling replies. Here's how to set it up.
SendGrid can forward incoming emails to your webhook as HTTP POST requests.
// Express.js webhook to receive parsed emails
const express = require('express');
const multer = require('multer');
const app = express();
const upload = multer();
app.post('/webhook/email', upload.none(), (req, res) => {
const {
from, // Sender email
to, // Recipient (your email)
subject, // Email subject
text, // Plain text body
html, // HTML body
attachments // Number of attachments
} = req.body;
console.log(`Email from ${from}: ${subject}`);
// Process the email (create ticket, parse data, etc.)
processEmail({ from, to, subject, text, html });
res.status(200).send('OK');
});
# MX Record Setup
# Add this MX record for your receiving domain
# e.g., inbound.yourapp.com
Type: MX
Host: inbound
Value: mx.sendgrid.net
Priority: 10
SES can receive emails and either store them in S3 or trigger a Lambda function.
// Lambda function to process incoming emails
exports.handler = async (event) => {
const sesNotification = event.Records[0].ses;
const mail = sesNotification.mail;
console.log('From:', mail.source);
console.log('Subject:', mail.commonHeaders.subject);
// Email content is in S3, fetch it
const s3 = new AWS.S3();
const email = await s3.getObject({
Bucket: 'your-email-bucket',
Key: mail.messageId
}).promise();
// Parse the raw email
const parsed = await simpleParser(email.Body);
console.log('Body:', parsed.text);
return { statusCode: 200 };
};
const { simpleParser } = require('mailparser');
const parsed = await simpleParser(rawEmailString);
console.log(parsed.from.text); // "John Doe <[email protected]>"
console.log(parsed.subject); // "Re: Your order"
console.log(parsed.text); // Plain text body
console.log(parsed.html); // HTML body
console.log(parsed.attachments); // Array of attachments
Always validate the webhook source to prevent spoofing. SendGrid provides a signature header you can verify, and SES uses AWS IAM permissions.