@sendgrid/mail
semver
>=7.0.0 <9.0.0postconditions7functions3last verified2026-06-23Postconditions: what we check
- send · send-no-try-catcherrorWhensend() called without try-catch or .catch() handlerThrows
Error with error.response.body containing API error detailsRequired handlingMUST wrap await sgMail.send() in try-catch block, or use .catch() to handle promise rejection. Catch block should check error.response.body for detailed API error information.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - send · send-no-error-response-checkwarningWhensend() in try-catch but catch does not check error.responseRequired handlingSHOULD check for error.response existence and log error.response.body for detailed API error information. This helps distinguish between network errors (no response) and API errors (response with error details).costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1]
- send · send-array-batch-no-partial-failure-handlingwarningWhensend() called with a MailDataRequired[] array argument and the caller treats the resolved promise as all-or-nothing (no per-item .catch / Promise.allSettled)Throws
First per-item rejection bubbles up and aborts the Promise.all, leaving sibling sends in an indeterminate state from the caller's perspectiveRequired handlingMUST use one of: (a) iterate `for (const msg of messages) { try { await sgMail.send(msg) } catch (e) { ... } }` to get per-item visibility, (b) wrap the per-item sends in `Promise.allSettled(messages.map(m => sgMail.send(m)))` and inspect each result/reason, or (c) use sgMail.sendMultiple() with a single MailDataRequired whose `personalizations` array contains the recipient list — sendMultiple is for the one-message-many-recipients shape, NOT for many-independent-messages.costmediumin prodsilent failureusers seelost transactionvisibilitysilent - send · send-validation-error-no-response-shapewarningWhensend() rejects with a bare Error (no error.response) due to malformed MailDataRequired or a triggered secret rule, but the catch block dereferences error.response.body unconditionallyThrows
Error('Expecting object for Mail data') / Error('Provide at least one of to, cc or bcc') / Error('Expected each `content` entry to contain a `value` string') / Error("The pattern '...' was found in the Mail content!") / etc. — sync throws from Mail.create() and filterSecrets() are caught at mail-service.js:210 and re-emitted as Promise.reject(error)Required handlingMUST guard error.response access with optional chaining or an explicit existence check: `if (error.response) { console.error(error.response.body); } else { console.error(error.message); }`. The SendGrid documentation pattern shows this guard explicitly — it is not optional. Treat any rejection without error.response as a validation/configuration failure (caller's fault, not API's).costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - sendMultiple · send-multiple-no-try-catcherrorWhensendMultiple() called without try-catch or .catch() handlerThrows
Error with error.response.body containing API error detailsRequired handlingMUST wrap await sgMail.sendMultiple() in try-catch block. Should implement rate limiting protection and retry logic for 429 errors. Check error.response.body for detailed error information.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[6] - sendMultiple · send-multiple-callback-with-unhandled-promisewarningWhensendMultiple() or send() invoked with a callback argument but the returned promise is not awaited and has no .catch handlerThrows
UnhandledPromiseRejection on the returned promise in addition to the callback being invoked with the error — Node 15+ default behavior crashes the processRequired handlingMUST do one of: (a) use only the Promise pattern with `await sgMail.send(data)` in a try-catch, (b) use only the Promise pattern with `sgMail.send(data).catch(handler)`, or (c) if a callback is required for legacy reasons, also catch the returned promise: `sgMail.send(data, cb).catch(() => {})` — empty .catch is acceptable when the callback already handles the error, because it converts the unhandled-rejection into a no-op.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - setApiKey · api-key-not-validatedwarningWhensetApiKey() called without trimming or validating API keyRequired handlingSHOULD trim whitespace from API key using .trim() and validate key exists before setting: process.env.SENDGRID_API_KEY?.trim() || throw errorcostmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[8]
Sources
Every postcondition cites at least one of these. Grouped by source type; numbered to match the footnotes above.
Official documentation
Source code
- [1]github.com/sendgrid/sendgrid-nodejs/blobsendgrid/sendgrid-nodejs · success-failure-errors.md
- [2]github.com/sendgrid/sendgrid-nodejs/blobsendgrid/sendgrid-nodejs · mail-service.js
- [4]github.com/sendgrid/sendgrid-nodejs/blobsendgrid/sendgrid-nodejs · sending-multiple-emails-to-multiple-recipients.md
- [5]github.com/sendgrid/sendgrid-nodejs/blobsendgrid/sendgrid-nodejs · mail.js
- [8]github.com/sendgrid/sendgrid-nodejs/blobsendgrid/sendgrid-nodejs · TROUBLESHOOTING.md
Issues & pull requests
- [6]github.com/sendgrid/sendgrid-nodejs/issuessendgrid/sendgrid-nodejs issue #1081
Research notes
Curator notes from SOURCES.md captured when the profile was written so you can verify the reasoning, not just the rules.
Sources: @sendgrid/mail Nark profile
Package: @sendgrid/mail Version: ^7.0.0 || ^8.0.0 Research Date: 2026-02-24
Official Documentation
Error Handling Guide
- URL: https://github.com/sendgrid/sendgrid-nodejs/blob/main/docs/use-cases/success-failure-errors.md
- Key Points:
- Promise-based error handling with
.catch() - Callback-based error handling with
(error, result) => {} - Error structure:
error.message,error.code,error.response.body - Recommends extracting error details before logging
- Promise-based error handling with
Troubleshooting Guide
- URL: https://github.com/sendgrid/sendgrid-nodejs/blob/main/TROUBLESHOOTING.md
- Key Points:
- API key configuration issues (missing, hardcoded, whitespace)
- Kubernetes deployment: use
.trim()on API keys - Request validation and debugging techniques
- Webhook verification patterns
Rate Limiting Documentation
- URL: https://www.twilio.com/docs/sendgrid/api-reference/how-to-use-the-sendgrid-v3-api/rate-limits
- Key Points:
- 429 Too Many Requests when rate limit exceeded
- X-RateLimit-Reset header indicates retry timing
- Rate limits vary by account type
- Implement exponential backoff for retries
npm Package Page
- URL: https://www.npmjs.com/package/@sendgrid/mail
- Key Points:
- Official SendGrid Node.js library
- Supports send() and sendMultiple() methods
- Promise-based API
- Requires API key configuration
GitHub Issues & Community
Rate Limiting with sendMultiple()
- URL: https://github.com/sendgrid/sendgrid-nodejs/issues/1081
- Issue: API rate restrictions when sending multiple emails
- Key Finding: sendMultiple() calls v3/mail/send endpoint multiple times in parallel
- Implication: High risk of hitting rate limits with bulk sending
Error Code Handling Discussion
- URL: https://github.com/sendgrid/sendgrid-nodejs/issues/997
- Issue: How to properly handle error codes
- Key Finding: Error response structure contains statusCode, body, headers
- Best Practice: Check error.response existence before accessing properties
Sendgrid Errors List
- URL: https://github.com/sendgrid/sendgrid-nodejs/issues/851
- Issue: Request for comprehensive error list
- Key Finding: Errors include 401 (auth), 429 (rate limit), 400 (validation)
- Implication: Need to handle multiple error types differently
CVE & Security Analysis
@sendgrid/mail Package Security
- URL: https://security.snyk.io/package/npm/%40sendgrid%2Fmail
- Finding: No direct vulnerabilities found in @sendgrid/mail
- Note: Dependencies should still be monitored
CVE-2021-34629 (WordPress Plugin Only)
- URL: https://www.cvedetails.com/cve/CVE-2021-34629/
- Affected: SendGrid WordPress plugin through version 1.11.8
- Not Affected: Node.js @sendgrid/mail package
- Implication: Node.js library is not affected by this CVE
Error Patterns & Best Practices
Common API Errors
401 Unauthorized
- Cause: Invalid or missing API key
- Solution: Validate API key exists and is correctly configured
- Prevention: Trim whitespace, check for empty values
429 Too Many Requests
- Cause: Rate limit exceeded
- Solution: Implement retry with exponential backoff
- Prevention: Use queuing systems (Bull, AWS SQS) for bulk sends
- Headers: Check X-RateLimit-Reset for retry timing
400 Bad Request
- Cause: Invalid email addresses, missing required fields, malformed data
- Solution: Validate email format, ensure from/to/subject exist
- Prevention: Schema validation before sending
Network Errors
- Cause: Connection timeout, DNS failures
- Solution: Implement retry logic with timeout configuration
- Prevention: Set reasonable timeout values
Real-World Implementation Patterns
Recommended Pattern (Async/Await)
import sgMail from '@sendgrid/mail';
sgMail.setApiKey(process.env.SENDGRID_API_KEY?.trim() || '');
async function sendEmail() {
try {
await sgMail.send({
to: 'user@example.com',
from: 'noreply@company.com',
subject: 'Test Email',
text: 'Hello World'
});
console.log('Email sent successfully');
} catch (error) {
// Check for API error response
if (error.response) {
console.error('SendGrid API Error:', error.response.body);
// Handle specific error codes
if (error.response.statusCode === 429) {
// Rate limit - implement retry
} else if (error.response.statusCode === 401) {
// Invalid API key
}
} else {
// Network error
console.error('Network Error:', error.message);
}
throw error;
}
}
Recommended Pattern (Promise-Based)
sgMail.send(msg)
.then(() => {
console.log('Email sent');
})
.catch(error => {
if (error.response) {
console.error(error.response.body);
}
console.error(error);
});
Testing Strategy
Test Scenarios
- Valid email send (should succeed)
- Invalid API key (should throw 401)
- Invalid email address (should throw 400)
- Rate limit exceeded (should throw 429)
- Network timeout (should throw network error)
- Missing required fields (should throw 400)
Fixture Coverage
- proper-error-handling.ts: Demonstrates correct try-catch with error.response check
- missing-error-handling.ts: No try-catch (should trigger violations)
- generic-catch.ts: Try-catch without error.response check (should warn)
- rate-limit-handling.ts: Demonstrates 429 error handling
References
Articles & Tutorials:
- SendGrid Node.js Integration Guide
- How to Send Emails with SendGrid in Node.js
- LogRocket: Send Emails with Node.js Using SendGrid
Additional Documentation:
Research Notes
Completed: 2026-02-24 Researcher: Claude Sonnet 4.5 Quality: High - Official documentation and GitHub issues reviewed Coverage: Comprehensive - All major error scenarios identified
Need a different package?
Request a profile