Notifications
- @ductape/client
- React
- Vue 3
Send notifications through various channels (email, SMS, push notifications) using Ductape's unified notification system.
When using a publishable key (frontend), include session (token from your backend) in every request.
Sending Notifications
Send Email Notification
import { Ductape } from '@ductape/client';
const ductape = new Ductape({
publishableKey: 'your-publishable-key',
product: 'your-product',
env: 'prd'
});
const sessionToken = getSessionFromYourBackend();
// Send email notification
await ductape.notifications.send({
channel: 'email',
to: 'user@example.com',
template: 'welcome-email',
data: {
name: 'John Doe',
verificationLink: 'https://app.com/verify?token=abc123'
},
session: sessionToken,
});
Send SMS Notification
await ductape.notifications.send({
channel: 'sms',
to: '+1234567890',
template: 'verification-code',
data: {
code: '123456',
expiresIn: '10 minutes'
}
});
Send Push Notification
await ductape.notifications.send({
channel: 'push',
to: 'user-device-token',
template: 'new-message',
data: {
title: 'New Message',
body: 'You have a new message from John',
badge: 1,
sound: 'default'
}
});
Multiple Recipients
// Send to multiple recipients
await ductape.notifications.send({
channel: 'email',
to: ['user1@example.com', 'user2@example.com', 'user3@example.com'],
template: 'team-announcement',
data: {
announcement: 'New features released!',
link: 'https://app.com/releases'
}
});
Custom Templates
// Send with inline template
await ductape.notifications.send({
channel: 'email',
to: 'user@example.com',
subject: 'Welcome to Our App',
body: `
<h1>Welcome {{name}}!</h1>
<p>Thank you for joining us.</p>
<a href="{{link}}">Get Started</a>
`,
data: {
name: 'John Doe',
link: 'https://app.com/onboarding'
}
});
Notification with Attachments
// Email with attachments
await ductape.notifications.send({
channel: 'email',
to: 'user@example.com',
template: 'invoice',
data: {
invoiceNumber: 'INV-001',
amount: 99.99
},
attachments: [
{
filename: 'invoice.pdf',
content: pdfBuffer,
contentType: 'application/pdf'
}
]
});
Scheduled Notifications
// Schedule notification for later
await ductape.notifications.send({
channel: 'email',
to: 'user@example.com',
template: 'reminder',
data: {
eventName: 'Team Meeting',
eventTime: '2pm today'
},
scheduledFor: new Date('2024-01-15T13:00:00Z')
});
Notification Preferences
// Send with user preferences
await ductape.notifications.send({
channel: 'email',
to: 'user@example.com',
template: 'newsletter',
data: {
articles: [...]
},
respectUserPreferences: true, // Won't send if user opted out
userId: 'user-123'
});
Track Notification Status
// Send and get notification ID
const notification = await ductape.notifications.send({
channel: 'email',
to: 'user@example.com',
template: 'order-confirmation',
data: {
orderId: 'ORDER-123',
total: 299.99
}
});
// Check notification status
const status = await ductape.notifications.getStatus(notification.id);
console.log(status.delivered); // true/false
console.log(status.opened); // true/false
console.log(status.clicked); // true/false
Batch Notifications
// Send batch notifications efficiently
const recipients = [
{ email: 'user1@example.com', name: 'User 1' },
{ email: 'user2@example.com', name: 'User 2' },
{ email: 'user3@example.com', name: 'User 3' }
];
await ductape.notifications.sendBatch({
channel: 'email',
template: 'promotional-offer',
recipients: recipients.map(user => ({
to: user.email,
data: {
name: user.name,
offer: '20% OFF',
code: `SAVE20-${user.email.split('@')[0].toUpperCase()}`
}
}))
});
Rich Notifications
// Send rich push notification
await ductape.notifications.send({
channel: 'push',
to: 'device-token',
data: {
title: 'New Photo',
body: 'John shared a photo with you',
image: 'https://cdn.example.com/photo.jpg',
actions: [
{ action: 'view', title: 'View Photo' },
{ action: 'like', title: 'Like' }
],
data: {
photoId: 'photo-123',
userId: 'john-456'
}
}
});
Error Handling
try {
await ductape.notifications.send({
channel: 'email',
to: 'invalid-email',
template: 'welcome'
});
} catch (error) {
if (error.code === 'INVALID_EMAIL') {
console.error('Invalid email address');
} else if (error.code === 'TEMPLATE_NOT_FOUND') {
console.error('Template does not exist');
} else if (error.code === 'RATE_LIMIT_EXCEEDED') {
console.error('Too many notifications sent');
}
}
Complete Example
class NotificationService {
private ductape: Ductape;
constructor() {
this.ductape = new Ductape({
accessKey: process.env.DUCTAPE_ACCESS_KEY!,
product: 'my-app',
env: 'prd'
});
}
async sendWelcomeEmail(userEmail: string, userName: string) {
await this.ductape.notifications.send({
channel: 'email',
to: userEmail,
template: 'welcome-email',
data: {
name: userName,
loginLink: 'https://app.com/login'
}
});
}
async sendOrderConfirmation(order: any) {
// Send email
await this.ductape.notifications.send({
channel: 'email',
to: order.customerEmail,
template: 'order-confirmation',
data: {
orderNumber: order.id,
items: order.items,
total: order.total,
trackingLink: order.trackingUrl
}
});
// Send SMS
await this.ductape.notifications.send({
channel: 'sms',
to: order.customerPhone,
template: 'order-sms',
data: {
orderNumber: order.id,
trackingLink: order.trackingUrl
}
});
// Send push notification
if (order.deviceToken) {
await this.ductape.notifications.send({
channel: 'push',
to: order.deviceToken,
data: {
title: 'Order Confirmed',
body: `Your order #${order.id} has been confirmed`,
badge: 1
}
});
}
}
async sendPasswordReset(email: string, resetToken: string) {
await this.ductape.notifications.send({
channel: 'email',
to: email,
template: 'password-reset',
data: {
resetLink: `https://app.com/reset-password?token=${resetToken}`,
expiresIn: '1 hour'
}
});
}
async sendTeamInvitation(inviterName: string, inviteeEmail: string, teamName: string) {
await this.ductape.notifications.send({
channel: 'email',
to: inviteeEmail,
template: 'team-invitation',
data: {
inviterName,
teamName,
acceptLink: `https://app.com/invitations/accept?email=${inviteeEmail}`
}
});
}
}
// Usage
const notifications = new NotificationService();
await notifications.sendWelcomeEmail('user@example.com', 'John Doe');
await notifications.sendOrderConfirmation(orderData);
await notifications.sendPasswordReset('user@example.com', 'reset-token-123');
Next Steps
React hooks for sending notifications through email, SMS, and push notification channels.
When using a publishable key, include session in every notification call. Example: import { session } from './config'.
useNotification
Send notifications with automatic loading states and error handling.
Send Email Notification
import { useNotification } from '@ductape/react';
import { session } from './config';
function WelcomeEmail({ userEmail, userName }: { userEmail: string; userName: string }) {
const { mutate: sendNotification, isLoading, error } = useNotification({
onSuccess: () => {
alert('Welcome email sent!');
}
});
const handleSend = () => {
sendNotification({
channel: 'email',
to: userEmail,
template: 'welcome-email',
data: {
name: userName,
loginLink: 'https://app.com/login'
},
session,
});
};
return (
<button onClick={handleSend} disabled={isLoading}>
{isLoading ? 'Sending...' : 'Send Welcome Email'}
</button>
);
}
Send SMS Notification
import { session } from './config';
function SendVerificationCode({ phoneNumber }: { phoneNumber: string }) {
const [code] = useState(() => Math.floor(100000 + Math.random() * 900000).toString());
const { mutate, isLoading } = useNotification({
onSuccess: () => {
alert('Verification code sent!');
}
});
const handleSend = () => {
mutate({
channel: 'sms',
to: phoneNumber,
template: 'verification-code',
data: {
code,
expiresIn: '10 minutes'
},
session,
});
};
return (
<button onClick={handleSend} disabled={isLoading}>
{isLoading ? 'Sending...' : 'Send Code'}
</button>
);
}
Send Push Notification
import { session } from './config';
function SendPushNotification({ deviceToken, message }: { deviceToken: string; message: string }) {
const { mutate, isLoading } = useNotification();
const handleSend = () => {
mutate({
channel: 'push',
to: deviceToken,
data: {
title: 'New Message',
body: message,
badge: 1,
sound: 'default'
},
session,
});
};
return (
<button onClick={handleSend} disabled={isLoading}>
Send Push
</button>
);
}
useNotificationBatch
Send notifications to multiple recipients.
function BulkEmailSender() {
const [recipients, setRecipients] = useState<string[]>([]);
const { mutate: sendBatch, isLoading } = useNotificationBatch({
onSuccess: (result) => {
alert(`Sent ${result.successCount} emails successfully`);
}
});
const handleSendAll = () => {
sendBatch({
channel: 'email',
template: 'newsletter',
recipients: recipients.map(email => ({
to: email,
data: {
unsubscribeLink: `https://app.com/unsubscribe?email=${email}`
}
}))
});
};
return (
<div>
<textarea
placeholder="Enter emails (one per line)"
onChange={(e) => setRecipients(e.target.value.split('\n').filter(Boolean))}
/>
<button onClick={handleSendAll} disabled={isLoading || recipients.length === 0}>
{isLoading ? `Sending to ${recipients.length} recipients...` : 'Send Newsletter'}
</button>
</div>
);
}
useNotificationStatus
Track notification delivery status.
function NotificationTracker({ notificationId }: { notificationId: string }) {
const { data: status, isLoading } = useNotificationStatus(notificationId, {
refetchInterval: 5000 // Poll every 5 seconds
});
if (isLoading) return <div>Loading status...</div>;
return (
<div className="notification-status">
<h3>Notification Status</h3>
<div>
<span>Sent:</span> {status?.sent ? '✓' : '✗'}
</div>
<div>
<span>Delivered:</span> {status?.delivered ? '✓' : '✗'}
</div>
<div>
<span>Opened:</span> {status?.opened ? '✓' : '✗'}
</div>
<div>
<span>Clicked:</span> {status?.clicked ? '✓' : '✗'}
</div>
{status?.deliveredAt && (
<div>
<span>Delivered at:</span> {new Date(status.deliveredAt).toLocaleString()}
</div>
)}
</div>
);
}
Complete Examples
Order Confirmation
function OrderConfirmation({ order }: { order: any }) {
const { mutate: sendEmail } = useNotification();
const { mutate: sendSMS } = useNotification();
const { mutate: sendPush } = useNotification();
useEffect(() => {
sendOrderNotifications();
}, [order.id]);
const sendOrderNotifications = async () => {
// Send email
await sendEmail({
channel: 'email',
to: order.customerEmail,
template: 'order-confirmation',
data: {
orderNumber: order.id,
items: order.items,
total: order.total,
trackingLink: order.trackingUrl
}
});
// Send SMS
if (order.customerPhone) {
await sendSMS({
channel: 'sms',
to: order.customerPhone,
template: 'order-sms',
data: {
orderNumber: order.id,
trackingLink: order.trackingUrl
}
});
}
// Send push notification
if (order.deviceToken) {
await sendPush({
channel: 'push',
to: order.deviceToken,
data: {
title: 'Order Confirmed',
body: `Your order #${order.id} has been confirmed`,
badge: 1
}
});
}
};
return (
<div className="order-confirmation">
<h2>Order Confirmed!</h2>
<p>Order #{order.id}</p>
<p>Confirmation sent to {order.customerEmail}</p>
</div>
);
}
Password Reset
function ForgotPasswordForm() {
const [email, setEmail] = useState('');
const { mutate: sendReset, isLoading, error } = useNotification({
onSuccess: () => {
alert('Password reset link sent! Check your email.');
}
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Generate reset token (in real app, do this on server)
const resetToken = Math.random().toString(36).substring(2);
sendReset({
channel: 'email',
to: email,
template: 'password-reset',
data: {
resetLink: `https://app.com/reset-password?token=${resetToken}`,
expiresIn: '1 hour'
}
});
};
return (
<form onSubmit={handleSubmit}>
<h2>Forgot Password</h2>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
required
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Sending...' : 'Send Reset Link'}
</button>
{error && <p className="error">{error.message}</p>}
</form>
);
}
Team Invitation
function InviteTeamMember() {
const [email, setEmail] = useState('');
const [role, setRole] = useState('member');
const { session } = useSession();
const { mutate: sendInvite, isLoading } = useNotification({
onSuccess: () => {
alert('Invitation sent!');
setEmail('');
}
});
const handleInvite = (e: React.FormEvent) => {
e.preventDefault();
sendInvite({
channel: 'email',
to: email,
template: 'team-invitation',
data: {
inviterName: session.user.name,
teamName: session.team.name,
role,
acceptLink: `https://app.com/invitations/accept?email=${email}&role=${role}`
}
});
};
return (
<form onSubmit={handleInvite}>
<h3>Invite Team Member</h3>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email address"
required
/>
<select value={role} onChange={(e) => setRole(e.target.value)}>
<option value="member">Member</option>
<option value="admin">Admin</option>
<option value="owner">Owner</option>
</select>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Sending...' : 'Send Invitation'}
</button>
</form>
);
}
Scheduled Notification
function ScheduleReminder() {
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
const [scheduledTime, setScheduledTime] = useState('');
const { mutate, isLoading } = useNotification({
onSuccess: () => {
alert('Reminder scheduled!');
}
});
const handleSchedule = (e: React.FormEvent) => {
e.preventDefault();
mutate({
channel: 'email',
to: email,
template: 'reminder',
data: {
message,
scheduledFor: scheduledTime
},
scheduledFor: new Date(scheduledTime)
});
};
return (
<form onSubmit={handleSchedule}>
<h3>Schedule Reminder</h3>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
/>
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Reminder message"
required
/>
<input
type="datetime-local"
value={scheduledTime}
onChange={(e) => setScheduledTime(e.target.value)}
required
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Scheduling...' : 'Schedule Reminder'}
</button>
</form>
);
}
Multi-Channel Notification
function MultiChannelAlert({ userId, message }: { userId: string; message: string }) {
const { data: user } = useQuery(['user', userId], () => fetchUser(userId));
const { mutate: sendEmail } = useNotification();
const { mutate: sendSMS } = useNotification();
const { mutate: sendPush } = useNotification();
const sendAlert = async () => {
const promises = [];
// Email
if (user?.email) {
promises.push(
sendEmail({
channel: 'email',
to: user.email,
subject: 'Important Alert',
body: message
})
);
}
// SMS
if (user?.phone) {
promises.push(
sendSMS({
channel: 'sms',
to: user.phone,
template: 'alert-sms',
data: { message }
})
);
}
// Push
if (user?.deviceTokens?.length > 0) {
user.deviceTokens.forEach(token => {
promises.push(
sendPush({
channel: 'push',
to: token,
data: {
title: 'Alert',
body: message,
priority: 'high'
}
})
);
});
}
await Promise.all(promises);
};
return (
<button onClick={sendAlert}>
Send Multi-Channel Alert
</button>
);
}
Next Steps
Vue 3 composables for sending notifications through email, SMS, and push notification channels.
useNotification
Send notifications with reactive loading states and error handling.
Send Email Notification
<script setup lang="ts">
import { useNotification } from '@ductape/vue';
const props = defineProps<{
userEmail: string;
userName: string;
}>();
const { mutate: sendNotification, isLoading, error } = useNotification({
onSuccess: () => {
alert('Welcome email sent!');
}
});
const handleSend = () => {
sendNotification({
channel: 'email',
to: props.userEmail,
template: 'welcome-email',
data: {
name: props.userName,
loginLink: 'https://app.com/login'
}
});
};
</script>
<template>
<button @click="handleSend" :disabled="isLoading">
{{ isLoading ? 'Sending...' : 'Send Welcome Email' }}
</button>
</template>
Send SMS Notification
<script setup lang="ts">
import { ref } from 'vue';
import { useNotification } from '@ductape/vue';
const props = defineProps<{ phoneNumber: string }>();
const code = ref(Math.floor(100000 + Math.random() * 900000).toString());
const { mutate, isLoading } = useNotification({
onSuccess: () => {
alert('Verification code sent!');
}
});
const handleSend = () => {
mutate({
channel: 'sms',
to: props.phoneNumber,
template: 'verification-code',
data: {
code: code.value,
expiresIn: '10 minutes'
}
});
};
</script>
<template>
<button @click="handleSend" :disabled="isLoading">
{{ isLoading ? 'Sending...' : 'Send Code' }}
</button>
</template>
Send Push Notification
<script setup lang="ts">
import { useNotification } from '@ductape/vue';
const props = defineProps<{
deviceToken: string;
message: string;
}>();
const { mutate, isLoading } = useNotification();
const handleSend = () => {
mutate({
channel: 'push',
to: props.deviceToken,
data: {
title: 'New Message',
body: props.message,
badge: 1,
sound: 'default'
}
});
};
</script>
<template>
<button @click="handleSend" :disabled="isLoading">
Send Push
</button>
</template>
useNotificationBatch
Send notifications to multiple recipients.
<script setup lang="ts">
import { ref } from 'vue';
import { useNotificationBatch } from '@ductape/vue';
const recipients = ref<string[]>([]);
const recipientsText = ref('');
const { mutate: sendBatch, isLoading } = useNotificationBatch({
onSuccess: (result) => {
alert(`Sent ${result.successCount} emails successfully`);
}
});
const handleSendAll = () => {
const emails = recipientsText.value.split('\n').filter(Boolean);
recipients.value = emails;
sendBatch({
channel: 'email',
template: 'newsletter',
recipients: emails.map(email => ({
to: email,
data: {
unsubscribeLink: `https://app.com/unsubscribe?email=${email}`
}
}))
});
};
</script>
<template>
<div>
<textarea
v-model="recipientsText"
placeholder="Enter emails (one per line)"
/>
<button @click="handleSendAll" :disabled="isLoading || !recipientsText">
{{ isLoading ? `Sending to ${recipients.length} recipients...` : 'Send Newsletter' }}
</button>
</div>
</template>
useNotificationStatus
Track notification delivery status.
<script setup lang="ts">
import { useNotificationStatus } from '@ductape/vue';
const props = defineProps<{ notificationId: string }>();
const { data: status, isLoading } = useNotificationStatus(props.notificationId, {
refetchInterval: 5000 // Poll every 5 seconds
});
</script>
<template>
<div v-if="isLoading">Loading status...</div>
<div v-else class="notification-status">
<h3>Notification Status</h3>
<div>
<span>Sent:</span> {{ status?.sent ? '✓' : '✗' }}
</div>
<div>
<span>Delivered:</span> {{ status?.delivered ? '✓' : '✗' }}
</div>
<div>
<span>Opened:</span> {{ status?.opened ? '✓' : '✗' }}
</div>
<div>
<span>Clicked:</span> {{ status?.clicked ? '✓' : '✗' }}
</div>
<div v-if="status?.deliveredAt">
<span>Delivered at:</span> {{ new Date(status.deliveredAt).toLocaleString() }}
</div>
</div>
</template>
Complete Examples
Order Confirmation
<script setup lang="ts">
import { onMounted } from 'vue';
import { useNotification } from '@ductape/vue';
const props = defineProps<{ order: any }>();
const { mutate: sendEmail } = useNotification();
const { mutate: sendSMS } = useNotification();
const { mutate: sendPush } = useNotification();
onMounted(() => {
sendOrderNotifications();
});
const sendOrderNotifications = async () => {
// Send email
await sendEmail({
channel: 'email',
to: props.order.customerEmail,
template: 'order-confirmation',
data: {
orderNumber: props.order.id,
items: props.order.items,
total: props.order.total,
trackingLink: props.order.trackingUrl
}
});
// Send SMS
if (props.order.customerPhone) {
await sendSMS({
channel: 'sms',
to: props.order.customerPhone,
template: 'order-sms',
data: {
orderNumber: props.order.id,
trackingLink: props.order.trackingUrl
}
});
}
// Send push notification
if (props.order.deviceToken) {
await sendPush({
channel: 'push',
to: props.order.deviceToken,
data: {
title: 'Order Confirmed',
body: `Your order #${props.order.id} has been confirmed`,
badge: 1
}
});
}
};
</script>
<template>
<div class="order-confirmation">
<h2>Order Confirmed!</h2>
<p>Order #{{ order.id }}</p>
<p>Confirmation sent to {{ order.customerEmail }}</p>
</div>
</template>
Password Reset
<script setup lang="ts">
import { ref } from 'vue';
import { useNotification } from '@ductape/vue';
const email = ref('');
const { mutate: sendReset, isLoading, error } = useNotification({
onSuccess: () => {
alert('Password reset link sent! Check your email.');
}
});
const handleSubmit = () => {
// Generate reset token (in real app, do this on server)
const resetToken = Math.random().toString(36).substring(2);
sendReset({
channel: 'email',
to: email.value,
template: 'password-reset',
data: {
resetLink: `https://app.com/reset-password?token=${resetToken}`,
expiresIn: '1 hour'
}
});
};
</script>
<template>
<form @submit.prevent="handleSubmit">
<h2>Forgot Password</h2>
<input
v-model="email"
type="email"
placeholder="Enter your email"
required
/>
<button type="submit" :disabled="isLoading">
{{ isLoading ? 'Sending...' : 'Send Reset Link' }}
</button>
<p v-if="error" class="error">{{ error.message }}</p>
</form>
</template>
Team Invitation
<script setup lang="ts">
import { ref } from 'vue';
import { useNotification } from '@ductape/vue';
import { useSession } from '@ductape/vue';
const email = ref('');
const role = ref('member');
const { session } = useSession();
const { mutate: sendInvite, isLoading } = useNotification({
onSuccess: () => {
alert('Invitation sent!');
email.value = '';
}
});
const handleInvite = () => {
sendInvite({
channel: 'email',
to: email.value,
template: 'team-invitation',
data: {
inviterName: session.value?.user.name,
teamName: session.value?.team.name,
role: role.value,
acceptLink: `https://app.com/invitations/accept?email=${email.value}&role=${role.value}`
}
});
};
</script>
<template>
<form @submit.prevent="handleInvite">
<h3>Invite Team Member</h3>
<input
v-model="email"
type="email"
placeholder="Email address"
required
/>
<select v-model="role">
<option value="member">Member</option>
<option value="admin">Admin</option>
<option value="owner">Owner</option>
</select>
<button type="submit" :disabled="isLoading">
{{ isLoading ? 'Sending...' : 'Send Invitation' }}
</button>
</form>
</template>
Scheduled Notification
<script setup lang="ts">
import { ref } from 'vue';
import { useNotification } from '@ductape/vue';
const email = ref('');
const message = ref('');
const scheduledTime = ref('');
const { mutate, isLoading } = useNotification({
onSuccess: () => {
alert('Reminder scheduled!');
}
});
const handleSchedule = () => {
mutate({
channel: 'email',
to: email.value,
template: 'reminder',
data: {
message: message.value,
scheduledFor: scheduledTime.value
},
scheduledFor: new Date(scheduledTime.value)
});
};
</script>
<template>
<form @submit.prevent="handleSchedule">
<h3>Schedule Reminder</h3>
<input
v-model="email"
type="email"
placeholder="Email"
required
/>
<textarea
v-model="message"
placeholder="Reminder message"
required
/>
<input
v-model="scheduledTime"
type="datetime-local"
required
/>
<button type="submit" :disabled="isLoading">
{{ isLoading ? 'Scheduling...' : 'Schedule Reminder' }}
</button>
</form>
</template>