emailr_
All articles
usecase·10 min

B2B email: Enterprise notification patterns

b2benterprisepatterns

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

  1. Role-based delivery - Send to the right people based on their responsibilities
  2. Clear severity levels - Make urgency obvious in subject lines
  3. Actionable content - Include direct links to resolve issues
  4. Audit everything - Log all notifications for compliance
  5. Escalation paths - Ensure critical alerts reach someone
  6. 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.

e_

Written by the emailr team

Building email infrastructure for developers

Ready to start sending?

Get your API key and send your first email in under 5 minutes. No credit card required.