Skip to main content

Overview

The sendEmail() function returns a SvelteKit form action that sends emails using Resend or a custom email provider. Use this with the EmailPreview component to test and send emails directly from your browser.

Function Signature

function sendEmail(options?: SendEmailOptions): {
  'send-email': (event: RequestEvent) => Promise<SendEmailResponse>
}

Parameters

options
SendEmailOptions
default:"{}"
Optional configuration object
options.resendApiKey
string
Your Resend API key. Required if not using a custom send function
options.customSendEmailFunction
function
Custom function to send emails using your preferred providerFunction Signature:
(email: {
  from: string;
  to: string;
  subject: string;
  html: string;
}) => Promise<{
  success: boolean;
  error?: any;
}>
options.renderer
Renderer
Custom renderer instance with your Tailwind config. If not provided, uses default renderer
options.from
string
default:"'better-svelte-email <[email protected]>'"
Default sender email address

Return Type

actions
object
SvelteKit actions object to be exported from +page.server.ts
'send-email'
FormAction
Form action that handles email sending requestsSuccess Response:
success
boolean
true if email was sent successfully
error
null
null on success
Error Response:
success
boolean
false if email failed to send
error
object
Error details from the email provider
message
string
Error description

Usage

Basic Example with Resend

+page.server.ts
import { sendEmail } from 'better-svelte-email/preview';
import { PRIVATE_RESEND_API_KEY } from '$env/static/private';

export const actions = sendEmail({
  resendApiKey: PRIVATE_RESEND_API_KEY
});
Security: Never expose your Resend API key to the client. Always import it from $env/static/private or use environment variables on the server.

Custom Sender Address

+page.server.ts
import { sendEmail } from 'better-svelte-email/preview';
import { PRIVATE_RESEND_API_KEY } from '$env/static/private';

export const actions = sendEmail({
  resendApiKey: PRIVATE_RESEND_API_KEY,
  from: 'Your App <[email protected]>'
});

With Custom Renderer

+page.server.ts
import { sendEmail } from 'better-svelte-email/preview';
import { PRIVATE_RESEND_API_KEY } from '$env/static/private';
import Renderer from 'better-svelte-email/render';

const renderer = new Renderer({
  tailwindConfig: {
    theme: {
      extend: {
        colors: {
          brand: '#FF3E00'
        }
      }
    }
  }
});

export const actions = sendEmail({
  resendApiKey: PRIVATE_RESEND_API_KEY,
  renderer
});

Complete Setup

+page.server.ts
import { emailList, createEmail, sendEmail } from 'better-svelte-email/preview';
import { PRIVATE_RESEND_API_KEY } from '$env/static/private';
import Renderer from 'better-svelte-email/render';

const renderer = new Renderer({
  tailwindConfig: {
    theme: {
      extend: {
        colors: {
          brand: '#FF3E00'
        }
      }
    }
  }
});

export function load() {
  const emails = emailList();
  return { emails };
}

export const actions = {
  ...createEmail({ renderer }),
  ...sendEmail({ 
    resendApiKey: PRIVATE_RESEND_API_KEY, 
    renderer,
    from: 'Your App <[email protected]>'
  })
};
+page.svelte
<script>
  import { EmailPreview } from 'better-svelte-email/preview';
  
  export let data;
</script>

<EmailPreview {data} />

Custom Email Provider

You can use any email provider by providing a custom send function:

Postmark Example

+page.server.ts
import { sendEmail } from 'better-svelte-email/preview';
import { ServerClient } from 'postmark';
import { POSTMARK_API_KEY } from '$env/static/private';

const client = new ServerClient(POSTMARK_API_KEY);

const customSendFunction = async (email: {
  from: string;
  to: string;
  subject: string;
  html: string;
}) => {
  try {
    await client.sendEmail({
      From: email.from,
      To: email.to,
      Subject: email.subject,
      HtmlBody: email.html
    });
    return { success: true };
  } catch (error) {
    return { success: false, error };
  }
};

export const actions = sendEmail({
  customSendEmailFunction: customSendFunction,
  from: 'Your App <[email protected]>'
});

SendGrid Example

+page.server.ts
import { sendEmail } from 'better-svelte-email/preview';
import sgMail from '@sendgrid/mail';
import { SENDGRID_API_KEY } from '$env/static/private';

sgMail.setApiKey(SENDGRID_API_KEY);

const customSendFunction = async (email: {
  from: string;
  to: string;
  subject: string;
  html: string;
}) => {
  try {
    await sgMail.send({
      from: email.from,
      to: email.to,
      subject: email.subject,
      html: email.html
    });
    return { success: true };
  } catch (error) {
    return { success: false, error };
  }
};

export const actions = sendEmail({
  customSendEmailFunction: customSendFunction,
  from: 'Your App <[email protected]>'
});
When using a custom send function, you don’t need to provide resendApiKey. The custom function takes precedence.

How It Works

  1. The EmailPreview component submits a form with email details:
    • file: Email component filename
    • path: Path to emails directory
    • to: Recipient email address
    • component: Component name (used in subject)
    • note: Optional note (appended to subject)
  2. The send-email action:
    • Dynamically imports the email component
    • Renders it to HTML using the provided renderer
    • Constructs the email object
    • Sends via Resend or custom function
  3. The response indicates success or failure

Subject Line Format

The subject line is automatically generated:
{component} | {note}
Examples:
  • Component: welcome, Note: empty → Subject: welcome
  • Component: welcome, Note: Testing new design → Subject: welcome | Testing new design

Error Handling

Missing Parameters

{
  "success": false,
  "error": {
    "message": "Missing file or path parameter"
  }
}

Missing API Key

{
  "success": false,
  "error": {
    "message": "Resend API key not configured. Please pass your API key to the sendEmail() function in your +page.server.ts file."
  }
}

Provider Error

Errors from Resend or your custom provider are passed through:
{
  "success": false,
  "error": {
    "message": "Invalid recipient email address",
    "name": "validation_error"
  }
}
Test emails in development before sending in production. Email deliverability can be affected by:
  • Invalid sender/recipient addresses
  • Domain verification status
  • Provider rate limits
  • Spam filters

Best Practices

Environment Variables

Always use environment variables for API keys:
.env
PRIVATE_RESEND_API_KEY=re_xxxxxxxxxxxx

Sender Domain

For production, use a verified domain:
export const actions = sendEmail({
  resendApiKey: PRIVATE_RESEND_API_KEY,
  from: 'Your App <[email protected]>' // Verified domain
});

Rate Limiting

Implement rate limiting to prevent abuse:
+page.server.ts
import { sendEmail } from 'better-svelte-email/preview';
import { PRIVATE_RESEND_API_KEY } from '$env/static/private';
import { rateLimit } from '$lib/rate-limit';

export const actions = {
  'send-email': async (event) => {
    // Check rate limit
    const limited = await rateLimit(event.getClientAddress());
    if (limited) {
      return {
        success: false,
        error: { message: 'Too many requests. Please try again later.' }
      };
    }
    
    // Send email
    const sendAction = sendEmail({ resendApiKey: PRIVATE_RESEND_API_KEY });
    return sendAction['send-email'](event);
  }
};

Access Control

Restrict preview interface to authorized users:
+page.server.ts
import { redirect } from '@sveltejs/kit';

export async function load({ locals }) {
  // Only allow admins to access email preview
  if (!locals.user?.isAdmin) {
    throw redirect(302, '/login');
  }
  
  const emails = emailList();
  return { emails };
}
  • emailList() - Get list of available email components
  • createEmail() - Render email components on demand
  • Renderer - Learn more about custom Tailwind configuration

Build docs developers (and LLMs) love