@plantagoai/messaging
Transactional email via Resend, push notifications via Firebase Cloud Messaging (FCM), and in-app notifications backed by Firestore + RTDB.
Consumers: All active projects
Installation
"@plantagoai/messaging": "file:../../shared/packages/messaging"
Peer Dependencies
| Dependency | Required For |
|---|---|
resend |
Email sending |
firebase-admin |
Push notifications, in-app notifications |
Email (Resend)
Initialize
import { createMailer } from "@plantagoai/messaging";
const mailer = createMailer(
process.env.RESEND_API_KEY,
"Foundation <noreply@foundation.plantagoai.com>" // default from address
);
Send Email
import { sendEmail } from "@plantagoai/messaging";
const result = await sendEmail({
to: "user@example.com",
subject: "Your proposal has been approved",
html: "<h1>Congratulations!</h1><p>Your proposal passed with 85% support.</p>",
text: "Congratulations! Your proposal passed with 85% support.", // optional fallback
from: "Foundation <noreply@foundation.plantagoai.com>", // overrides default
replyTo: "support@plantagoai.com", // optional
tags: [{ name: "category", value: "governance" }], // optional
});
if (result.success) {
console.log("Email sent:", result.id);
} else {
console.error("Failed:", result.error);
}
EmailOptions
| Field | Type | Required | Description |
|---|---|---|---|
to |
string | string[] |
Yes | Recipient(s) |
subject |
string |
Yes | Email subject |
html |
string |
No | HTML body |
text |
string |
No | Plain text body |
from |
string |
No | From address (overrides default) |
replyTo |
string |
No | Reply-to address |
tags |
Array<{name, value}> |
No | Tracking tags |
Push Notifications (FCM)
Send to Device
import { sendPush } from "@plantagoai/messaging";
const messageId = await sendPush("device-token-abc...", {
title: "New Proposal",
body: "A new governance proposal needs your vote",
data: { proposalId: "p-123", screen: "voting" }, // custom data payload
imageUrl: "https://example.com/icon.png", // optional
clickAction: "OPEN_PROPOSAL", // optional
});
Send to Topic
import { sendPushToTopic } from "@plantagoai/messaging";
const messageId = await sendPushToTopic("governance-updates", {
title: "Voting Round Started",
body: "The voting round for 'Community Fund Allocation' is now open",
data: { proposalId: "p-456" },
});
PushOptions
| Field | Type | Required | Description |
|---|---|---|---|
title |
string |
Yes | Notification title |
body |
string |
Yes | Notification body |
data |
Record<string, string> |
No | Custom data payload |
imageUrl |
string |
No | Image URL |
clickAction |
string |
No | Action on click |
In-App Notifications
Stored in Firestore (notifications collection) with optional RTDB real-time presence.
Create Notification
import { createNotification } from "@plantagoai/messaging";
const notifId = await createNotification({
userId: "user-123",
tenantId: "org-1", // optional
type: "proposal_passed",
title: "Proposal Approved",
body: "Your proposal 'Community Garden' has been approved by the council",
data: { proposalId: "p-789" }, // optional metadata
});
Mark as Read
import { markRead } from "@plantagoai/messaging";
await markRead("notif-abc123");
Get Unread Count
import { getUnreadCount } from "@plantagoai/messaging";
const count = await getUnreadCount("user-123", "org-1");
// e.g. 5
List Notifications
import { listNotifications } from "@plantagoai/messaging";
const notifications = await listNotifications("user-123", {
tenantId: "org-1", // optional
limit: 20, // default: 50
unreadOnly: true, // default: false
});
// [
// {
// id: "notif-abc",
// userId: "user-123",
// tenantId: "org-1",
// type: "proposal_passed",
// title: "Proposal Approved",
// body: "Your proposal...",
// read: false,
// data: { proposalId: "p-789" },
// createdAt: Date
// },
// ...
// ]
AppNotification
interface AppNotification {
id: string;
userId: string;
tenantId?: string;
type: string;
title: string;
body: string;
read: boolean;
data?: Record<string, unknown>;
createdAt: Date;
}
Integration Example: Foundation
Foundation's sendNotification Cloud Function uses the messaging package:
import { createNotification, sendPush, sendEmail } from "@plantagoai/messaging";
// Multi-channel notification
await createNotification({ userId, type: "vote_confirmed", title: "Vote Recorded", body: "..." });
await sendPush(deviceToken, { title: "Vote Recorded", body: "..." });
await sendEmail({ to: userEmail, subject: "Vote Confirmation", html: "..." });