Install
$ agentstack add skill-stonegiantstudio-skills-resend Open-source listing, not yet scanned by AgentStack. Follow the source repository for install instructions.
Security review
⚠ Flagged1 finding(s); flagged for manual review. · v0.1.0 How review works →
- • Prompt-injection patterns
- • Secret / credential exfiltration
- • Dangerous shell & filesystem operations
- • Untrusted network calls
- • Known-malicious package signatures
- high Reads credentials/environment and may exfiltrate them.
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.
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
Resend Email API
Resend is the email API for developers. TypeScript-first, excellent DX.
Always consult resend.com/docs for latest API.
> Upstream: Based on resend/resend-skills. Check weekly for updates.
Quick Start
# Install
add resend
# Environment
RESEND_API_KEY=re_xxxxxxxxx # Get from resend.com/api-keys
Decision: Single vs Batch
| Approach | Endpoint | Use Case | |----------|----------|----------| | Single | POST /emails | Individual emails, attachments, scheduled sends | | Batch | POST /emails/batch | 2-100 distinct emails, reduce API calls |
Choose batch when: 2+ emails, no attachments, no scheduling, reducing API calls matters (rate limit: 2 req/sec default)
Choose single when: One email, needs attachments, needs scheduling, different timing per recipient
Single Email
import { Resend } from "resend";
const resend = new Resend(process.env.RESEND_API_KEY);
// With idempotency key (always use in production)
const { data, error } = await resend.emails.send(
{
from: "Acme ",
to: ["user@example.com"],
subject: "Welcome to Acme",
html: "Thanks for signing up!",
},
{ idempotencyKey: `welcome-email/${userId}` }
);
if (error) {
console.error("Failed:", error.message);
return;
}
console.log("Sent:", data.id);
Required Parameters
| Parameter | Type | Description | |-----------|------|-------------| | from | string | Sender: "Name " | | to | string[] | Recipients (max 50) | | subject | string | Subject line | | html or text | string | Body content |
Optional Parameters
| Parameter | Type | Description | |-----------|------|-------------| | cc | string[] | CC recipients | | bcc | string[] | BCC recipients | | replyTo | string[] | Reply-to addresses | | scheduledAt | string | ISO 8601 datetime | | attachments | array | Files (max 40MB total) | | tags | array | Key/value pairs for tracking | | headers | object | Custom headers |
Batch Email
const { data, error } = await resend.batch.send(
[
{
from: "Acme ",
to: ["user1@example.com"],
subject: "Order Shipped",
html: "Your order has shipped!",
},
{
from: "Acme ",
to: ["user2@example.com"],
subject: "Order Confirmed",
html: "Your order is confirmed!",
},
],
{ idempotencyKey: `batch-orders/${batchId}` }
);
Limitations:
- No attachments (use single sends)
- No scheduling (use single sends)
- Max 100 emails per request
- Atomic: one invalid email fails entire batch
Large Batches (100+ Emails)
function chunkArray(array: T[], size: number): T[][] {
const chunks: T[][] = [];
for (let i = 0; i
resend.batch.send(chunk, {
idempotencyKey: `batch-${batchId}/chunk-${index}`,
})
)
);
React Router Integration
// app/lib/email.server.ts
import { Resend } from "resend";
const resend = new Resend(process.env.RESEND_API_KEY);
export async function sendWelcomeEmail(user: { email: string; name: string }) {
const { error } = await resend.emails.send(
{
from: "Acme ",
to: [user.email],
subject: "Welcome to Acme!",
html: `Hi ${user.name}, welcome aboard!`,
},
{ idempotencyKey: `welcome/${user.email}` }
);
if (error) {
console.error("Email failed:", error);
throw new Error("Failed to send welcome email");
}
}
// app/routes/signup.tsx
import { sendWelcomeEmail } from "~/lib/email.server";
export async function action({ request }: Route.ActionArgs) {
const formData = await request.formData();
const user = await createUser(formData);
// Fire and forget (or await if critical)
sendWelcomeEmail(user).catch(console.error);
return redirect("/dashboard");
}
Idempotency Keys
Always use in production - prevents duplicate sends on retry.
| Format | Example | |--------|---------| | Single | / → welcome-email/user-123 | | Batch | batch-/ → batch-orders/batch-456 |
- Expires after 24 hours
- Max 256 characters
- Same key + same payload = returns original response (no resend)
- Same key + different payload = 409 error
Error Handling
| Code | Action | |------|--------| | 400, 422 | Fix request, don't retry | | 401, 403 | Check API key / domain, don't retry | | 409 | Idempotency conflict - new key or fix payload | | 429 | Rate limited - exponential backoff | | 500 | Server error - exponential backoff |
async function sendWithRetry(
emailFn: () => Promise,
maxRetries = 3
) {
for (let attempt = 0; attempt setTimeout(r, delay));
}
return { data: null, error: new Error("Max retries exceeded") };
}
Webhooks
Track delivery status in real-time.
| Event | Use Case | |-------|----------| | email.delivered | Confirm delivery | | email.bounced | Remove from list, alert user | | email.complained | Auto-unsubscribe (spam complaint) | | email.opened | Track engagement (marketing only) | | email.clicked | Track engagement (marketing only) |
Always verify signatures:
import { Webhook } from "resend";
export async function action({ request }: Route.ActionArgs) {
const payload = await request.text();
const headers = Object.fromEntries(request.headers);
const webhook = new Webhook(process.env.RESEND_WEBHOOK_SECRET!);
try {
const event = webhook.verify(payload, headers);
switch (event.type) {
case "email.bounced":
await handleBounce(event.data);
break;
case "email.complained":
await handleComplaint(event.data);
break;
}
return new Response("OK", { status: 200 });
} catch {
return new Response("Invalid signature", { status: 401 });
}
}
Templates
const { data, error } = await resend.emails.send({
from: "Acme ",
to: ["user@example.com"],
subject: "Welcome!",
template: {
id: "tmpl_abc123",
variables: {
USER_NAME: "John", // Case-sensitive!
ORDER_TOTAL: "$99.00",
},
},
});
Important:
- Variable names are case-sensitive (
USER_NAME≠user_name) - Max 20 variables per template
- Must be published in dashboard (drafts don't work)
- Can't combine with
html,text, orreactparams
Tags
tags: [
{ name: "user_id", value: "usr_123" },
{ name: "email_type", value: "welcome" },
{ name: "plan", value: "enterprise" },
]
Use for: filtering in dashboard, correlating webhooks, analytics.
Testing
Never use fake addresses at real providers (test@gmail.com destroys reputation).
| Address | Result | |---------|--------| | delivered@resend.dev | Simulates success | | bounced@resend.dev | Simulates hard bounce | | complained@resend.dev | Simulates spam complaint |
Deliverability Checklist
Required:
- [ ] Valid SPF, DKIM, DMARC records
- [ ] Links match sending domain (send from @acme.com → link to acme.com)
- [ ] Include plain text version (or let Resend auto-generate)
- [ ] Avoid "no-reply" addresses (use support@, hello@)
- [ ] Body under 102KB (Gmail clips larger)
Recommended:
- [ ] Use subdomains (notifications.acme.com for transactional)
- [ ] Disable tracking for transactional emails (password resets, receipts)
Domain Warm-up (New Domains)
| Day | Max Emails | Max/Hour | |-----|------------|----------| | 1 | 150 | - | | 2 | 250 | - | | 3 | 400 | - | | 4 | 700 | 50 | | 5 | 1,000 | 75 | | 6 | 1,500 | 100 | | 7 | 2,000 | 150 |
Monitor: Bounce rate < 4%, Spam complaints < 0.08%
Common Mistakes
| Mistake | Fix | |---------|-----| | Retrying without idempotency key | Always include - prevents duplicates | | Batch with attachments | Use single sends for attachments | | Retrying 400/422 errors | Fix request, don't retry | | Tracking on transactional | Disable for password resets, receipts | | Using "no-reply" sender | Use real address (support@) | | Not verifying webhook signatures | Always verify - security critical | | Testing with fake emails | Use @resend.dev test addresses | | High volume from new domain | Warm up gradually |
Resources
- Documentation
- API Reference
- React Email - Component library for emails
- Official Skills - Upstream source
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: stonegiantstudio
- Source: stonegiantstudio/skills
- License: Apache-2.0
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.