Skip to content

feat(sendgrid): transactional email integration (#18)#76

Open
jackulau wants to merge 2 commits into
wespreadjam:mainfrom
jackulau:18
Open

feat(sendgrid): transactional email integration (#18)#76
jackulau wants to merge 2 commits into
wespreadjam:mainfrom
jackulau:18

Conversation

@jackulau

Copy link
Copy Markdown

Closes #18.

Adds a SendGrid transactional email integration to @jam-nodes/nodes with three new nodes, an API-key credential, full Zod schemas, and a comprehensive Vitest suite. Follows the established Slack integration pattern (packages/nodes/src/integrations/slack/) end-to-end.

What changed

New files

  • packages/nodes/src/integrations/sendgrid/credentials.tsdefineApiKeyCredential with header auth Authorization: Bearer {{apiKey}}.
  • packages/nodes/src/integrations/sendgrid/schemas.ts — input/output Zod schemas for all three operations, plus shared SendgridContentSchema, SendgridAttachmentSchema, SendgridContactSchema.
  • packages/nodes/src/integrations/sendgrid/sendgrid-send-email.tssendgridSendEmailNode (POST /v3/mail/send).
  • packages/nodes/src/integrations/sendgrid/sendgrid-create-contact.tssendgridCreateContactNode (PUT /v3/marketing/contacts, upsert).
  • packages/nodes/src/integrations/sendgrid/sendgrid-get-contacts.tssendgridGetContactsNode (POST /v3/marketing/contacts/search, SGQL).
  • packages/nodes/src/integrations/sendgrid/index.ts — barrel export.
  • packages/nodes/src/integrations/sendgrid/__tests__/sendgrid.test.ts — 54 unit tests.

Modified files

  • packages/core/src/types/node.ts — adds sendgrid?: { apiKey: string } to NodeCredentials.
  • packages/nodes/src/integrations/index.ts — re-exports the new nodes, schemas, types, and credential.

Design notes

  • Pure nodes — every executor is a thin wrapper over fetchWithRetry; retry / cache / timeout cross-cutting concerns stay in the execution engine per the architectural rule in CLAUDE.md (Issue feat: add execution engine with configurable retry, caching, and timeout #37, PR feat: add an execution engine with configurable retry, caching, and timeout #39).
  • Bearer header auth via defineApiKeyCredential, mirroring devto/credentials.ts.
  • x-message-id header is the only signal SendGrid returns from /v3/mail/send (the body is empty on 202); the executor reads it from response headers and falls back to an empty string when absent.
  • PUT /v3/marketing/contacts is SendGrid's upsert endpoint — the executor surfaces the returned job_id as contactJobId so callers can poll job status.
  • POST /v3/marketing/contacts/search is the only contacts endpoint that supports listId filtering. The executor builds an SGQL query with single-quote and backslash escaping to prevent SGQL injection. Results are capped at 50 per call by SendGrid; limit/offset are applied client-side and total reflects contact_count from the response.
  • templateId strips top-level content before sending (SendGrid rejects requests that include both).
  • Zod superRefine rejects dynamicTemplateData without templateId so callers get an immediate validation error instead of a silent drop.
  • subject blocks \\r / \\n as a defense-in-depth guard against header injection.
  • All email fields are trimmed before .email() validation so callers don't get opaque 400s from SendGrid for trailing whitespace.

Verification

npm test --workspace=@jam-nodes/nodes      # 249 / 249 (was 195 baseline, +54 SendGrid)
npm run typecheck --workspace=@jam-nodes/core   # exit 0

The pre-existing typecheck errors in slack-send-message.ts / slack-update-message.ts / apify / google-sheets on `main` are unchanged — fixing them is out of scope for this issue.

Acceptance criteria (from #18)

  • Credential definition
  • All 3 operations
  • Template support
  • Attachment handling
  • Zod schemas
  • Unit tests

Adds @jam-nodes/nodes SendGrid integration with three nodes:
- sendgridSendEmailNode  — POST /v3/mail/send (template + attachment support, x-message-id header)
- sendgridCreateContactNode — PUT /v3/marketing/contacts (upsert; returns job_id)
- sendgridGetContactsNode — POST /v3/marketing/contacts/search (SGQL filter, 50/call cap, client-side limit/offset)

Also extends @jam-nodes/core NodeCredentials with a sendgrid?: { apiKey } entry
and registers the new exports in packages/nodes/src/integrations/index.ts.

48 unit tests cover credential metadata, schema validation, all happy paths,
template/attachment handling, SGQL escaping, error responses, and 401 auth
failures. Full nodes test suite goes from 195 -> 243 with no regressions.

Closes wespreadjam#18
- Add Zod superRefine: dynamicTemplateData requires templateId
- Block CRLF characters in subject (defense-in-depth header injection guard)
- Trim trailing whitespace on all email fields before .email() validation
- Cover attachment disposition/content_id mapping in tests
- Cover SGQL backslash escaping in tests (single quote, backslash, and \\' edge case)

Test count 48 -> 54 (243 -> 249 across the nodes package).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Integration] SendGrid - Transactional email

1 participant