# Remix and React Router

An action function, where the server code and the component sit in the same file.

In Remix, and in React Router 7 in framework mode, anything inside loader or action is stripped from the client bundle. The send goes there. This page works unchanged for both.

### Install

Remix 2 / React Router 7:
```bash
npm install @emails.sh/sdk
```

.env:
```bash
# .env, read by the dev server. The key comes from
# https://emails.sh/dashboard/api-keys.
EMAILSSH_API_KEY=esh_your_key_here
```

### The route

app/routes/subscribe.tsx:
```ts
// app/routes/subscribe.tsx
import { Form, useActionData } from '@remix-run/react';
import { json, type ActionFunctionArgs } from '@remix-run/node';
import { Emailssh } from '@emails.sh/sdk';

export async function action({ request }: ActionFunctionArgs) {
  const form = await request.formData();
  const address = String(form.get('email') ?? '');
  if (!address.includes('@')) return json({ message: 'Enter an email address.' }, { status: 400 });

  const mail = new Emailssh({ apiKey: process.env.EMAILSSH_API_KEY });

  try {
    const { id } = await mail.send({
      from: 'Acme <hello@acme.com>',
      to: [address],
      subject: 'Confirm your subscription',
      html: '<p>Click the link in this email to confirm.</p>',
      idempotencyKey: `subscribe-${address}`
    });
    return json({ id });
  } catch (err) {
    console.error('emails.sh refused the send', err);
    return json({ message: 'Could not send that email.' }, { status: 502 });
  }
}

export default function Subscribe() {
  const data = useActionData<typeof action>();

  return (
    <Form method="post">
      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" required />
      <button type="submit">Subscribe</button>
      {data && 'message' in data ? <p>{data.message}</p> : null}
      {data && 'id' in data ? <p>Check your inbox.</p> : null}
    </Form>
  );
}
```

On the Cloudflare Pages adapter there is no process.env: the key arrives on context.cloudflare.env, so read it from the action arguments rather than the global.

---

Base URL: https://emails.sh/v1. Auth: `Authorization: Bearer esh_...`.
Whole API in one file: https://emails.sh/llms.txt. All documentation: https://emails.sh/docs.md.
