email hooks
email hooks
this guide covers the email sending hooks and their usage configuration and advanced features
table of contents
- overview
- usesendemail
- usesendemailwithnotification
- configuration options
- examples
- error handling
- typescript types
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