Skip to content

Commit 999af4d

Browse files
committed
feat: add smtp action (#20) - index, helpers, functions, registration
1 parent 5731244 commit 999af4d

7 files changed

Lines changed: 514 additions & 0 deletions

File tree

.gitlab-ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ include:
2525
- woocommerce-action
2626
- shopware-action
2727
- twilio-action
28+
- smtp-action
2829

2930
test-node:
3031
image: node:24.10.0

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ The "central" place for all actions — a monorepo containing integrations provi
77
| Action | Description |
88
|--------|-------------|
99
| [GLS](docs/Actions/GLS/overview.md) | GLS ShipIT integration for creating and managing shipments |
10+
| SMTP | Send emails (with attachments) through any SMTP server via nodemailer |
1011

1112
## ENV
1213

actions/smtp-action/README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# SMTP Action
2+
3+
Send emails through any SMTP server from a Hercules flow. Built on
4+
[nodemailer](https://nodemailer.com/), the de-facto standard SMTP client for
5+
Node.js.
6+
7+
This action is function-only (like the GLS and Twilio actions) — it exposes
8+
functions to send mail and build attachments, and does not register any
9+
triggers.
10+
11+
## Configuration
12+
13+
Configured via the action's `ConfigurationDefinition`s:
14+
15+
| Identifier | Type | Required | Description |
16+
|---|---|---|---|
17+
| `host` | TEXT | yes | Hostname of the SMTP server, e.g. `smtp.example.com`. |
18+
| `port` | TEXT | no (default `587`) | SMTP port. Common values: `587` (STARTTLS), `465` (implicit TLS). |
19+
| `secure` | TEXT | no (default `false`) | `"true"` to use implicit TLS (port 465), `"false"` for STARTTLS (port 587). |
20+
| `username` | TEXT | no | Username for SMTP AUTH. Leave empty for unauthenticated relays. |
21+
| `password` | TEXT | no | Password for SMTP AUTH. |
22+
| `from_address` | TEXT | no | Default `From` used when a function call omits it, e.g. `Acme <no-reply@example.com>`. |
23+
24+
Beyond the shared Hercules variables (`HERCULES_AUTH_TOKEN`,
25+
`HERCULES_AQUILA_URL`, `HERCULES_ACTION_ID`, `HERCULES_SDK_VERSION`), no
26+
provider-specific environment variables are required — SMTP credentials are
27+
supplied through the configuration above.
28+
29+
## Functions
30+
31+
| Identifier | Signature | Description |
32+
|---|---|---|
33+
| `sendEmail` | `(To, Subject, Text, Html?, From?, Cc?, Bcc?, ReplyTo?): SMTP_SEND_RESULT` | Send a plain-text (and optionally HTML) email. `To`/`Cc`/`Bcc` are comma-separated address lists. |
34+
| `sendEmailWithAttachments` | `(To, Subject, Text, Attachments, Html?, From?, Cc?, Bcc?): SMTP_SEND_RESULT` | Send an email with one or more attachments built via `createAttachment`. |
35+
| `createAttachment` | `(Filename, Content, ContentType?, Encoding?): SMTP_ATTACHMENT` | Build an attachment object. Use `base64` encoding for binary files. |
36+
37+
## Data types
38+
39+
- `SMTP_SEND_RESULT` — normalized nodemailer `SentMessageInfo`: `messageId`,
40+
`accepted`, `rejected`, `response`, `envelope`.
41+
- `SMTP_ENVELOPE` — the SMTP envelope (`from`, `to`) used for delivery.
42+
- `SMTP_ATTACHMENT``filename`, `content`, optional `contentType` and
43+
`encoding`.
44+
45+
## Provider setup
46+
47+
Most providers (Gmail, Microsoft 365, Amazon SES, Postmark, Mailgun, etc.)
48+
expose SMTP credentials. For Gmail/Workspace you typically need an
49+
[App Password](https://support.google.com/accounts/answer/185833) rather than
50+
your account password. Point `host`/`port` at the provider's SMTP endpoint and
51+
set `secure` to match the port (465 → `true`, 587 → `false`).
52+
53+
## Development
54+
55+
```bash
56+
cd actions/smtp-action
57+
npm install
58+
npm run typecheck
59+
npm run build
60+
npm run test
61+
```
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import {
2+
Description,
3+
DisplayIcon,
4+
DisplayMessage,
5+
Documentation,
6+
FunctionContext,
7+
Identifier,
8+
Name,
9+
Parameter,
10+
RuntimeError,
11+
Signature,
12+
} from "@code0-tech/hercules";
13+
import { sendEmail } from "../helpers.js";
14+
import { SmtpSendResult } from "../data_types/smtpSendResult.js";
15+
16+
@Identifier("sendEmail")
17+
@DisplayIcon("tabler:mail")
18+
@Signature("(To: string, Subject: string, Text: string, Html?: string, From?: string, Cc?: string, Bcc?: string, ReplyTo?: string): SMTP_SEND_RESULT")
19+
@Name({ code: "en-US", content: "Send email" })
20+
@DisplayMessage({ code: "en-US", content: "Send email to ${To}" })
21+
@Documentation({
22+
code: "en-US",
23+
content:
24+
"Sends an email through the configured SMTP server using nodemailer.\nProvide `Html` to send a rich body alongside the plain text, and `From` to override the action's default sender. `To`, `Cc`, and `Bcc` accept comma-separated address lists.",
25+
})
26+
@Description({
27+
code: "en-US",
28+
content: "Sends an email through the configured SMTP server.",
29+
})
30+
@Parameter({
31+
runtimeName: "To",
32+
name: [{ code: "en-US", content: "To" }],
33+
description: [{ code: "en-US", content: "Comma-separated list of recipient email addresses." }],
34+
})
35+
@Parameter({
36+
runtimeName: "Subject",
37+
name: [{ code: "en-US", content: "Subject" }],
38+
description: [{ code: "en-US", content: "The subject line of the email." }],
39+
})
40+
@Parameter({
41+
runtimeName: "Text",
42+
name: [{ code: "en-US", content: "Text body" }],
43+
description: [{ code: "en-US", content: "The plain-text body of the email." }],
44+
})
45+
@Parameter({
46+
runtimeName: "Html",
47+
name: [{ code: "en-US", content: "HTML body" }],
48+
description: [{ code: "en-US", content: "Optional HTML body. When set it is sent alongside the plain-text body." }],
49+
optional: true,
50+
})
51+
@Parameter({
52+
runtimeName: "From",
53+
name: [{ code: "en-US", content: "From" }],
54+
description: [{ code: "en-US", content: "The sender address. Falls back to the configured default sender when omitted." }],
55+
optional: true,
56+
})
57+
@Parameter({
58+
runtimeName: "Cc",
59+
name: [{ code: "en-US", content: "CC" }],
60+
description: [{ code: "en-US", content: "Comma-separated list of CC recipients." }],
61+
optional: true,
62+
})
63+
@Parameter({
64+
runtimeName: "Bcc",
65+
name: [{ code: "en-US", content: "BCC" }],
66+
description: [{ code: "en-US", content: "Comma-separated list of BCC recipients." }],
67+
optional: true,
68+
})
69+
@Parameter({
70+
runtimeName: "ReplyTo",
71+
name: [{ code: "en-US", content: "Reply-To" }],
72+
description: [{ code: "en-US", content: "The Reply-To address for the email." }],
73+
optional: true,
74+
})
75+
export class SendEmailFunction {
76+
async run(
77+
context: FunctionContext,
78+
To: string,
79+
Subject: string,
80+
Text: string,
81+
Html?: string,
82+
From?: string,
83+
Cc?: string,
84+
Bcc?: string,
85+
ReplyTo?: string
86+
): Promise<SmtpSendResult> {
87+
try {
88+
return await sendEmail({ To, Subject, Text, Html, From, Cc, Bcc, ReplyTo }, context);
89+
} catch (error) {
90+
if (error instanceof RuntimeError) {
91+
throw error;
92+
}
93+
if (error instanceof Error) {
94+
throw new RuntimeError("ERROR_SENDING_EMAIL", error.message);
95+
}
96+
throw new RuntimeError("ERROR_SENDING_EMAIL", "An error occurred while sending the email.");
97+
}
98+
}
99+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import {
2+
Description,
3+
DisplayIcon,
4+
DisplayMessage,
5+
Documentation,
6+
FunctionContext,
7+
Identifier,
8+
Name,
9+
Parameter,
10+
RuntimeError,
11+
Signature,
12+
} from "@code0-tech/hercules";
13+
import { sendEmail } from "../helpers.js";
14+
import { SmtpSendResult } from "../data_types/smtpSendResult.js";
15+
import { SmtpAttachment } from "../data_types/smtpAttachment.js";
16+
17+
@Identifier("sendEmailWithAttachments")
18+
@DisplayIcon("tabler:mail")
19+
@Signature("(To: string, Subject: string, Text: string, Attachments: SMTP_ATTACHMENT, Html?: string, From?: string, Cc?: string, Bcc?: string): SMTP_SEND_RESULT")
20+
@Name({ code: "en-US", content: "Send email with attachments" })
21+
@DisplayMessage({ code: "en-US", content: "Send email with attachments to ${To}" })
22+
@Documentation({
23+
code: "en-US",
24+
content:
25+
"Sends an email with one or more attachments through the configured SMTP server.\nBuild each attachment with the `createAttachment` function. `To`, `Cc`, and `Bcc` accept comma-separated address lists.",
26+
})
27+
@Description({
28+
code: "en-US",
29+
content: "Sends an email with attachments through the configured SMTP server.",
30+
})
31+
@Parameter({
32+
runtimeName: "To",
33+
name: [{ code: "en-US", content: "To" }],
34+
description: [{ code: "en-US", content: "Comma-separated list of recipient email addresses." }],
35+
})
36+
@Parameter({
37+
runtimeName: "Subject",
38+
name: [{ code: "en-US", content: "Subject" }],
39+
description: [{ code: "en-US", content: "The subject line of the email." }],
40+
})
41+
@Parameter({
42+
runtimeName: "Text",
43+
name: [{ code: "en-US", content: "Text body" }],
44+
description: [{ code: "en-US", content: "The plain-text body of the email." }],
45+
})
46+
@Parameter({
47+
runtimeName: "Attachments",
48+
name: [{ code: "en-US", content: "Attachments" }],
49+
description: [{ code: "en-US", content: "One or more attachments built with the createAttachment function." }],
50+
})
51+
@Parameter({
52+
runtimeName: "Html",
53+
name: [{ code: "en-US", content: "HTML body" }],
54+
description: [{ code: "en-US", content: "Optional HTML body. When set it is sent alongside the plain-text body." }],
55+
optional: true,
56+
})
57+
@Parameter({
58+
runtimeName: "From",
59+
name: [{ code: "en-US", content: "From" }],
60+
description: [{ code: "en-US", content: "The sender address. Falls back to the configured default sender when omitted." }],
61+
optional: true,
62+
})
63+
@Parameter({
64+
runtimeName: "Cc",
65+
name: [{ code: "en-US", content: "CC" }],
66+
description: [{ code: "en-US", content: "Comma-separated list of CC recipients." }],
67+
optional: true,
68+
})
69+
@Parameter({
70+
runtimeName: "Bcc",
71+
name: [{ code: "en-US", content: "BCC" }],
72+
description: [{ code: "en-US", content: "Comma-separated list of BCC recipients." }],
73+
optional: true,
74+
})
75+
export class SendEmailWithAttachmentsFunction {
76+
async run(
77+
context: FunctionContext,
78+
To: string,
79+
Subject: string,
80+
Text: string,
81+
Attachments: SmtpAttachment[],
82+
Html?: string,
83+
From?: string,
84+
Cc?: string,
85+
Bcc?: string
86+
): Promise<SmtpSendResult> {
87+
try {
88+
return await sendEmail(
89+
{ To, Subject, Text, Html, From, Cc, Bcc, Attachments: Attachments ?? [] },
90+
context
91+
);
92+
} catch (error) {
93+
if (error instanceof RuntimeError) {
94+
throw error;
95+
}
96+
if (error instanceof Error) {
97+
throw new RuntimeError("ERROR_SENDING_EMAIL", error.message);
98+
}
99+
throw new RuntimeError("ERROR_SENDING_EMAIL", "An error occurred while sending the email.");
100+
}
101+
}
102+
}

0 commit comments

Comments
 (0)