Mailtrap Send ExecModule
Overview
MailtrapSendModule submits one transactional email to Mailtrap's fixed https://send.api.mailtrap.io/api/send 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 Mailtrap message ID.
The former version ignored its declared IntegrationAccount, accepted credentials and an arbitrary endpoint from server environment, silently switched to a dry run when credentials were missing, counted dry runs as successful deliveries, retried non-idempotent sends three times, allowed unbounded batches and concurrency, and exposed recipient/provider details in logs and failures. Those behaviors are removed. Existing workflows that provide mailItems, dryRun, concurrency, retries, raw credentials, or an endpoint must migrate to the v2 single-message shape.
Mail submission is an external, non-idempotent side effect. The module never automatically retries an HTTP, timeout, or ambiguous transport failure because Mailtrap may have accepted the email before the workflow observed the failure.
Usage
- Verify the sending domain in Mailtrap.
- Create a Mailtrap API token with transactional email sending permission.
- Store the token only in the encrypted apiKey field of a ValkyrAI IntegrationAccount, set the account status to READY, and bind it through ExecModuleConfig.authConfig.integrationAccount.
- Add MailtrapSendModule to the approved outbound workflow.
- Supply one to address, one verified from address, subject, and at least one of text or html.
- Set confirmSend to true only after the workflow has resolved its recipient, content, and required approval.
- Persist the returned messageId for delivery-event or provider-console reconciliation.
Inputs
| Name | Type | Required | Description | Local constraints |
|---|---|---|---|---|
| to | string | Yes | Single transactional recipient. | Valid email, at most 254 characters. |
| from | string | Yes | Verified Mailtrap sender. | Valid email, at most 254 characters. |
| fromName | string | No | Sender display name. | At most 128 Unicode code points; no control characters. |
| 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 1,000,000 code points. |
| html | string | Conditional | HTML body. | html or text is required; at most 1,000,000 code points. |
| 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 2 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 or api_key
- token, secret, or password
- endpoint or url
Outputs
| Name | Type | When present | Description |
|---|---|---|---|
| status | string | Always | success only after verified acceptance; otherwise error. |
| messageId | string | Success | One bounded provider message ID from message_ids. |
| providerAccepted | boolean | Always | true only for HTTP 200, success:true, and exactly one valid ID. |
| 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 Mailtrap accepted the request for processing. It does not prove final delivery, inbox placement, reading, or link interaction. The module does not return the recipient, message content, API token, or provider response body.
IntegrationAccount Requirements
| Setting | Requirement |
|---|---|
| Provider | Mailtrap Email API transactional stream |
| Status | READY |
| Authentication | Bearer API token with sending permission |
| accountName | Human-readable Mailtrap account or verified-domain identity |
| apiKey | Encrypted Mailtrap API token, 20 to 4,096 non-whitespace characters |
| Relationship | Bind through ExecModuleConfig.authConfig.integrationAccount |
The module does not read MAILTRAP_SECRET_KEY, MAILTRAP_API_ENDPOINT, workflow payload credentials, or caller-supplied URLs. It can contact only send.api.mailtrap.io in production. Sender verification, domain DNS, suppression lists, unsubscribe policy, retention, and account-level compliance remain provider and tenant responsibilities.
Configuration
Illustrative normalized configuration:
{
"version": "2.0.0",
"authConfig": {
"authStrategy": 1,
"integrationAccount": "integration-account:mailtrap-transactional"
},
"payloadConfig": {
"parameters": "{\"to\":\"customer@example.com\",\"from\":\"billing@example.com\",\"fromName\":\"Valkyr Billing\",\"replyTo\":\"support@example.com\",\"subject\":\"Your download is ready\",\"text\":\"Your secure link is ready.\",\"confirmSend\":true}"
}
}
The account value is a symbolic secure reference. Persisted workflows use the generated IntegrationAccount relationship and never a plaintext token.
Operations
MailtrapSendModule supports one operation:
| Provider request | Local behavior | Side effect |
|---|---|---|
| POST https://send.api.mailtrap.io/api/send | Construct one bounded transactional message and verify the acceptance envelope. | Submits one email to one recipient. |
The request uses Authorization: Bearer and Content-Type: application/json. A provider response is successful only when all of these conditions hold:
- HTTP status is exactly 200.
- The JSON body contains success:true.
- message_ids is an array with exactly one string.
- The ID contains only the module's bounded safe characters.
Bulk stream, provider batch sending, marketing campaigns, templates, attachments, scheduled delivery, contact management, sandbox inboxes, 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 Mailtrap 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 400 | Returns MAILTRAP_HTTP_400 after one attempt. | Correct addresses, sender, body, or account-domain state. |
| HTTP 401 or 403 | Returns the matching HTTP code after one attempt. | Rotate the token or repair sending permission/domain verification. |
| HTTP 429 | Returns MAILTRAP_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 Mailtrap logs/activity before any new send. |
| Oversized response | Returns MAILTRAP_RESPONSE_TOO_LARGE. | Inspect provider compatibility; the body is not retained. |
| HTTP 200 without verified envelope | Returns MAILTRAP_UNVERIFIED_ACCEPTANCE. | Treat as failure and inspect provider/API compatibility. |
Provider failure bodies can contain recipient data or credential-like strings, so they are not copied into workflow output. Exceptions expose only bounded local guidance and the HTTP code when known.
Example
Submit one approved fulfillment notice:
{
"to": "customer@example.com",
"from": "billing@example.com",
"fromName": "Valkyr Billing",
"replyTo": "support@example.com",
"subject": "Your download is ready",
"text": "Your secure link is ready.",
"confirmSend": true
}
Expected result after Mailtrap returns a verified acceptance envelope:
{
"status": "success",
"messageId": "0c7fd939-02cf-11ed-88c2-0a58a9feac02",
"providerAccepted": true,
"attempts": 1
}
Notes
- Pagination: not applicable; one execution submits one message.
- Limits: one recipient, one request, 998 subject code points, 1,000,000 code points per body representation, and a 2 MB serialized request.
- Rate limits: HTTP 429 is surfaced without automatic replay.
- Idempotency: sending email is non-idempotent. confirmSend proves explicit workflow intent but is not a durable provider idempotency key.
- API constraints: the production origin is fixed. Workflow input cannot select the transactional, bulk, batch, or sandbox endpoint.
- Destructive behavior: the module cannot recall or compensate a message after provider acceptance.
- Migration: replace each legacy mailItems batch 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-envelope verification, secret redaction, metadata discovery, and the single-attempt rule. Live sending requires separately authorized Mailtrap credentials and is not exercised in repository tests.
See Mailtrap's official Email API overview and transactional sending reference for current provider behavior.