You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Node.js+Express实现类SendGrid的子账户创建与成员邮件邀请逻辑

Got it, let's walk through building this sub-account creation and team invite system with Node.js and Express—mirroring SendGrid's workflow. I'll break this down into actionable steps with code examples you can adapt:

1. System Design Overview

First, let's map the core workflow to align with SendGrid's pattern:

  • A primary account owner creates a sub-account with specific permissions
  • The system generates a time-limited invite token, sends it via email to the team member
  • The recipient clicks the link, sets their password, and activates the sub-account
  • The sub-account can only perform actions matching its assigned permissions

We'll use these key dependencies:

  • express for the server
  • mongoose (or your preferred ORM) for database modeling
  • nodemailer + a transactional email service (like SendGrid itself) for invites
  • jsonwebtoken for secure invite tokens
  • bcryptjs for password hashing
  • dotenv for environment variables
2. Core Implementation Steps

2.1 Set Up Dependencies & Database Models

First, install the required packages:

npm install express mongoose nodemailer jsonwebtoken bcryptjs dotenv

Next, define your database models. We'll need two core models: a primary User account and a SubAccount linked to it.

// models/User.js (Primary Account)
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  subAccounts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'SubAccount' }]
});
module.exports = mongoose.model('User', userSchema);

// models/SubAccount.js
const subAccountSchema = new mongoose.Schema({
  email: { type: String, required: true, unique: true },
  password: { type: String }, // Empty until invite is accepted
  owner: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  permissions: [{ 
    type: String, 
    enum: ['send_emails', 'view_stats', 'manage_templates'], 
    required: true 
  }],
  status: { type: String, enum: ['pending', 'active'], default: 'pending' },
  inviteToken: { type: String }
});
module.exports = mongoose.model('SubAccount', subAccountSchema);

2.2 Create Sub-Account & Send Invite Email

Build an endpoint for primary account owners to create sub-accounts and trigger invite emails. We'll generate a JWT as the invite token (expires in 7 days) and send it via a transactional email service.

// routes/subAccounts.js
const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const nodemailer = require('nodemailer');
const SubAccount = require('../models/SubAccount');
const User = require('../models/User');
require('dotenv').config();

// Configure Nodemailer with SendGrid (replace with your credentials)
const transporter = nodemailer.createTransport({
  service: 'SendGrid',
  auth: {
    user: process.env.SENDGRID_USERNAME,
    pass: process.env.SENDGRID_API_KEY
  }
});

// Create sub-account and send invite
router.post('/create', async (req, res) => {
  try {
    const { ownerId, email, permissions } = req.body;

    // Check if sub-account email already exists
    const existingSubAccount = await SubAccount.findOne({ email });
    if (existingSubAccount) {
      return res.status(400).json({ message: 'This email is already linked to a sub-account' });
    }

    // Generate time-limited invite token
    const inviteToken = jwt.sign(
      { email, ownerId }, 
      process.env.JWT_SECRET, 
      { expiresIn: '7d' }
    );

    // Create pending sub-account record
    const subAccount = new SubAccount({
      email,
      owner: ownerId,
      permissions,
      inviteToken
    });
    await subAccount.save();

    // Link sub-account to primary user
    await User.findByIdAndUpdate(ownerId, { $push: { subAccounts: subAccount._id } });

    // Send invite email
    const mailOptions = {
      from: 'your-app@example.com',
      to: email,
      subject: 'Invitation to join our team',
      html: `
        <p>You've been invited to join a team on our platform!</p>
        <p>Click the link below to set up your account:</p>
        <a href="${process.env.FRONTEND_URL}/accept-invite?token=${inviteToken}">Accept Invitation</a>
        <p>This link expires in 7 days.</p>
      `
    };
    await transporter.sendMail(mailOptions);

    res.status(201).json({ 
      message: 'Sub-account created successfully, invite sent', 
      subAccount: { id: subAccount._id, email: subAccount.email, permissions: subAccount.permissions }
    });
  } catch (err) {
    res.status(500).json({ message: 'Failed to create sub-account', error: err.message });
  }
});

2.3 Handle Invite Acceptance

Build an endpoint to let recipients set their password and activate their sub-account. We'll verify the invite token, hash the password, and update the sub-account status.

// routes/subAccounts.js (add this to the existing file)
const bcrypt = require('bcryptjs');

// Accept invite and set password
router.post('/accept-invite', async (req, res) => {
  try {
    const { token, password } = req.body;

    // Verify invite token
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    const { email, ownerId } = decoded;

    // Find the pending sub-account
    const subAccount = await SubAccount.findOne({
      email,
      owner: ownerId,
      status: 'pending',
      inviteToken: token
    });

    if (!subAccount) {
      return res.status(400).json({ message: 'Invalid or expired invite token' });
    }

    // Hash the new password
    const hashedPassword = await bcrypt.hash(password, 10);

    // Activate the sub-account
    subAccount.password = hashedPassword;
    subAccount.status = 'active';
    subAccount.inviteToken = undefined; // Clear token after use
    await subAccount.save();

    res.status(200).json({ message: 'Invite accepted! Your account is now active.' });
  } catch (err) {
    if (err.name === 'TokenExpiredError') {
      return res.status(400).json({ message: 'Invite token has expired' });
    }
    res.status(500).json({ message: 'Failed to accept invite', error: err.message });
  }
});

2.4 Permission Middleware

Add middleware to enforce permission checks for sub-account actions, ensuring they only access allowed features.

// middleware/permissionMiddleware.js
const SubAccount = require('../models/SubAccount');

const hasPermission = (requiredPermission) => {
  return async (req, res, next) => {
    try {
      // Assuming sub-account ID is attached to req.user after authentication
      const subAccount = await SubAccount.findById(req.user.id);
      if (!subAccount.permissions.includes(requiredPermission)) {
        return res.status(403).json({ message: 'Insufficient permissions' });
      }
      next();
    } catch (err) {
      res.status(500).json({ message: 'Failed to check permissions', error: err.message });
    }
  };
};

module.exports = hasPermission;

Use the middleware in your feature routes:

// routes/email.js
const express = require('express');
const router = express.Router();
const hasPermission = require('../middleware/permissionMiddleware');

router.post('/send', hasPermission('send_emails'), async (req, res) => {
  // Logic to send emails (e.g., using SendGrid API)
  res.status(200).json({ message: 'Email sent successfully' });
});
3. Key Production Considerations
  • Security: Use HTTPS, store JWT secrets and API keys in environment variables, sanitize all inputs to prevent NoSQL injection, and use bcrypt with a high salt round (12+).
  • Email Deliverability: Stick to transactional email services (SendGrid, Mailgun) instead of plain SMTP to avoid landing in spam folders.
  • Authentication: Implement JWT or session-based auth for both primary and sub-accounts (sub-accounts should have their own auth tokens).
  • Audit Logs: Track sub-account actions (e.g., emails sent, changes made) for accountability.
  • Error Handling: Add granular error messages for common issues (e.g., duplicate invites, expired tokens) to improve user experience.

内容的提问来源于stack exchange,提问作者Vishal Trivedi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 15:02:44