یہ مضمون ابھی آپ کی زبان میں دستیاب نہیں، اس لیے آپ انگریزی اصل پڑھ رہے ہیں۔
Context
Pedibee is a sales workspace: companies, contacts, campaigns, sequences and event-triggered automations, with templates and engagement tracking. I am its lead architect.
Most outreach tools make you borrow their sending, their domain and their AI. Pedibee is built the other way round. Mail goes out from the customer's own Google Workspace account, opens and clicks are tracked on a subdomain they own, and people who prefer their own assistant connect Claude or ChatGPT over MCP and work with the same tools they use in the dashboard.
Constraints
- Someone else's mailbox. Every customer email goes through Gmail on behalf of the workspace. We hold an OAuth grant with the send scope and no inbox read, and Google decides what is allowed.
- Many workspaces, one database. Every customer-owned row carries a workspace id. Services take it as their first argument and controllers read it from the session, never from the request body. A row in another workspace is reported as not found.
- Paths with no session. Tracking pixels, click links, unsubscribe links and queue events arrive without a logged-in person, so they derive the workspace from the record they touch.
- One set of rules for every entry point. The dashboard, the public REST API and the MCP server must behave the same way, so the rules can't live in controllers.
Architecture
The repo is an npm-workspaces monorepo where each app deploys on its own: the Next.js dashboard, the marketing site, the docs, and two server apps.
The core app is one NestJS API on PostgreSQL through Prisma. Its worker is the same app with the event processors switched on: one BullMQ queue per kind of background work (campaigns, sequences, enrichment, engagement, tracking, automations, knowledge), plus recurring jobs. Redis backs the queues, rate limits and the per-Gmail pacer that spaces sends out.
Services own every rule and controllers only read the request and hand over. That is what lets the REST API and the MCP server share a single list of operations, each with a name, a description, the scope it needs and a JSON Schema for its input.
Decisions and alternatives I rejected
Send from the customer's Gmail, not a shared ESP
Every email a customer sends goes out through one path, from a connected company Google Workspace account. We use a transactional provider, Resend, only for Pedibee's own emails to account holders, and a boundary test fails the build if anything else imports it or the Gmail API.
I rejected a shared sending service because it means mail carries the vendor's reputation, not the customer's. The cost is that we live inside Google's limits, so sending is paced: one email at a time inside the audience's time window, with a random gap between emails.
Track on the customer's own subdomain
Opens and clicks are tracked only through each workspace's own tracking domain, never a Pedibee domain. Setup is one CNAME on a subdomain they own; where the DNS provider supports Domain Connect it is one click, and a certificate is issued for them.
With the alternative, a shared tracking domain, every customer's links carry everyone else's reputation. Links are signed and bound to the hostname they were minted for, so a link copied onto another workspace's domain is refused.
A separate app for email verification
Checking whether an address exists needs an SMTP conversation with the recipient's mail server, which needs outbound port 25, and Google Cloud blocks it. So the verifier is its own NestJS app with its own database and queues, sharing no code with core. Core calls it over HTTPS with signed requests and gets results back on a signed webhook.
Putting the checks inside core would have tied the main API to infrastructure that can open port 25. Keeping them apart means core never opens that port.
One operation list for REST and MCP
Rather than write a separate tool layer for assistants, the MCP server registers the same operations the REST API runs. Each connection lists only the tools its scopes and the signed-in person's role allow, and the server tells assistants to confirm deletes and sends first. Pedibee is its own OAuth 2.1 authorization server, so clients like claude.ai can connect without pasting a key.
Trade-offs I accepted
Tracking only on the customer's own domain has a real cost. A workspace that hasn't set one up sends untracked, so a new customer sees no opens or clicks until they add a DNS record. We chose to say so plainly in the product, in the inbox check, sequence steps and Compose, with a link to the setup page, rather than fall back to a shared domain.
Outcome
Pedibee is live at pedibee.com. Customers run campaigns, sequences and product-event automations from their own Gmail, tracked on their own domain, with built-in address checks before a send. A REST API with scoped keys and an MCP server are both documented, and Claude, ChatGPT and Codex can connect to it with approval before sending.