---
title: "Template: Email Sender"
description: "Transactional email via Resend with template support -- send styled emails to one or multiple recipients"
loadWhen: "Applying the email-sender workflow template"
---

**Category:** Communication

**Platform Tools:** `resend` (send email)

**Credentials Required:**

- `my-resend` -- Resend API key (bearer type)

---

## What This Workflow Does

Sends a transactional email to one or more recipients using the Resend email service. Supports plain text and HTML content, reply-to addresses, and CC/BCC. Suitable for welcome emails, notification emails, confirmation emails, and any triggered transactional communication.

---

## Input Schema

```typescript
z.object({
  to: z.union([z.string(), z.array(z.string())]), // Recipient(s)
  subject: z.string(),                             // Email subject
  html: z.string().optional(),                     // HTML body
  text: z.string().optional(),                     // Plain text body (fallback)
  from: z.string().optional(),                     // Sender (defaults to workspace default)
  replyTo: z.string().optional(),                  // Reply-to address
})
```

## Output Schema

```typescript
z.object({
  sent: z.boolean(),
  messageId: z.string().optional(),
  error: z.string().optional(),
})
```

---

## Workflow Code Pattern

```typescript
import type { WorkflowDefinition } from '@elevasis/sdk'
import { platform, PlatformToolError } from '@elevasis/sdk/worker'
import { z } from 'zod'

const inputSchema = z.object({
  to: z.union([z.string(), z.array(z.string())]),
  subject: z.string(),
  html: z.string().optional(),
  text: z.string().optional(),
  from: z.string().optional(),
  replyTo: z.string().optional(),
})
const outputSchema = z.object({
  sent: z.boolean(),
  messageId: z.string().optional(),
  error: z.string().optional(),
})

type Input = z.infer<typeof inputSchema>

export const emailSender: WorkflowDefinition = {
  config: {
    resourceId: 'email-sender',
    name: 'Email Sender',
    type: 'workflow',
    description: 'Sends transactional email via Resend',
    version: '1.0.0',
    status: 'dev',
  },
  contract: { inputSchema, outputSchema },
  steps: {
    send: {
      id: 'send',
      name: 'Send Email',
      description: 'Send the email via Resend',
      inputSchema,
      outputSchema,
      handler: async (input, context) => {
        const { to, subject, html, text, from, replyTo } = input as Input

        try {
          const result = await platform.call({
            tool: 'resend',
            method: 'sendEmail',
            credential: 'my-resend',
            params: {
              to: Array.isArray(to) ? to : [to],
              subject,
              html: html ?? `<p>${text ?? ''}</p>`,
              text,
              from: from ?? 'noreply@yourdomain.com',
              replyTo,
            },
          }) as { id: string }

          context.logger.info('Email sent', { messageId: result.id, to })
          return { sent: true, messageId: result.id }
        } catch (err) {
          if (err instanceof PlatformToolError) {
            context.logger.error('Email send failed', { error: err.message, to })
            return { sent: false, error: err.message }
          }
          throw err
        }
      },
      next: null,
    },
  },
  entryPoint: 'send',
}
```

---

## Adaptation Notes

- **Credential name:** Replace `'my-resend'` with the user's Resend credential name.
- **From address:** Replace `'noreply@yourdomain.com'` with the user's verified sender address (must be verified in Resend).
- **HTML vs text:** For beginners, suggest writing plain text first and adding HTML later. For developers, show both.
- **Error handling:** The template returns `{ sent: false, error }` on failure rather than throwing, suitable for non-critical emails. For critical notifications, change to `throw err` so the execution fails visibly.
- **Multi-step context:** This template is a single step, but it can be used as the last step of a multi-step workflow (e.g., "process data then send summary email").

---

**Last Updated:** 2026-02-26
