Profiles·Public

@sendgrid/mail

semver>=7.0.0 <9.0.0postconditions7functions3last verified2026-06-23

Postconditions: what we check

  • send · send-no-try-catch
    error
    Whensend() called without try-catch or .catch() handler
    ThrowsError with error.response.body containing API error details
    Required 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 unavailablevisibilityvisible
    Sources[1]
  • send · send-no-error-response-check
    warning
    Whensend() in try-catch but catch does not check error.response
    Required 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 unavailablevisibilityvisible
    Sources[1]
  • send · send-array-batch-no-partial-failure-handling
    warning
    Whensend() called with a MailDataRequired[] array argument and the caller treats the resolved promise as all-or-nothing (no per-item .catch / Promise.allSettled)
    ThrowsFirst per-item rejection bubbles up and aborts the Promise.all, leaving sibling sends in an indeterminate state from the caller's perspective
    Required 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
    Sources[2][3][4]
  • send · send-validation-error-no-response-shape
    warning
    Whensend() 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 unconditionally
    ThrowsError('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
    Sources[2][5][1]
  • sendMultiple · send-multiple-no-try-catch
    error
    WhensendMultiple() called without try-catch or .catch() handler
    ThrowsError with error.response.body containing API error details
    Required 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 unavailablevisibilityvisible
    Sources[6]
  • sendMultiple · send-multiple-callback-with-unhandled-promise
    warning
    WhensendMultiple() or send() invoked with a callback argument but the returned promise is not awaited and has no .catch handler
    ThrowsUnhandledPromiseRejection on the returned promise in addition to the callback being invoked with the error — Node 15+ default behavior crashes the process
    Required 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
    Sources[2][7][1]
  • setApiKey · api-key-not-validated
    warning
    WhensetApiKey() called without trimming or validating API key
    Required handlingSHOULD trim whitespace from API key using .trim() and validate key exists before setting: process.env.SENDGRID_API_KEY?.trim() || throw error
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[8]

Sources

Every postcondition cites at least one of these. Grouped by source type; numbered to match the footnotes above.

Official documentation
  • [3]
    developer.mozilla.org/en-US/docs/Web
    All
  • [7]
    nodejs.org/api/process.html
    Process
Source code
Issues & pull requests

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

Troubleshooting Guide

Rate Limiting Documentation

npm Package Page


GitHub Issues & Community

Rate Limiting with sendMultiple()

Error Code Handling Discussion

Sendgrid Errors List


CVE & Security Analysis

@sendgrid/mail Package Security

CVE-2021-34629 (WordPress Plugin Only)


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

  1. Valid email send (should succeed)
  2. Invalid API key (should throw 401)
  3. Invalid email address (should throw 400)
  4. Rate limit exceeded (should throw 429)
  5. Network timeout (should throw network error)
  6. 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:

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