Send Remix email from the mutation, not the component. An action accepts it; a webhook reports the outcome.
Queue transactional mail from a server action or resource route, return the accepted message ID, and keep delivery work out of the browser lifecycle.
✓Server-side in actions and resource routes only.
One request, complete trace
Request
POST /api/v1/emails
Authorization: Bearer aem_…
Idempotency-Key: welcome-8f21
{ "to": "[email protected]", "template": "welcome" }
202 Accepted
{ "id": "msg_7ca31", "status": "queued" }
[ 01 / 03 ]Integration shape
Keep the email boundary aligned with the data mutation.
Use actions and server loaders
Actions belong with form mutations; resource routes expose a focused HTTP boundary for services and webhooks.
Publish product events
Publish the event after the action commits the product change, rather than sending because a component rendered.
Verify the webhook
A resource route reading await request.text() for signature verification.
[ 02 / 03 ]Server quickstart
Queue one email from a Remix action.
The action keeps the key private, protects repeated submissions with an idempotency key, and returns an accepted ID.
Keep the call inside the server action, not the browser component.
export async function action({ request }: ActionFunctionArgs) {
const input = await request.json();
return fetch(EMAIL_URL, { method: "POST",
headers: authHeaders(`welcome-${input.userId}`),
body: JSON.stringify({ to: input.email, template: "welcome" }) });
}Accepted response
{ "id": "msg_7ca31", "status": "queued" }[ 03 / 03 ]Related
Keep building with Remix
Questions people ask us
Give your next Remix action a delivery trail.
Queue the message with a stable key and verify the result in a resource route.