# Ekx Resend

> Transactional email with Resend — sending from a route handler or cron, React Email templates, domain verification and DNS, dry-run mode for local development, and delivery webhooks. Use when sending an alert, receipt, magic link or digest email, when email is not arriving, or when setting up a new sending domain.

- Skill: `ekinoxis-evm/ekx-resend` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ekinoxis-evm/ekx-resend`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ekinoxis-evm/ekx-resend/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: Ekinoxis-evm (https://skillmd.com/u/ekinoxis-evm)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/ekinoxis-evm/ekx-resend

---


# Resend

Our transactional email vendor across the portfolio.
There is a bundled `resend` skill in this Claude installation and a **`resend` MCP
server** — both are authoritative over this file for API detail.

Docs: https://resend.com/docs

---

## Environment

```bash
RESEND_API_KEY=          # SECRET
RESEND_FROM=             # "Ekinoxis <alerts@ekinoxis.xyz>"
RESEND_FROM_EMAIL=       # same thing, older name — pick one per repo
RESEND_DRY_RUN=true      # local: log instead of send
```

`RESEND_DRY_RUN` came out of a scheduled scanner and is worth copying everywhere. That
scanner emails on every detected opportunity; without a dry-run flag, a local debugging
session sends a few hundred real emails and burns the domain's reputation.

```ts
if (process.env.RESEND_DRY_RUN === "true") {
  console.log("[resend:dry-run]", { to, subject });
  return { id: "dry-run" };
}
```

---

## Sending

```ts
import { Resend } from "resend";
const resend = new Resend(process.env.RESEND_API_KEY!);

const { data, error } = await resend.emails.send({
  from: process.env.RESEND_FROM!,
  to: [user.email],
  subject: "Oportunidad detectada — spread 4.2%",
  react: <OpportunityEmail opportunity={opp} />,   // or: html / text
  headers: { "X-Entity-Ref-ID": opportunity.id },  // idempotency hint
});

if (error) {
  // Resend returns { data: null, error } — it does NOT throw.
  console.error("resend failed", error);
}
```

**Resend returns errors, it does not throw.** Code that only wraps the call in
`try/catch` will report success on every failure. Always destructure and check `error`.

Batch (up to 100 per call):

```ts
await resend.batch.send([{ from, to, subject, html }, /* … */]);
```

---

## Domain setup

Verify the sending domain in the dashboard, then add the DNS records it gives you:
**SPF**, **DKIM**, and a **DMARC** record. All three. Sending from an unverified
domain works only to your own address; sending from a verified domain without DMARC
lands in spam at Gmail and Outlook.

Use a subdomain for transactional mail (`mail.ekinoxis.xyz`) so a reputation problem
never touches the root domain's deliverability.

---

## Templates

React Email components live alongside the app and are type-checked with the data they
render — better than HTML strings:

```tsx
export function OpportunityEmail({ opportunity }: { opportunity: Opportunity }) {
  return (
    <Html><Body>
      <Heading>Spread {opportunity.spread}%</Heading>
      <Text>{opportunity.marketA} vs {opportunity.marketB}</Text>
      <Button href={opportunity.url}>Ver en Polymarket</Button>
    </Body></Html>
  );
}
```

Always provide a plain-text fallback — some clients, and most spam filters, want one.

---

## From a cron

A scheduled scan that emails on a hit. Two rules:

1. **Deduplicate before sending.** Store what was already alerted in Supabase and check before send, or a stuck condition emails on every tick.
2. **Rate-limit.** Resend's default is 2 requests/second. A loop over 200 recipients needs `batch.send` or a delay, not 200 concurrent calls.

---

## Gotchas

1. **Errors are returned, not thrown.** Check `error`.
2. **Unverified domain** silently limits delivery to your own address.
3. **Missing DMARC** → spam folder at the big providers.
4. **2 req/s default rate limit.**
5. **No dry-run flag** in local dev = real emails from your laptop.
6. **`to` is an array.** A bare string works in some SDK versions and not others; always pass an array.

