XYLEX Group

email hooks

email hooks

this guide covers the email sending hooks and their usage configuration and advanced features

table of contents


overview

the email system provides two hooks:

| hook | purpose | |------|---------| | usesendemail | core hook with full control over email sending | | usesendemailwithnotification | wrapper that automatically shows notifications |

both hooks provide:

  • rate limiting protection
  • duplicate detection
  • email validation
  • domain blocking
  • bulk email support
  • database storage

usesendemail

the core hook for sending emails with comprehensive protections

import

import { useSendEmail } from "@/hooks/use-send-email";
```typescript

### basic usage

```tsx
function SendEmailButton() {
  const {
    sendEmail,
    isLoading,
    isSuccess,
    isError,
    error,
    reset,
  } = useSendEmail();

  const handleSend = async () => {
    const result = await sendEmail({
      to: "customer@example.com",
      subject: "Hello!",
      template_id: "welcome",
      template_data: {
        USER_NAME: "John",
      },
    });

    if (result.success) {
      console.log("Sent! Email ID:", result.email_id);
    } else {
      console.error("Failed:", result.error);
    }
  };

  return (
    <button onClick={handleSend} disabled={isLoading}>
      {isLoading ? "Sending..." : "Send Email"}
    </button>
  );
}
```typescript

### return values

```typescript
interface UseSendEmailReturn {
  // State
  isLoading: boolean;      // Currently sending
  isSuccess: boolean;      // Last send succeeded
  isError: boolean;        // Last send failed
  error: string | null;    // Error message
  lastResponse: SendEmailResponse | null;

  // Methods
  sendEmail: (payload: SendEmailPayload) => Promise<SendEmailResponse>;
  sendBulkEmails: (emails: BulkEmailItem[], options?) => Promise<BulkEmailResponse>;
  canSendEmail: (recipient: string, subject: string) => { canSend: boolean; reason?: string };
  getRateLimitStatus: () => RateLimitStatus;
  reset: () => void;
  clearRateLimits: () => void;
}
```typescript

### sendemail method

send a single email:

```typescript
const result = await sendEmail({
  // Required
  to: "user@example.com",          // Single recipient
  // OR
  to: ["user1@example.com", "user2@example.com"],  // Multiple
  // OR
  to: { email: "user@example.com", name: "John Doe" },  // With name
  
  subject: "Your invoice is ready",
  template_id: "invoice",
  
  // Optional
  template_data: {
    INVOICE_NUMBER: "INV-001",
    AMOUNT: "€100.00",
  },
  from: "Custom Sender <custom@suitsbooks.com>",
  reply_to: "support@suitsbooks.com",
  cc: "cc@example.com",
  bcc: "bcc@example.com",
  attachments: [
    {
      filename: "invoice.pdf",
      content: base64Content,  // Base64 encoded
      contentType: "application/pdf",
    },
  ],
  tags: ["invoice", "important"],
  resource_type_ref: "invoice",
  resource_id_ref: "inv_123456",
  locale: "en-US",
  language: "en",
});
```typescript

### sendbulkemails method

send multiple emails in batches:

```typescript
const emails = [
  {
    id: "email-1",  // Optional ID for tracking
    to: "user1@example.com",
    subject: "Invoice #001",
    template_id: "invoice",
    template_data: { INVOICE_NUMBER: "001" },
  },
  {
    id: "email-2",
    to: "user2@example.com",
    subject: "Invoice #002",
    template_id: "invoice",
    template_data: { INVOICE_NUMBER: "002" },
  },
];

const result = await sendBulkEmails(emails, {
  skipConfirmation: true,  // Skip confirmation for bulk > 10
  batchDelay: 1000,        // 1 second between batches
});

console.log(`Sent: ${result.sent}/${result.total}`);
console.log("Results:", result.results);
// [
//   { id: "email-1", success: true, email_id: "..." },
//   { id: "email-2", success: false, error: "..." },
// ]
```typescript

### cansendemail method

check if an email can be sent (without actually sending):

```typescript
const { canSend, reason } = canSendEmail(
  "user@example.com",
  "Test Subject"
);

if (!canSend) {
  console.log("Cannot send:", reason);
  // "Too many emails sent. Please wait a minute before sending more."
  // "This email was recently sent. Please wait before sending again."
  // "Emails to this domain are not allowed."
}
```typescript

### getratelimitstatus method

get current rate limit status:

```typescript
const status = getRateLimitStatus();

console.log(status);
// {
//   emailsLastMinute: 3,
//   emailsLastHour: 45,
//   emailsLastDay: 200,
//   remainingMinute: 7,
//   remainingHour: 55,
//   remainingDay: 300,
// }
```typescript

### reset method

reset the hook state:

```typescript
const { reset } = useSendEmail();

// After showing success message
reset();  // Clears isSuccess, isError, error
```typescript

### clearratelimits method

clear rate limit tracking (admin use):

```typescript
const { clearRateLimits } = useSendEmail();

// Warning: This removes all rate limit protection
clearRateLimits();
```typescript

---

## usesendemailwithnotification

a wrapper hook that automatically shows notifications using the project's notification system

### import

```tsx
import { useSendEmailWithNotification } from "@/hooks/use-send-email-with-notification";
```typescript

### basic usage

```tsx
function SendInvoiceButton({ invoice }) {
  const { sendEmail, isLoading } = useSendEmailWithNotification();

  const handleSend = async () => {
    // Notification is shown automatically
    await sendEmail({
      to: invoice.customer_email,
      subject: `Invoice ${invoice.number}`,
      template_id: "invoice",
      template_data: {
        INVOICE_NUMBER: invoice.number,
        AMOUNT_TOTAL: invoice.total,
      },
    });
  };

  return (
    <button onClick={handleSend} disabled={isLoading}>
      Send Invoice
    </button>
  );
}
```typescript

### configuration

```tsx
const { sendEmail } = useSendEmailWithNotification({
  // Show success notification (default: true)
  showSuccessNotification: true,

  // Show error notification (default: true)
  showErrorNotification: true,

  // Custom messages
  messages: {
    sendSuccess: "Invoice sent successfully!",
    sendError: "Failed to send invoice",
    bulkSuccess: "All invoices sent",
    bulkPartialSuccess: "Some invoices failed to send",
    bulkError: "Failed to send invoices",
    rateLimited: "Too many emails. Please wait.",
  },

  // Plus all useSendEmail options...
  enableRateLimiting: true,
  maxEmailsPerMinute: 5,
});
```typescript

---

## configuration options

### full configuration

```typescript
interface EmailHookConfig {
  // Rate Limiting
  enableRateLimiting?: boolean;        // Default: true
  maxEmailsPerMinute?: number;         // Default: 10
  maxEmailsPerHour?: number;           // Default: 100
  maxEmailsPerDay?: number;            // Default: 500

  // Duplicate Detection
  enableDuplicateDetection?: boolean;  // Default: true
  duplicateWindowSeconds?: number;     // Default: 60

  // Bulk Sending
  requireBulkConfirmation?: boolean;   // Default: true
  bulkThreshold?: number;              // Default: 10

  // Domain Restrictions
  allowedRecipientDomains?: string[];  // Default: [] (all allowed)
  blockedRecipientDomains?: string[];  // Default: disposable email domains

  // Validation
  enableEmailValidation?: boolean;     // Default: true

  // Storage
  storeInDatabase?: boolean;           // Default: true

  // Callbacks
  onSuccess?: (response: SendEmailResponse) => void;
  onError?: (error: string) => void;
  onRateLimited?: () => void;
}
```typescript

### example configurations

#### strict rate limiting

```tsx
const { sendEmail } = useSendEmail({
  enableRateLimiting: true,
  maxEmailsPerMinute: 3,
  maxEmailsPerHour: 20,
  maxEmailsPerDay: 100,
});
```typescript

#### internal emails only

```tsx
const { sendEmail } = useSendEmail({
  allowedRecipientDomains: [
    "suitsbooks.com",
    "company.internal",
  ],
});
```typescript

#### block disposable emails

```tsx
const { sendEmail } = useSendEmail({
  blockedRecipientDomains: [
    "mailinator.com",
    "guerrillamail.com",
    "tempmail.com",
    "10minutemail.com",
    "throwaway.email",
  ],
});
```typescript

#### disable protections (not recommended)

```tsx
const { sendEmail } = useSendEmail({
  enableRateLimiting: false,
  enableDuplicateDetection: false,
  enableEmailValidation: false,
  requireBulkConfirmation: false,
});
```typescript

---

## examples

### send invoice email

```tsx
import { useSendEmailWithNotification } from "@/hooks/use-send-email-with-notification";
import { Button } from "@/components/ui/button";
import { Send } from "lucide-react";

interface SendInvoiceButtonProps {
  invoice: {
    invoice_id: string;
    number: string;
    customer_email: string;
    customer_name: string;
    total: string;
    due_date: string;
  };
}

export function SendInvoiceButton({ invoice }: SendInvoiceButtonProps) {
  const { sendEmail, isLoading } = useSendEmailWithNotification({
    messages: {
      sendSuccess: `Invoice ${invoice.number} sent successfully`,
      sendError: `Failed to send invoice ${invoice.number}`,
    },
  });

  const handleSend = async () => {
    await sendEmail({
      to: {
        email: invoice.customer_email,
        name: invoice.customer_name,
      },
      subject: `Invoice ${invoice.number} from SuitsBooks`,
      template_id: "invoice",
      template_data: {
        RECIPIENT_NAME: invoice.customer_name,
        AUTHOR_NAME: "SuitsBooks",
        AMOUNT_TOTAL: invoice.total,
        DUE_DATE: invoice.due_date,
        INVOICE_NUMBER: invoice.number,
        INVOICE_URL: `https://app.suitsbooks.com/invoices/${invoice.invoice_id}`,
      },
      resource_type_ref: "invoice",
      resource_id_ref: invoice.invoice_id,
    });
  };

  return (
    <Button
      variant="brand"
      onClick={handleSend}
      disabled={isLoading}
      loading={isLoading}
    >
      <Send className="h-5 w-5 text-white" />
      Send invoice
    </Button>
  );
}
```typescript

### bulk send with progress

```tsx
import { useSendEmail } from "@/hooks/use-send-email";
import { useState } from "react";

export function BulkEmailSender({ invoices }) {
  const { sendBulkEmails, isLoading } = useSendEmail();
  const [progress, setProgress] = useState({ sent: 0, total: 0 });

  const handleBulkSend = async () => {
    const emails = invoices.map((inv) => ({
      id: inv.invoice_id,
      to: inv.customer_email,
      subject: `Invoice ${inv.number}`,
      template_id: "invoice",
      template_data: {
        INVOICE_NUMBER: inv.number,
        AMOUNT_TOTAL: inv.total,
        // ... more data
      },
    }));

    setProgress({ sent: 0, total: emails.length });

    const result = await sendBulkEmails(emails, {
      skipConfirmation: true,
      batchDelay: 500,
    });

    setProgress({ sent: result.sent, total: result.total });

    // Show results
    const failed = result.results.filter((r) => !r.success);
    if (failed.length > 0) {
      console.log("Failed emails:", failed);
    }
  };

  return (
    <div>
      <button onClick={handleBulkSend} disabled={isLoading}>
        Send {invoices.length} Invoices
      </button>
      {isLoading && (
        <p>
          Sending: {progress.sent}/{progress.total}
        </p>
      )}
    </div>
  );
}
```typescript

### check before sending

```tsx
import { useSendEmail } from "@/hooks/use-send-email";

export function SmartEmailSender({ recipient, subject }) {
  const { sendEmail, canSendEmail, getRateLimitStatus } = useSendEmail();

  const handleSend = async () => {
    // Check if we can send
    const { canSend, reason } = canSendEmail(recipient, subject);

    if (!canSend) {
      alert(`Cannot send: ${reason}`);
      return;
    }

    // Check rate limit status
    const status = getRateLimitStatus();
    if (status.remainingMinute < 2) {
      const confirm = window.confirm(
        `You have ${status.remainingMinute} emails remaining this minute. Continue?`
      );
      if (!confirm) return;
    }

    // Send the email
    await sendEmail({
      to: recipient,
      subject,
      template_id: "custom",
      html_body: "<p>Hello!</p>",
    });
  };

  return <button onClick={handleSend}>Send</button>;
}
```typescript

### with custom callbacks

```tsx
import { useSendEmail } from "@/hooks/use-send-email";
import { useRouter } from "next/navigation";

export function SendAndRedirect() {
  const router = useRouter();

  const { sendEmail, isLoading } = useSendEmail({
    onSuccess: (response) => {
      console.log("Email sent:", response.email_id);
      router.push(`/emails/${response.email_id}`);
    },
    onError: (error) => {
      console.error("Failed:", error);
      // Log to error tracking service
    },
    onRateLimited: () => {
      console.warn("User hit rate limit");
      // Show upgrade prompt
    },
  });

  // ... rest of component
}
```typescript

---

## error handling

### error types

```typescript
const ERROR_MESSAGES = {
  RATE_LIMIT_MINUTE: "Too many emails sent. Please wait a minute before sending more.",
  RATE_LIMIT_HOUR: "Hourly email limit reached. Please try again later.",
  RATE_LIMIT_DAY: "Daily email limit reached. Please try again tomorrow.",
  DUPLICATE_EMAIL: "This email was recently sent. Please wait before sending again.",
  INVALID_EMAIL: "Invalid email address provided.",
  BLOCKED_DOMAIN: "Emails to this domain are not allowed.",
  MISSING_TEMPLATE: "Email template not found.",
  MISSING_SUBJECT: "Email subject is required.",
  MISSING_RECIPIENT: "Email recipient is required.",
  MISSING_API_KEY: "Email service not configured.",
  SEND_FAILED: "Failed to send email. Please try again.",
  BULK_CONFIRMATION_REQUIRED: "Bulk email send requires confirmation.",
  UNAUTHORIZED: "You are not authorized to send emails.",
};
```typescript

### handling errors

```tsx
const { sendEmail, isError, error } = useSendEmail();

const handleSend = async () => {
  const result = await sendEmail({ /* ... */ });

  if (!result.success) {
    switch (result.error) {
      case "Too many emails sent. Please wait a minute before sending more.":
        // Show countdown timer
        break;
      case "Invalid email address provided.":
        // Highlight email field
        break;
      default:
        // Generic error handling
        console.error(result.error);
    }
  }
};
```typescript

---

## typescript types

### sendemailpayload

```typescript
interface SendEmailPayload {
  to: string | EmailRecipient | EmailRecipient[];
  subject: string;
  template_id: EmailTemplateId;
  template_data?: TemplateVariables;
  from?: string;
  reply_to?: string;
  cc?: string | EmailRecipient[];
  bcc?: string | EmailRecipient[];
  attachments?: EmailAttachment[];
  tags?: string[];
  resource_type_ref?: string;
  resource_id_ref?: string;
  html_body?: string;  // For template_id: "custom"
  locale?: string;
  language?: string;
}
```typescript

### sendemailresponse

```typescript
interface SendEmailResponse {
  success: boolean;
  email_id?: string;     // Database ID
  resend_id?: string;    // Resend API ID
  error?: string;
  message?: string;
}
```typescript

### bulkemailresponse

```typescript
interface BulkEmailResponse {
  success: boolean;
  total: number;
  sent: number;
  failed: number;
  results: Array<{
    id: string;
    success: boolean;
    email_id?: string;
    error?: string;
  }>;
}
```typescript

### emailrecipient

```typescript
interface EmailRecipient {
  email: string;
  name?: string;
}
```typescript

### emailattachment

```typescript
interface EmailAttachment {
  filename: string;
  content?: string;      // Base64 encoded
  path?: string;         // URL
  contentType?: string;
}
```typescript