B2B email differs fundamentally from consumer email. Enterprise customers expect reliability, detailed information, and integration with their workflows. Here's how to design B2B notification systems.
B2B email characteristics
Enterprise email must handle:
- —Multiple recipients per organization
- —Role-based notifications
- —Audit and compliance requirements
- —Integration with enterprise tools
- —Higher deliverability expectations
Organization-level notifications
Team alerts
interface TeamAlert {
organization: {
id: string;
name: string;
};
alert: {
type: 'security' | 'billing' | 'usage' | 'system';
severity: 'info' | 'warning' | 'critical';
title: string;
details: string;
};
recipients: TeamMember[];
}
async function sendTeamAlert(alert: TeamAlert) {
// Filter recipients by role and preferences
const recipients = alert.recipients.filter(member =>
shouldReceiveAlert(member, alert.alert.type, alert.alert.severity)
);
for (const recipient of recipients) {
await sendEmail({
to: recipient.email,
subject: `[${alert.alert.severity.toUpperCase()}] ${alert.alert.title}`,
template: 'team-alert',
data: {
organization: alert.organization,
alert: alert.alert,
recipient,
actionUrl: getAlertActionUrl(alert)
},
headers: {
'X-Priority': alert.alert.severity === 'critical' ? '1' : '3'
}
});
}
}
Admin notifications
interface AdminNotification {
type: 'user_added' | 'user_removed' | 'role_changed' | 'settings_changed';
actor: { name: string; email: string };
target?: { name: string; email: string };
details: Record<string, any>;
timestamp: Date;
}
// Notify all admins of security-relevant changes
async function notifyAdmins(org: Organization, notification: AdminNotification) {
const admins = await getOrgAdmins(org.id);
for (const admin of admins) {
await sendEmail({
to: admin.email,
subject: `[${org.name}] ${formatNotificationType(notification.type)}`,
template: 'admin-notification',
data: {
org,
notification,
auditLogUrl: `${baseUrl}/admin/audit-log`
}
});
}
}
Role-based notifications
Permission-aware delivery
interface RoleBasedEmail {
organization: Organization;
emailType: string;
data: Record<string, any>;
}
async function sendRoleBasedEmail(config: RoleBasedEmail) {
const roleConfig = {
'billing.invoice': ['owner', 'billing_admin'],
'security.alert': ['owner', 'security_admin', 'admin'],
'usage.warning': ['owner', 'admin'],
'user.joined': ['owner', 'admin', 'hr_admin'],
'feature.announcement': ['owner', 'admin', 'user']
};
const allowedRoles = roleConfig[config.emailType] || ['owner'];
const recipients = await getOrgMembersByRoles(config.organization.id, allowedRoles);
for (const recipient of recipients) {
await sendEmail({
to: recipient.email,
template: config.emailType.replace('.', '-'),
data: {
...config.data,
recipient,
organization: config.organization
}
});
}
}
Escalation patterns
interface EscalationConfig {
initialRecipients: string[]; // roles
escalateAfter: number; // minutes
escalateTo: string[]; // roles
maxEscalations: number;
}
async function sendWithEscalation(
org: Organization,
alert: Alert,
config: EscalationConfig
) {
// Send initial notification
await sendToRoles(org, alert, config.initialRecipients);
// Schedule escalation check
await scheduleJob({
type: 'check_escalation',
runAt: addMinutes(new Date(), config.escalateAfter),
data: {
alertId: alert.id,
orgId: org.id,
escalationLevel: 1,
config
}
});
}
async function checkEscalation(job: EscalationJob) {
const alert = await getAlert(job.data.alertId);
if (alert.acknowledged) return;
if (job.data.escalationLevel < job.data.config.maxEscalations) {
// Escalate to next level
await sendToRoles(
await getOrg(job.data.orgId),
alert,
job.data.config.escalateTo
);
// Schedule next escalation
await scheduleJob({
...job,
runAt: addMinutes(new Date(), job.data.config.escalateAfter),
data: {
...job.data,
escalationLevel: job.data.escalationLevel + 1
}
});
}
}
Enterprise billing emails
Invoice notifications
interface InvoiceEmail {
organization: Organization;
invoice: {
id: string;
number: string;
amount: number;
currency: string;
dueDate: Date;
lineItems: LineItem[];
pdfUrl: string;
};
}
await sendEmail({
to: billingContact.email,
subject: `Invoice #${invoice.number} for ${org.name}`,
template: 'invoice',
data: {
organization: org,
invoice,
paymentUrl: `${baseUrl}/billing/pay/${invoice.id}`,
supportEmail: '[email protected]'
},
attachments: [
{
filename: `invoice-${invoice.number}.pdf`,
path: invoice.pdfUrl
}
]
});
Usage alerts
const usageAlertThresholds = [
{ percent: 75, template: 'usage-warning-75' },
{ percent: 90, template: 'usage-warning-90' },
{ percent: 100, template: 'usage-limit-reached' }
];
async function checkUsageAlerts(org: Organization) {
const usage = await getOrgUsage(org.id);
const limit = org.plan.limits;
const percent = (usage.current / limit.max) * 100;
for (const threshold of usageAlertThresholds) {
if (percent >= threshold.percent && !await hasAlertedThreshold(org.id, threshold.percent)) {
await sendRoleBasedEmail({
organization: org,
emailType: 'usage.warning',
data: {
currentUsage: usage.current,
limit: limit.max,
percent,
upgradeUrl: `${baseUrl}/billing/upgrade`
}
});
await markThresholdAlerted(org.id, threshold.percent);
}
}
}
Compliance and audit
Audit trail emails
// Send audit summary to compliance team
async function sendAuditDigest(org: Organization) {
const events = await getAuditEvents(org.id, {
since: subDays(new Date(), 7),
types: ['security', 'access', 'data']
});
const complianceContacts = await getComplianceContacts(org.id);
for (const contact of complianceContacts) {
await sendEmail({
to: contact.email,
subject: `Weekly audit digest for ${org.name}`,
template: 'audit-digest',
data: {
organization: org,
period: 'Last 7 days',
summary: {
totalEvents: events.length,
byType: groupBy(events, 'type'),
flaggedEvents: events.filter(e => e.flagged)
},
fullReportUrl: `${baseUrl}/admin/audit-log`
}
});
}
}
Data export notifications
// GDPR data export ready
await sendEmail({
to: requester.email,
subject: `Data export ready for ${org.name}`,
template: 'data-export-ready',
data: {
organization: org,
export: {
requestedAt: exportRequest.createdAt,
expiresAt: addDays(new Date(), 7),
downloadUrl: exportRequest.downloadUrl,
format: 'JSON'
},
securityNote: 'This link expires in 7 days. Download contains sensitive data.'
}
});
Integration notifications
Webhook failure alerts
await sendEmail({
to: techContact.email,
subject: `[Action Required] Webhook failures for ${org.name}`,
template: 'webhook-failures',
data: {
organization: org,
webhook: {
url: webhook.url,
failureCount: webhook.consecutiveFailures,
lastError: webhook.lastError,
lastAttempt: webhook.lastAttemptAt
},
action: webhook.consecutiveFailures >= 10
? 'Webhook has been disabled'
: 'Will retry automatically',
settingsUrl: `${baseUrl}/settings/webhooks`
}
});
Enterprise notification preferences
interface EnterpriseNotificationPrefs {
// Organization-level defaults
orgDefaults: {
securityAlerts: 'all_admins' | 'security_team' | 'owner_only';
billingNotifications: 'billing_contact' | 'all_admins';
usageAlerts: boolean;
weeklyDigest: boolean;
};
// Per-user overrides
userOverrides: {
[userId: string]: {
emailEnabled: boolean;
digestFrequency: 'daily' | 'weekly' | 'never';
alertTypes: string[];
};
};
}
Best practices
- —Role-based delivery - Send to the right people based on their responsibilities
- —Clear severity levels - Make urgency obvious in subject lines
- —Actionable content - Include direct links to resolve issues
- —Audit everything - Log all notifications for compliance
- —Escalation paths - Ensure critical alerts reach someone
- —Respect preferences - Allow granular notification control
B2B email is about reliability and trust. Your notifications should help organizations run smoothly while maintaining security and compliance standards.