Trading Platform Notification System: From Database to Email Push
Trading Platform Notification System: From Database to Email Push
Every transaction on ClawDUX triggers notifications — in-app and via email. Here's the architecture behind it.
The Notification Service Pattern
// services/notification.ts
import { PrismaClient } from '@prisma/client';
import { sendEmail } from './email';
const prisma = new PrismaClient();
type NotificationType =
| 'WELCOME'
| 'DEPOSIT'
| 'ORDER_CONFIRMED'
| 'ORDER_DISPUTED'
| 'OFFER_RECEIVED'
| 'OFFER_ACCEPTED'
| 'ARBITRATION_RESULT';
interface NotifyParams {
userId: string;
type: NotificationType;
title: string;
message: string;
metadata?: Record<string, any>;
}
export async function notify(params: NotifyParams): Promise<void> {
const { userId, type, title, message, metadata } = params;
// 1. Create in-app notification (always)
await prisma.notification.create({
data: {
userId,
type,
title,
message,
metadata: metadata ? JSON.stringify(metadata) : null,
},
});
// 2. Send email (if user has email)
const user = await prisma.user.findUnique({
where: { id: userId },
select: { email: true, username: true },
});
if (user?.email) {
await sendEmail({
to: user.email,
subject: title,
template: type,
data: {
username: user.username || 'Trader',
message,
...metadata,
},
}).catch((err) => {
// Fire-and-forget: don't fail the main operation
console.error('Email send failed:', err);
});
}
}
Usage in Business Logic
// In GraphQL resolvers
async confirmOrder(_, { orderId }, context) {
const order = await prisma.order.update({
where: { id: orderId },
data: { status: 'CONFIRMED' },
include: { listing: true, buyer: true },
});
// Notify buyer
notify({
userId: order.buyerId,
type: 'ORDER_CONFIRMED',
title: 'Purchase Confirmed',
message: `Your purchase of "${order.listing.title}" is confirmed.`,
metadata: {
orderId: order.id,
amount: order.amount.toString(),
listingTitle: order.listing.title,
},
}).catch(() => {});
// ^^^^ Fire-and-forget — notification failure
// should never block the main flow
// Notify seller
notify({
userId: order.listing.sellerId,
type: 'ORDER_CONFIRMED',
title: 'Sale Confirmed',
message: `Your strategy "${order.listing.title}" sale is confirmed.`,
metadata: { orderId: order.id },
}).catch(() => {});
return order;
}
Frontend: The Notification Bell
function NotificationBell() {
const [unread, setUnread] = useState(0);
const [notifications, setNotifications] = useState([]);
const [open, setOpen] = useState(false);
// Poll for unread count
useEffect(() => {
const poll = setInterval(async () => {
const count = await api.query(UNREAD_COUNT);
setUnread(count);
}, 10000);
return () => clearInterval(poll);
}, []);
return (
<div className="relative">
<button onClick={() => setOpen(!open)}>
<span className="material-symbols-outlined">
notifications
</span>
{unread > 0 && (
<span className="absolute -top-1 -right-1 w-5 h-5
bg-red-500 text-white text-xs rounded-full
flex items-center justify-center">
{unread > 9 ? '9+' : unread}
</span>
)}
</button>
{open && (
<div className="absolute right-0 mt-2 w-80
bg-surface-card border border-surface-border
rounded-xl shadow-xl overflow-hidden">
{notifications.map((n) => (
<div
key={n.id}
className={`p-3 border-b border-surface-border
${!n.isRead ? 'bg-blue-600/5' : ''}`}
>
<p className="text-sm text-gray-300">{n.title}</p>
<p className="text-xs text-gray-500 mt-1">
{n.message}
</p>
</div>
))}
</div>
)}
</div>
);
}
Design Decisions
| Decision | Why |
|---|---|
| Fire-and-forget | Notification failure should never block transactions |
| Database-first | Notifications persist even if email fails |
| Polling (not WebSocket) | Simpler, good enough for 10s latency |
| JSON metadata | Flexible — each notification type carries different data |
This notification architecture handles thousands of events daily on ClawDUX, keeping both buyers and sellers informed throughout the escrow lifecycle.
The core logic discussed in this article has been integrated into the ClawDUX API. Access ClawDUX-core for full permissions, or browse the marketplace to discover verified trading strategies.