@happyvertical/smrt-messages
Multi-channel messaging with STI hierarchies for Email, Slack, and Twitter. Credential encryption via smrt-secrets.
Overview
smrt-messages provides unified multi-channel messaging with STI-based channel
hierarchies. Messages and accounts each use single-table inheritance, so Email, SlackMessage,
and Tweet all share a single messages table, while EmailAccount, SlackAccount, and
TwitterAccount share a single accounts table.
Installation
npm install @happyvertical/smrt-messages
# or
pnpm add @happyvertical/smrt-messagesDepends on @happyvertical/smrt-core, @happyvertical/smrt-secrets (credential encryption),
and @happyvertical/email (SMTP/IMAP client).
Quick Start (5 Minutes)
1. Create an Email Account with Encrypted Credentials
import {
Email, EmailCollection,
EmailAccount, EmailAccountCollection,
EmailSender, MessageCollection,
} from '@happyvertical/smrt-messages';
const accounts = new EmailAccountCollection(db);
const account = await accounts.create({
name: 'Support',
providerType: 'smtp',
});
// Credentials stored via smrt-secrets envelope encryption
await account.setCredentials({
host: 'smtp.example.com',
user: 'support@example.com',
pass: process.env.SMTP_PASSWORD,
});2. Send an Email
const emails = new EmailCollection(db);
const email = await emails.create({
accountId: account.id,
subject: 'Welcome',
toAddresses: JSON.stringify([{ address: 'user@example.com' }]),
textBody: 'Thanks for signing up!',
});
// Send lifecycle: draft -> sending -> sent (or failed)
const result = await email.send();
// Retry a failed message (respects maxRetries budget)
if (!result.success) {
await email.retrySend();
}3. Query Messages Across Channels
// Query all messages (Email, Slack, Twitter) via base collection
const messages = new MessageCollection(db);
const recent = await messages.list({
orderBy: 'createdAt DESC',
limit: 20,
});Core Concepts
STI Message Hierarchy
All message types share a single messages table via STI (_meta_type discriminator):
| Type | STI Discriminator | Key Fields |
|---|---|---|
Message | (base class) | accountId, threadId, subject, body, fromAddress, toAddresses, sendStatus, retryCount |
Email | @happyvertical/smrt-messages:Email | messageId (RFC 822), inReplyTo, ccAddresses, bccAddresses, htmlBody, textBody, folderId, labels, headers |
Tweet | @happyvertical/smrt-messages:Tweet | tweetId, retweetCount, likeCount, mediaUrls, hashtags, mentions |
SlackMessage | @happyvertical/smrt-messages:SlackMessage | channelId, slackTs, slackThreadTs, reactions, blocks |
STI Account Hierarchy
Accounts also use STI, sharing the accounts table:
| Type | Key Fields |
|---|---|
Account | providerType, credentialSecretId, isActive, lastSyncAt, settings |
EmailAccount | SMTP/IMAP provider-specific fields + sync methods |
SlackAccount | Slack workspace connection |
TwitterAccount | Twitter API connection |
Credential Security
Account credentials are stored via credentialSecretId pointing to
smrt-secrets envelope encryption. Never store passwords as plain fields.
// Always use setCredentials/getCredentials
await account.setCredentials({
host: 'smtp.example.com',
user: 'support@example.com',
pass: process.env.SMTP_PASSWORD,
});
const creds = await account.getCredentials();Send Lifecycle
message.send() resolves the account, creates a provider-specific sender,
and transitions through send statuses:
// Status flow: draft -> sending -> sent (or failed)
const result = await email.send();
// Each channel has a dedicated sender
// EmailSender, SlackSender, TweetSender
// All implement MessageSenderInterfaceEmail Filtering (Whitelist/Blacklist)
New in v0.20.42: Whitelist and Blacklist models for address-based email filtering.
import {
Whitelist, WhitelistCollection,
Blacklist, BlacklistCollection,
} from '@happyvertical/smrt-messages';
// Create whitelist/blacklist entries
const whitelist = new WhitelistCollection(db);
await whitelist.create({ address: 'trusted@example.com' });
const blacklist = new BlacklistCollection(db);
await blacklist.create({ address: 'spam@example.com' });Per-Channel Senders
Each channel has a dedicated sender implementing MessageSenderInterface:
| Sender | Description |
|---|---|
EmailSender | Send emails via @happyvertical/email client |
SlackSender | Send Slack messages via API |
TweetSender | Post tweets via Twitter API |
Svelte 5 Components
The package includes UI components for managing email accounts and filters:
- EmailAccountManager: Admin UI for managing email account connections and credentials
- EmailFilterManager: Admin UI for managing whitelist/blacklist rules
- MessageCard, MessageList: Display message summaries
- ComposeForm, ReplyForm, ForwardForm: Message composition
- ThreadView, MessageDetail: Conversation display
- FolderNav, MessageFilters: Navigation and filtering
- AccountCard, AccountList, AccountAvatar: Account management
- AttachmentChip, AttachmentUpload: Attachment handling
- SendStatusBadge, MessageStatusIndicator, MessageTypeBadge: Status display
- MessageToolbar, RecipientInput: Toolbar and input components
import {
EmailAccountManager,
EmailFilterManager,
MessageCard,
MessageList,
} from '@happyvertical/smrt-messages/svelte';API Reference
Collections
// Base collections (query across all channels)
MessageCollection
AccountCollection
AttachmentCollection
// Email-specific collections
EmailCollection
EmailAccountCollection
EmailAttachmentCollection
EmailFolderCollection
// Email filtering
WhitelistCollection
BlacklistCollectionBest Practices
DOs
- Use
setCredentials()/getCredentials()for all account credentials - Use JSON address helpers:
getToAddresses(),getCcAddresses() - Handle send failures with retry logic (
maxRetriesbudget) - Use
MessageCollectionto query across all channel types - Store attachment references via
messageIdfield
DON'Ts
- Don't store passwords directly -- always use smrt-secrets encryption
- Don't modify JSON fields directly -- use accessor methods (
getX()/setX()) - Don't override
toJSON()-- usetransformJSON() - Don't use
emailIdon Attachment -- usemessageId(old field is deprecated) - Don't run concurrent syncs on the same account