Skip to main content

SendGrid Email ExecModule

Overview

SendGridEmailModule submits one email through Twilio SendGrid's Mail Send v3 endpoint. It supports plain-text and HTML bodies, dynamic templates, to/cc/bcc recipients, reply-to addressing, and bounded base64 attachments.

Version 2.0 replaces the catalog's empty input/output metadata and weak delivery boundary. The module now requires a READY SendGrid IntegrationAccount, an explicit verified sender, bounded and unique recipients, strict content/template rules, safe attachment basenames and MIME types, a 10 MB serialized request ceiling, a 10-second connection timeout, and a 30-second read timeout. A send is accepted only when SendGrid returns HTTP 202 and a valid X-Message-Id header.

Email submission is an external, non-idempotent side effect. The module never retries an HTTP, timeout, or ambiguous network failure automatically because SendGrid may have accepted the email before the workflow observed the failure.

Usage

  1. Create a restricted SendGrid API key with Mail Send permission.
  2. Verify the sender identity or authenticated domain used by from in SendGrid.
  3. Store the API key only in the encrypted apiKey field of a ValkyrAI IntegrationAccount, set the account to READY, and bind it through ExecModuleConfig.authConfig.integrationAccount.
  4. Add SendGridEmailModule to a workflow and supply at least one to address plus the verified from address.
  5. For a content send, provide subject and at least one of plain_text or html.
  6. For a dynamic-template send, provide template_id and optional dynamic_data; do not also provide plain_text or html.
  7. Require outbound approval where policy demands it and retain the returned message ID for provider reconciliation.

Inputs

NameTypeRequiredDescriptionLocal constraints
toarray of stringsYesPrimary recipients.Non-empty valid email array.
ccarray of stringsNoCarbon-copy recipients.Valid email array when present.
bccarray of stringsNoBlind-carbon-copy recipients.Valid email array when present.
fromstringYesVerified sender email.Valid email, at most 254 characters.
reply_tostringNoReply-to email.Valid email, at most 254 characters.
subjectstringConditionalSubject for content sends.Required without template_id; at most 998 Unicode code points.
template_idstringNoDynamic template ID.d- followed by exactly 32 hexadecimal characters.
dynamic_dataobjectNoDynamic-template variables.Requires template_id; at most 100,000 serialized UTF-8 bytes.
plain_textstringConditionalPlain-text content.At least one content body is required without template_id; at most 1,000,000 code points.
htmlstringConditionalHTML content.At least one content body is required without template_id; at most 1,000,000 code points.
attachmentsarray of objectsNoBase64 file attachments.At most five files, each at most 5 MB decoded, with a safe basename and optional bounded MIME type.

The combined to, cc, and bcc count is limited to 100. Addresses must be unique across all three fields using case-insensitive comparison. The complete serialized provider request is limited to 10 MB. mock is rejected and cannot fabricate delivery.

Each attachment has this shape:

{
"filename": "receipt.pdf",
"content_base64": "JVBERi0xLjQK...",
"type": "application/pdf"
}

filename must be a basename without /, \, or control characters. type defaults to application/octet-stream.

Outputs

NameTypeWhen presentDescription
sendgrid.message.idstringSuccessSafe X-Message-Id returned with SendGrid's 202 response.
sendgrid.message.recipientCountintegerSuccessTotal unique to, cc, and bcc recipients submitted.
sendgrid.message.statusstringAlwaysACCEPTED after verified provider acceptance or ERROR on failure.

ACCEPTED means SendGrid accepted the request for processing; it does not prove final delivery. The runtime ExecModule is also marked GOOD or ERROR. Recipient addresses, message bodies, attachment content, API keys, and provider response bodies are not written to error messages or WorkflowState.

IntegrationAccount Requirements

SettingRequirement
ProviderTwilio SendGrid Mail Send API v3
AuthenticationRestricted API key with Mail Send permission
StatusREADY
accountNameHuman-readable SendGrid account or sending domain identity
apiKeyEncrypted API key beginning with SG.
RelationshipBind through ExecModuleConfig.authConfig.integrationAccount

Never place the API key in payload fields, headers stored by a workflow, URLs, logs, examples, or documentation. Sender verification, authenticated-domain policy, unsubscribe requirements, suppression lists, and account-level compliance remain provider and tenant responsibilities.

Configuration

Illustrative normalized configuration:

{
"version": "2.0.0",
"authConfig": {
"authStrategy": 1,
"integrationAccount": "integration-account:sendgrid-transactional"
},
"payloadConfig": {
"parameters": "{\"to\":[\"customer@example.com\"],\"from\":\"billing@example.com\",\"subject\":\"Order A-1042\",\"plain_text\":\"Your order is ready.\"}"
}
}

The account value is a symbolic secure reference. Persisted workflows use the generated IntegrationAccount relationship, never a plaintext API key.

Operations

SendGridEmailModule performs one POST /v3/mail/send operation with two supported shapes:

ShapeRequired provider fieldsSide effect
Content emailRecipients, verified from, subject, and plain_text, html, or bothSubmits one email with explicit body content.
Dynamic-template emailRecipients, verified from, template_id, and optional dynamic_dataSubmits one email rendered by a SendGrid dynamic template.

One execution creates one SendGrid personalization containing all configured recipients. Bulk campaign orchestration, contact lists, marketing campaigns, template management, sender verification, delivery-event ingestion, suppression management, scheduled delivery, and provider-side cancellation are separate capabilities and are not implemented here.

Errors and Failure Modes

FailureCauseRetry guidance
Validation failureMissing or malformed recipients/sender, duplicate addresses, missing content, conflicting template content, invalid template ID, unsafe attachment, or size overflow.Correct the configuration; no SendGrid request was sent.
IntegrationAccount failureNo bound account, non-READY status, blank key, or key without the SG. prefix.Bind or repair the SendGrid account; no request was sent.
HTTP 400SendGrid rejects recipient, sender, content, template, or attachment data.Correct the request and verify sender/template state before a new attempt.
HTTP 401/403Invalid key, missing Mail Send permission, or account restriction.Rotate or reauthorize the IntegrationAccount.
HTTP 429Provider rate limit.Reconcile provider activity, honor rate-limit guidance, and schedule a deliberate new attempt.
HTTP 5xx or network failureProvider or transport failure with an ambiguous acceptance boundary.Do not retry blindly; inspect SendGrid Email Activity first.
Invalid success responseStatus is not 202, or X-Message-Id is missing, malformed, or contains unsafe characters.Treat as failure and inspect provider compatibility; success is not recorded.

Provider response bodies can contain recipient data or credential-like details. HTTP failures therefore expose only the status code and non-retry warning. The original provider exception is not retained as the emitted error cause.

Example

Submit one dynamic-template order confirmation:

{
"to": ["customer@example.com"],
"from": "billing@example.com",
"reply_to": "support@example.com",
"template_id": "d-0123456789abcdef0123456789abcdef",
"dynamic_data": {
"orderNumber": "A-1042",
"status": "ready"
}
}

Expected result after SendGrid returns a verified acceptance response:

{
"sendgrid.message.id": "provider-message-id",
"sendgrid.message.recipientCount": 1,
"sendgrid.message.status": "ACCEPTED"
}

Notes

  • Pagination: not applicable; one execution submits one Mail Send request.
  • Limits: local limits are intentionally stricter and deterministic. SendGrid applies final account, plan, recipient, attachment, message-size, template, and content rules.
  • Rate limits: HTTP 429 is surfaced without retry. The module does not persist rate-limit headers.
  • Idempotency: Mail Send is non-idempotent. A workflow retry can produce duplicate email, and WorkflowState is not treated as durable provider deduplication across ambiguous failures.
  • Delivery semantics: HTTP 202 plus X-Message-Id proves provider acceptance only. Final delivery, bounce, block, deferral, spam-report, unsubscribe, and open/click events require SendGrid Event Webhook or Email Activity reconciliation.
  • Attachments: content must already be base64 encoded. The module validates base64 and local decoded/file/payload limits but does not fetch remote files.
  • API constraints: dynamic-template sends reject plain_text and html so content is not silently ignored. dynamic_data is rejected without template_id.
  • Destructive behavior: submission is an external outbound write. This module cannot recall or compensate an accepted email.
  • Observability: EventLogs record validation, recipient count, verified acceptance ID, or sanitized failure. Addresses, content, attachment bytes, API keys, and provider bodies are excluded from failures.
  • Unverified boundary: deterministic tests cover IntegrationAccount binding, exact request construction, recipient/content/attachment limits, template mapping, provider-response verification, non-retry behavior, metadata serialization, and secret redaction. Live SendGrid execution requires separately authorized credentials and is not exercised in repository tests.

See Twilio SendGrid's official Mail Send API reference and Dynamic Templates documentation for current provider behavior.