Install
$ agentstack add skill-kgeminic-claude-skills-1-email-gateway ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Email Gateway (Multi-Provider)
Status: Production Ready ✅ Last Updated: 2026-01-10 Providers: Resend, SendGrid, Mailgun, SMTP2Go
Quick Start
Choose your provider based on needs:
| Provider | Best For | Key Feature | Free Tier | |----------|----------|-------------|-----------| | Resend | Modern apps, React Email | JSX templates | 100/day, 3k/month | | SendGrid | Enterprise scale | Dynamic templates | 100/day forever | | Mailgun | Developer webhooks | Event tracking | 100/day | | SMTP2Go | Reliable relay, AU | Simple API | 1k/month trial |
Resend (Recommended for New Projects)
const response = await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.RESEND_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: 'noreply@yourdomain.com',
to: 'user@example.com',
subject: 'Welcome!',
html: 'Hello World',
}),
});
const data = await response.json();
// { id: "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794" }
SendGrid (Enterprise)
const response = await fetch('https://api.sendgrid.com/v3/mail/send', {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.SENDGRID_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
personalizations: [{
to: [{ email: 'user@example.com' }],
}],
from: { email: 'noreply@yourdomain.com' },
subject: 'Welcome!',
content: [{
type: 'text/html',
value: 'Hello World',
}],
}),
});
// Returns 202 on success (no body)
Mailgun
const formData = new FormData();
formData.append('from', 'noreply@yourdomain.com');
formData.append('to', 'user@example.com');
formData.append('subject', 'Welcome!');
formData.append('html', 'Hello World');
const response = await fetch(
`https://api.mailgun.net/v3/${env.MAILGUN_DOMAIN}/messages`,
{
method: 'POST',
headers: {
'Authorization': `Basic ${btoa(`api:${env.MAILGUN_API_KEY}`)}`,
},
body: formData,
}
);
const data = await response.json();
// { id: "", message: "Queued. Thank you." }
SMTP2Go
const response = await fetch('https://api.smtp2go.com/v3/email/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
api_key: env.SMTP2GO_API_KEY,
to: [''],
sender: 'noreply@yourdomain.com',
subject: 'Welcome!',
html_body: 'Hello World',
}),
});
const data = await response.json();
// { data: { succeeded: 1, failed: 0, email_id: "..." } }
Provider Comparison
Features
| Feature | Resend | SendGrid | Mailgun | SMTP2Go | |---------|--------|----------|---------|---------| | React Email | ✅ Native | ❌ | ❌ | ❌ | | Dynamic Templates | ✅ | ✅ | ✅ | ✅ | | Batch Sending | 50/request | 1000/request | 1000/request | 100/request | | Webhooks | ✅ | ✅ | ✅ | ✅ | | SMTP | ✅ | ✅ | ✅ | ✅ Primary | | IP Warmup | Managed | Manual | Manual | Managed | | Dedicated IPs | Enterprise | $90+/mo | $80+/mo | Custom | | Analytics | Basic | Advanced | Advanced | Good | | A/B Testing | ❌ | ✅ | ✅ | ❌ |
Rate Limits (Free Tier)
| Provider | Daily | Monthly | Overage Cost | |----------|-------|---------|--------------| | Resend | 100 | 3,000 | $1/1k | | SendGrid | 100 | Forever | $15 for 10k | | Mailgun | 100 | Forever | $15 for 10k | | SMTP2Go | ~33 | 1,000 trial | $10 for 10k |
API Limits
| Provider | Requests/sec | Burst | Retry After Header | |----------|--------------|-------|-------------------| | Resend | 10 | Yes | ✅ | | SendGrid | 600 | Yes | ✅ | | Mailgun | Varies | Yes | ✅ | | SMTP2Go | 10 | Limited | ✅ |
Message Limits
| Provider | Max Size | Attachments | Max Recipients | |----------|----------|-------------|----------------| | Resend | 40 MB | 40 MB total | 50/request | | SendGrid | 20 MB | 20 MB total | 1000/request | | Mailgun | 25 MB | 25 MB total | 1000/request | | SMTP2Go | 50 MB | 50 MB total | 100/request |
Configuration
Environment Variables
# Resend
RESEND_API_KEY=re_xxxxxxxxx
# SendGrid
SENDGRID_API_KEY=SG.xxxxxxxxx
# Mailgun
MAILGUN_API_KEY=xxxxxxxx-xxxxxxxx-xxxxxxxx
MAILGUN_DOMAIN=mg.yourdomain.com
MAILGUN_REGION=us # or eu
# SMTP2Go
SMTP2GO_API_KEY=api-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Wrangler Secrets (Cloudflare Workers)
# Set secrets
echo "re_xxxxxxxxx" | npx wrangler secret put RESEND_API_KEY
echo "SG.xxxxxxxxx" | npx wrangler secret put SENDGRID_API_KEY
echo "xxxxxxxx-xxxxxxxx-xxxxxxxx" | npx wrangler secret put MAILGUN_API_KEY
echo "api-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" | npx wrangler secret put SMTP2GO_API_KEY
# Deploy to activate
npx wrangler deploy
TypeScript Types
// Resend
interface ResendEmail {
from: string;
to: string | string[];
subject: string;
html?: string;
text?: string;
cc?: string | string[];
bcc?: string | string[];
replyTo?: string | string[];
headers?: Record;
attachments?: Array;
tags?: Record;
scheduledAt?: string; // ISO 8601
}
interface ResendResponse {
id: string;
}
// SendGrid
interface SendGridEmail {
personalizations: Array;
cc?: Array;
bcc?: Array;
subject?: string;
dynamic_template_data?: Record;
}>;
from: { email: string; name?: string };
subject?: string;
content?: Array;
template_id?: string;
attachments?: Array;
}
// Mailgun
interface MailgunEmail {
from: string;
to: string | string[];
subject: string;
html?: string;
text?: string;
cc?: string | string[];
bcc?: string | string[];
'h:Reply-To'?: string;
template?: string;
'h:X-Mailgun-Variables'?: string; // JSON
attachment?: File | File[];
inline?: File | File[];
'o:tag'?: string | string[];
'o:tracking'?: 'yes' | 'no';
'o:tracking-clicks'?: 'yes' | 'no' | 'htmlonly';
'o:tracking-opens'?: 'yes' | 'no';
}
interface MailgunResponse {
id: string;
message: string;
}
// SMTP2Go
interface SMTP2GoEmail {
api_key: string;
to: string[];
sender: string;
subject: string;
html_body?: string;
text_body?: string;
custom_headers?: Array;
attachments?: Array;
}
interface SMTP2GoResponse {
data: {
succeeded: number;
failed: number;
failures?: string[];
email_id?: string;
};
}
Common Patterns
1. Transactional Emails
Password Reset:
// templates/password-reset.ts
export async function sendPasswordReset(
provider: 'resend' | 'sendgrid' | 'mailgun' | 'smtp2go',
to: string,
resetToken: string,
env: Env
): Promise {
const resetUrl = `https://yourapp.com/reset-password?token=${resetToken}`;
const html = `
Reset Your Password
Click the link below to reset your password:
Reset Password
This link expires in 1 hour.
`;
switch (provider) {
case 'resend':
return sendViaResend(to, 'Reset Your Password', html, env);
case 'sendgrid':
return sendViaSendGrid(to, 'Reset Your Password', html, env);
case 'mailgun':
return sendViaMailgun(to, 'Reset Your Password', html, env);
case 'smtp2go':
return sendViaSMTP2Go(to, 'Reset Your Password', html, env);
}
}
async function sendViaResend(
to: string,
subject: string,
html: string,
env: Env
): Promise {
const response = await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.RESEND_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: 'noreply@yourdomain.com',
to,
subject,
html,
}),
});
if (!response.ok) {
const error = await response.text();
return { success: false, error };
}
const data = await response.json();
return { success: true, id: data.id };
}
2. Batch Sending
Resend (max 50 recipients):
async function sendBatchResend(
recipients: string[],
subject: string,
html: string,
env: Env
): Promise> {
const results: Array = [];
// Chunk into groups of 50
for (let i = 0; i results.push({ email, id: data.id }));
} else {
const error = await response.text();
chunk.forEach(email => results.push({ email, error }));
}
}
return results;
}
SendGrid (max 1000 personalizations):
async function sendBatchSendGrid(
recipients: Array }>,
templateId: string,
env: Env
): Promise {
const response = await fetch('https://api.sendgrid.com/v3/mail/send', {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.SENDGRID_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
personalizations: recipients.map(r => ({
to: [{ email: r.email, name: r.name }],
dynamic_template_data: r.data || {},
})),
from: { email: 'noreply@yourdomain.com' },
template_id: templateId,
}),
});
if (!response.ok) {
const error = await response.text();
return { success: false, error };
}
return { success: true };
}
3. React Email Templates (Resend Only)
Install React Email:
npm install react-email @react-email/components
Create Template:
// emails/welcome.tsx
import {
Html,
Head,
Body,
Container,
Heading,
Text,
Button,
} from '@react-email/components';
interface WelcomeEmailProps {
name: string;
confirmUrl: string;
}
export default function WelcomeEmail({ name, confirmUrl }: WelcomeEmailProps) {
return (
Welcome, {name}!
Thanks for signing up. Please confirm your email address:
Confirm Email
);
}
Send via Resend SDK (Node.js):
import { Resend } from 'resend';
import WelcomeEmail from './emails/welcome';
const resend = new Resend(process.env.RESEND_API_KEY);
await resend.emails.send({
from: 'noreply@yourdomain.com',
to: 'user@example.com',
subject: 'Welcome!',
react: WelcomeEmail({ name: 'Alice', confirmUrl: 'https://...' }),
});
Send via Workers (render to HTML first):
import { render } from '@react-email/render';
import WelcomeEmail from './emails/welcome';
const html = render(WelcomeEmail({ name: 'Alice', confirmUrl: 'https://...' }));
await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.RESEND_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: 'noreply@yourdomain.com',
to: 'user@example.com',
subject: 'Welcome!',
html,
}),
});
4. Dynamic Templates
SendGrid:
// 1. Create template in SendGrid dashboard with handlebars
// Subject: Welcome {{name}}!
// Body: Hi {{name}}Your code: {{confirmationCode}}
// 2. Send with template ID
const response = await fetch('https://api.sendgrid.com/v3/mail/send', {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.SENDGRID_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
personalizations: [{
to: [{ email: 'user@example.com' }],
dynamic_template_data: {
name: 'Alice',
confirmationCode: 'ABC123',
},
}],
from: { email: 'noreply@yourdomain.com' },
template_id: 'd-xxxxxxxxxxxxxxxxxxxxxxxx',
}),
});
Mailgun:
// 1. Create template in Mailgun dashboard or via API
// Use {{name}} and {{confirmationCode}} variables
// 2. Send with template name
const formData = new FormData();
formData.append('from', 'noreply@yourdomain.com');
formData.append('to', 'user@example.com');
formData.append('subject', 'Welcome');
formData.append('template', 'welcome-template');
formData.append('h:X-Mailgun-Variables', JSON.stringify({
name: 'Alice',
confirmationCode: 'ABC123',
}));
const response = await fetch(
`https://api.mailgun.net/v3/${env.MAILGUN_DOMAIN}/messages`,
{
method: 'POST',
headers: {
'Authorization': `Basic ${btoa(`api:${env.MAILGUN_API_KEY}`)}`,
},
body: formData,
}
);
5. Attachments
Resend:
const fileBuffer = await file.arrayBuffer();
const base64Content = btoa(String.fromCharCode(...new Uint8Array(fileBuffer)));
await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.RESEND_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: 'noreply@yourdomain.com',
to: 'user@example.com',
subject: 'Your Invoice',
html: 'Attached is your invoice.',
attachments: [{
filename: 'invoice.pdf',
content: base64Content,
}],
}),
});
SendGrid:
const fileBuffer = await file.arrayBuffer();
const base64Content = btoa(String.fromCharCode(...new Uint8Array(fileBuffer)));
const response = await fetch('https://api.sendgrid.com/v3/mail/send', {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.SENDGRID_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
personalizations: [{
to: [{ email: 'user@example.com' }],
}],
from: { email: 'noreply@yourdomain.com' },
subject: 'Your Invoice',
content: [{ type: 'text/html', value: 'Attached is your invoice.' }],
attachments: [{
content: base64Content,
filename: 'invoice.pdf',
type: 'application/pdf',
disposition: 'attachment',
}],
}),
});
Mailgun (uses FormData with File):
const formData = new FormData();
formData.append('from', 'noreply@yourdomain.com');
formData.append('to', 'user@example.com');
formData.append('subject', 'Your Invoice');
formData.append('html', 'Attached is your invoice.');
formData.append('attachment', file); // File object directly
const response = await fetch(
`https://api.mailgun.net/v3/${env.MAILGUN_DOMAIN}/messages`,
{
method: 'POST',
headers: {
'Authorization': `Basic ${btoa(`api:${env.MAILGUN_API_KEY}`)}`,
},
body: formData,
}
);
6. Webhooks (Event Tracking)
Resend Webhooks:
Events: email.sent, email.delivered, email.delivery_delayed, email.bounced, email.complained, email.opened, email.clicked
// Verify webhook signature
import { createHmac } from 'crypto';
export async function verifyResendWebhook(
request: Request,
secret: string
): Promise {
const signature = request.headers.get('svix-signature');
const timestamp = request.headers.get('svix-timestamp');
const body = await request.text();
if (!signature || !timestamp) return false;
const signedContent = `${timestamp}.${body}`;
const expectedSignature = createHmac('sha256', secret)
.update(signedContent)
.digest('base64');
return signature.includes(expectedSignature);
}
// Handle webhook
export async function handleResendWebhook(request: Request, env: Env) {
const isValid = await verifyResendWebhook(request, env.RESEND_WEBHOOK_SECRET);
if (!isValid) {
return new Response('Invalid signature', { status: 401 });
}
const event = await request.json();
switch (event.type) {
case 'email.bounced':
// Mark email as invalid
await markEmailInvalid(event.data.email);
break;
case 'email.complained':
// Unsubscribe user
await unsubscribeUser(event.data.email);
break;
}
return new Response('OK');
}
SendGrid Webhooks:
// Verify webhook signature (requires express-style body parser)
import { EventWebhook, EventWebhookHeader } from '@sendgrid/eventwebhook';
export async function verifySendGridWebhook(
request: Request,
publicKey: string
): Promise {
const signature = request.headers.get(EventWebhookHeader.SIGNATURE());
const timestamp = request.headers.get(EventWebhookHeader.TIMESTAMP());
const body = a
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [Kgeminic](https://github.com/Kgeminic)
- **Source:** [Kgeminic/claude-skills-1](https://github.com/Kgeminic/claude-skills-1)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.