MailerSend Send ExecModule
Overview
MailersendSendModule submits one asynchronous transactional email to MailerSend's fixed
https://api.mailersend.com/v1/email endpoint. Version 2.0 replaces the legacy batch sender
with a narrow, verifiable contract: one recipient, one explicitly confirmed provider request,
one READY IntegrationAccount, bounded content, and one validated x-message-id response header.
The former version accepted server-environment credentials and caller-controlled endpoints,
silently changed missing credentials into a dry run, counted dry runs as successful delivery,
retried non-idempotent sends, permitted unbounded batches and concurrency, and copied recipient
and provider error details into logs and workflow output. Those behaviors are removed. Existing
workflows that provide mailItems, raw credentials, an endpoint, dryRun, concurrency, retries,
attachments, metadata, variables, tags, scheduled sends, or templates must migrate to the v2
single-message contract or a separately governed module that explicitly supports that feature.
Email submission is an external, non-idempotent side effect. The module never automatically retries an HTTP, timeout, or ambiguous transport failure because MailerSend may have accepted the message before ValkyrAI observed the failure.
Usage
- Verify the sending domain in MailerSend.
- Create a MailerSend API token with email sending permission.
- Store the token only in the encrypted
apiKeyfield of a ValkyrAI IntegrationAccount, set the account status to READY, and bind it throughExecModuleConfig.authConfig.integrationAccount. - Add MailersendSendModule to an approved outbound workflow.
- Supply one
toaddress, one verifiedfromaddress, a subject, andtextorhtml. - Set
confirmSendto true only after the workflow has resolved the recipient, content, and required approval. - Persist
messageIdandsendPausedfor provider-console or Messages API reconciliation.
Inputs
| Name | Type | Required | Description | Local constraints |
|---|---|---|---|---|
to | string | Yes | Single transactional recipient. | Valid email, at most 254 characters. |
toName | string | No | Recipient display name. | At most 128 Unicode code points; no control characters, comma, or semicolon. |
from | string | Yes | Verified MailerSend sender. | Valid email, at most 254 characters. |
fromName | string | No | Sender display name. | At most 128 Unicode code points; no control characters, comma, or semicolon. |
replyTo | string | No | Reply-to address. | Valid email, at most 254 characters. |
subject | string | Yes | Message subject. | Non-blank, at most 998 Unicode code points; no control characters. |
text | string | Conditional | Plain-text body. | text or html is required; at most 2 MB UTF-8. |
html | string | Conditional | HTML body. | html or text is required; at most 2 MB UTF-8. |
confirmSend | boolean | Yes | Explicit approval of the provider side effect. | Must be the JSON boolean true. |
Input-map values override the same normalized payload fields. The complete serialized provider request is limited to 4 MB. A body may contain newlines but not a null character.
The following legacy or unsafe fields are rejected before transport: mailItems, concurrency,
retries, dryRun, mock, test, apiKey, api_key, token, secret, password,
endpoint, url, attachments, metadata, variables, tags, sendAt, and templateId.
Outputs
| Name | Type | When present | Description |
|---|---|---|---|
status | string | Always | success only after verified acceptance; otherwise error. |
messageId | string | Success | One bounded value from the x-message-id response header. |
providerAccepted | boolean | Always | True only for HTTP 202 with one valid message ID. |
sendPaused | boolean | Success | True when MailerSend also returns x-send-paused: true. |
attempts | integer | Always | 0 before transport or 1 after the single provider attempt. |
error | object | Failure | Safe code, message, and retryable:false. |
providerAccepted:true means MailerSend accepted the asynchronous request. It does not prove
final delivery, inbox placement, reading, or link interaction. A paused send is accepted but not
yet dispatched. The module does not return recipient data, message content, the API token, or the
provider response body.
IntegrationAccount Requirements
| Setting | Requirement |
|---|---|
| Provider | MailerSend Email API |
| Status | READY |
| Authentication | Bearer API token with email sending permission |
accountName | Human-readable MailerSend account or verified-domain identity |
apiKey | Encrypted token, 20 to 4,096 non-whitespace characters |
| Relationship | Bind through ExecModuleConfig.authConfig.integrationAccount |
The module does not read MAILERSEND_API_KEY, MAILERSEND_API_ENDPOINT, alternate
IntegrationAccount fields, workflow payload credentials, or caller-supplied URLs. It can contact
only api.mailersend.com in production. Domain verification, suppression lists, unsubscribe
policy, retention, and account compliance remain provider and tenant responsibilities.
Configuration
Illustrative normalized configuration:
{
"version": "2.0.0",
"authConfig": {
"authStrategy": 1,
"integrationAccount": "integration-account:mailersend-transactional"
},
"payloadConfig": {
"parameters": "{\"to\":\"customer@example.com\",\"toName\":\"Customer\",\"from\":\"billing@example.com\",\"fromName\":\"Valkyr Billing\",\"replyTo\":\"support@example.com\",\"subject\":\"Your account is ready\",\"text\":\"Setup is complete.\",\"confirmSend\":true}"
}
}
The account value is a symbolic secure reference. Persisted workflows use the generated IntegrationAccount relationship and never a plaintext token.
Operations
MailersendSendModule supports one operation:
| Provider request | Local behavior | Side effect |
|---|---|---|
POST /v1/email on the fixed MailerSend origin | Construct one bounded transactional message and verify the acceptance headers. | Submits one asynchronous email to one recipient. |
The request uses Authorization: Bearer and Content-Type: application/json. A provider
response is successful only when the status is exactly 202 and x-message-id contains one safe,
bounded identifier. x-send-paused: true is preserved in sendPaused rather than treated as
final dispatch.
Bulk email, multiple recipients, cc/bcc, templates, personalization, attachments, tags, scheduled delivery, tracking settings, custom headers, contact management, delivery webhooks, and suppression management are outside this module's contract.
Errors and Failure Modes
| Failure | Behavior | Recovery |
|---|---|---|
| Validation failure | Returns VALIDATION_ERROR before transport. | Correct the named field and repeat approval. |
| Missing account | Returns INTEGRATION_ACCOUNT_REQUIRED. | Bind the intended MailerSend account. |
| Non-ready account | Returns INTEGRATION_ACCOUNT_NOT_READY. | Repair or reauthorize the account and set it to READY. |
| Invalid account token | Returns VALIDATION_ERROR without sending. | Rotate and store a bounded API token in apiKey. |
| HTTP 401 or 403 | Returns the matching MAILERSEND_HTTP_* code after one attempt. | Rotate the token or repair email permission. |
| HTTP 422 | Returns MAILERSEND_HTTP_422 without provider body data. | Verify sender domain, addresses, and required fields. |
| HTTP 429 | Returns MAILERSEND_HTTP_429 without retry. | Reconcile provider activity and schedule a deliberate later attempt. |
| HTTP 5xx or transport failure | Returns a safe ambiguous-acceptance failure without retry. | Check MailerSend activity before any new send. |
| Oversized response | Returns MAILERSEND_RESPONSE_TOO_LARGE. | Inspect provider compatibility; the body is not retained. |
| HTTP 202 without a safe ID | Returns MAILERSEND_UNVERIFIED_ACCEPTANCE. | Inspect MailerSend activity before retrying. |
Provider bodies can contain recipient data or credential-like strings, so they are never copied into workflow output. Exceptions expose only bounded local guidance and the HTTP code when known.
Example
Submit one approved account notice:
{
"to": "customer@example.com",
"toName": "Customer",
"from": "billing@example.com",
"fromName": "Valkyr Billing",
"replyTo": "support@example.com",
"subject": "Your account is ready",
"text": "Setup is complete.",
"confirmSend": true
}
Expected result after MailerSend returns HTTP 202 and x-message-id:
{
"status": "success",
"messageId": "5e42957d51f1d94a1070a733",
"providerAccepted": true,
"sendPaused": false,
"attempts": 1
}
Notes
- Pagination: not applicable; one execution submits one message.
- Limits: one recipient, one request, 998 subject code points, 2 MB per body representation, and a 4 MB serialized request.
- Rate limits: HTTP 429 is surfaced without automatic replay.
- Idempotency: sending email is non-idempotent.
confirmSendproves explicit workflow intent but is not a durable provider idempotency key. - API constraints: the production origin is fixed. Workflow input cannot select the bulk, template, scheduled, or alternate endpoint.
- Suppression: a 202 response without
x-message-id, including an all-suppressed response, is not claimed as accepted by this module. - Destructive behavior: the module cannot recall or compensate a message after provider acceptance.
- Migration: replace each legacy
mailItemsbatch with one governed task per recipient. Remove environment credentials, endpoints, concurrency, retries, and dry-run assumptions. - Observability: logs record only a validated message ID or failure class. Recipient addresses, subjects, bodies, tokens, and provider error bodies are omitted.
- Unverified boundary: repository tests cover request construction, account requirements, validation, fixed routing, success-header verification, secret redaction, metadata discovery, and the single-attempt rule. Live sending requires separately authorized MailerSend credentials and is not exercised in repository tests.
See MailerSend's official Sending an Email
reference for current request fields, the asynchronous 202 contract, x-message-id, paused sends,
and suppression behavior.