@molecule/api-emails-sendgrid
Provider bond · email · API (Node) · v1.0.3 · Apache-2.0
SendGrid email provider for molecule.dev.
npm install @molecule/api-emails-sendgridnpm · Source on GitHub · Implements @molecule/api-emails
How it works
@molecule/api-emails-sendgrid is a provider bond on the API (Node) side: it implements the email core interface (@molecule/api-emails) with a concrete vendor or library behind it.
Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.
import { setTransport } from '@molecule/api-emails'
import { provider } from '@molecule/api-emails-sendgrid'
setTransport(provider)Works with: @molecule/api-bond, @molecule/api-emails, @molecule/api-secrets
Secrets: SENDGRID_API_KEY
Reference
SendGrid email provider for molecule.dev.
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
import { setTransport } from '@molecule/api-emails'
import { provider } from '@molecule/api-emails-sendgrid'
setTransport(provider)
Type
provider
Installation
npm install @molecule/api-emails-sendgrid @molecule/api-analytics @molecule/api-bond @molecule/api-emails @molecule/api-secrets @sendgrid/mail
API
Interfaces
EmailMessage
Email message options.
interface EmailMessage {
/**
* Sender address.
*/
from: string | EmailAddress
/**
* Recipient(s).
*/
to: string | EmailAddress | (string | EmailAddress)[]
/**
* CC recipient(s).
*/
cc?: string | EmailAddress | (string | EmailAddress)[]
/**
* BCC recipient(s).
*/
bcc?: string | EmailAddress | (string | EmailAddress)[]
/**
* Reply-to address.
*/
replyTo?: string | EmailAddress
/**
* Email subject.
*/
subject: string
/**
* Plain text body.
*/
text?: string
/**
* HTML body.
*/
html?: string
/**
* File attachments.
*/
attachments?: EmailAttachment[]
/**
* i18n key for the subject (for client-side translation).
*/
subjectKey?: string
/**
* i18n key for the plain text body (for client-side translation).
*/
textKey?: string
/**
* i18n key for the HTML body (for client-side translation).
*/
htmlKey?: string
}
EmailSendResult
Result of sending an email.
interface EmailSendResult {
/**
* Whether the email was accepted for delivery.
*/
accepted: string[]
/**
* Addresses that were rejected.
*/
rejected: string[]
/**
* Message ID from the provider.
*/
messageId?: string
/**
* Raw response from the provider.
*/
response?: string
}
EmailTransport
Email transport interface.
All email providers must implement this interface.
interface EmailTransport {
/**
* Sends an email message.
* @returns The send result.
*/
sendMail(message: EmailMessage): Promise<EmailSendResult>
}
Functions
sendMail(message)
Sends an email through the SendGrid API.
function sendMail(message: EmailMessage): Promise<EmailSendResult>
message— The email message (to, from, subject, text/html, attachments).
Returns: Send result with accepted addresses, message ID, and status code.
getClient()
Returns the SendGrid mail client, applying configuration from the environment on FIRST USE and memoizing each setting thereafter.
Configuration is deferred to the first send — NOT module load — so an app that
resolves SENDGRID_API_KEY (and the optional SENDGRID_BASE_URL) into
process.env AFTER this module is imported (late secrets resolution via a
secrets bond) is honored: the value present at send time is the one applied.
Reading the key at import time instead froze an empty/stale key and every
request went out unauthenticated — an opaque SendGrid 401. Each env var is
applied once, the first time it is seen set, so whichever arrives late is
still picked up.
function getClient(): sgMail.MailService
Returns: The configured @sendgrid/mail client.
Constants
provider
The SendGrid email provider implementing the standard interface.
const provider: EmailTransport
emailsSendgridSecretDefinitions
Secret definitions required by the SendGrid email bond.
const emailsSendgridSecretDefinitions: SecretDefinition[]
Core Interface
Implements @molecule/api-emails interface.
Bond Wiring
Setup function to register this provider with the core interface:
import { setTransport } from '@molecule/api-emails'
import { provider } from '@molecule/api-emails-sendgrid'
export function setupEmailsSendgrid(): void {
setTransport(provider)
}
Injection Notes
Requirements
Peer dependencies:
@molecule/api-bond^1.0.1@molecule/api-emails^1.0.1@molecule/api-secrets^1.0.1
Environment Variables
SENDGRID_API_KEY(required) — SendGrid API key- Setup: SendGrid → Settings → API Keys → Create API Key with Mail Send permission.
- Get it here: https://app.sendgrid.com/settings/api_keys
- Example:
SG....
Runtime Dependencies
-
@molecule/api-analytics -
@molecule/api-bond -
@molecule/api-emails -
@molecule/api-secrets -
@sendgrid/mail -
Configuration is lazy and env-driven:
SENDGRID_API_KEY(and the optionalSENDGRID_BASE_URL) are read on the FIRST send viagetClient()— NOT at import time — and applied once. So a key resolved intoprocess.envAFTER this module is imported (late secrets resolution via a secrets bond) is honored: the value present at send time is the one used. If the key is genuinely absent at send time,sendMail()throws a tagged config-missing error (clean 503 /config.notConfigured) namingSENDGRID_API_KEY— never an opaque SendGrid 401. -
SENDGRID_TEST_MODE=trueenables SendGrid sandbox mode: the API validates and accepts the message (auth + payload exercised for real) but NOTHING is delivered.SENDGRID_BASE_URL(optional, read lazily on first send too) overrides the API base URL for brokers/compatible endpoints. -
Stream attachments are not supported — Buffer/string content only; a stream throws. On success
acceptedechoes everytorecipient (SendGrid returns no per-recipient verdict) andmessageIdis taken from thex-message-idresponse header.
E2E Tests
Integration checklist — drive the real UI (live preview, no mocks). The
sandbox CAPTURES outbound email instead of sending — read each message with
the read_activity tool (filter type 'email'); the verification/reset link
is in its payload. Never mock the send or modify production code to expose
it. Adapt each item to this app's actual screens/flows, and check every box
off one by one. A box you can't check is an integration bug to fix — not a
skip:
- Each email-triggering flow (signup verification, password-reset request, invites/notifications the app defines) confirms the send in the UI ("check your inbox") and a message actually reaches the transport.
- The password-reset round-trip completes: request a reset → open the captured message → follow its single-use link → set a new password → log in with it (and the old password no longer works).
- The message body contains a LINK, never the raw token/secret, and renders
with the app's real name/content (no
undefinedplaceholders). - Requesting a reset for an unknown email shows the same neutral UI response as a known one (no account-existence oracle).
- Account emails go only to the account's own address — no UI or endpoint lets an unauthenticated caller send to an arbitrary address.