# How to Configure Email Operations Using Probe's SMTP and IMAP Plugins

> Learn to configure email operations in Probe using its SMTP and IMAP plugins. Discover essential YAML parameters to send mail and query mailboxes with HashiCorp go-plugin.

- Repository: [Tomohisa Oda/probe](https://github.com/linyows/probe)
- Tags: how-to-guide
- Published: 2026-03-06

---

**Probe configures email operations by treating SMTP and IMAP as separate plugin actions that communicate via HashiCorp go-plugin, requiring specific YAML parameters in the `with` block to send mail or query mailboxes.**

The `linyows/probe` repository implements email automation through standalone Go binaries that handle protocol-specific logic. When you configure email operations using Probe's SMTP and IMAP plugins, you define workflow steps that spawn these plugins to transmit messages or inspect mailboxes, with results feeding back into the workflow engine as structured data.

## Architecture of Probe's Email Plugins

Probe implements email capabilities as **plugin actions** rather than built-in functions. The core workflow engine communicates with SMTP and IMAP functionality through HashiCorp go-plugin, isolating protocol complexity into separate binaries.

### Plugin Entry Points

Each plugin follows an identical interface defined in [`actions/smtp/main.go`](https://github.com/linyows/probe/blob/main/actions/smtp/main.go) and [`actions/imap/main.go`](https://github.com/linyows/probe/blob/main/actions/imap/main.go):

| Plugin | Entry File | Core Struct | Run Method |
|--------|------------|-------------|------------|
| **SMTP** | [`actions/smtp/main.go`](https://github.com/linyows/probe/blob/main/actions/smtp/main.go) | `type Action struct { log hclog.Logger }` | `func (a *Action) Run(args []string, with map[string]any) (map[string]any, error)` |
| **IMAP** | [`actions/imap/main.go`](https://github.com/linyows/probe/blob/main/actions/imap/main.go) | `type Action struct { log hclog.Logger }` | `func (a *Action) Run(args []string, with map[string]any) (map[string]any, error)` |

Both `Run` methods execute a consistent five-step pattern:

1. **Validate** that the `with` map contains required parameters (SMTP requires `addr`, `from`, `to`; IMAP requires `host`, `username`, `password`).
2. **Log** truncated input using `probe.TruncateMapStringAny` for security.
3. **Create callbacks** via `mail.WithBefore/WithAfter` or `imap.WithBefore/WithAfter` to hook into lifecycle events.
4. **Delegate** protocol work to library code—SMTP calls `mail.Send` from [`mail/mail.go`](https://github.com/linyows/probe/blob/main/mail/mail.go), while IMAP calls `imap.Request` from [`imap/client.go`](https://github.com/linyows/probe/blob/main/imap/client.go).
5. **Return** a `map[string]any` that the workflow engine converts to step outputs accessible via `{{outputs.<step>.field}}`.

### SMTP Implementation Details

The SMTP plugin wraps Go's `net/smtp` library through the abstraction in [`mail/smtp.go`](https://github.com/linyows/probe/blob/main/mail/smtp.go). The `mail.Send` function in [`mail/mail.go`](https://github.com/linyows/probe/blob/main/mail/mail.go) orchestrates the transmission:

- **Connection**: `Dial` establishes TCP, upgrading to TLS if the underlying connection implements `*tls.Conn`.
- **Authentication**: Optional STARTTLS and AUTH mechanisms negotiate after connection.
- **Transmission**: The client loops over `MessageCount` to issue `MAIL FROM`, `RCPT TO`, and `DATA` commands.
- **Enrichment**: Each message receives a unique `Message-ID` via `genMsgID`, and subjects automatically append a hash ID through `appendIDtoSubject`.

### IMAP Implementation Details

The IMAP plugin builds on **emersion/go-imap/v2** via [`imap/client.go`](https://github.com/linyows/probe/blob/main/imap/client.go). The `NewReq` function supplies secure defaults: port 993, TLS enabled, 30-second timeout, and strict host checking.

The request flow executes sequentially:

1. **Dial**: Uses `imapclient.DialTLS` or `imapclient.DialInsecure` based on configuration.
2. **Authenticate**: Login with the provided `Username` and `Password`.
3. **Execute**: The `ExecCommands` dispatcher runs each command from the workflow (e.g., `Select`, `Search`, `Fetch`).

Helper methods translate workflow syntax into go-imap structures:

- `parseSequenceSet` handles message sequences.
- `parseUIDSet` manages UID ranges.
- `parseFetchItems` and `parseBodySection` convert expressions like `BODY[HEADER.FIELDS (FROM TO)]` into library-specific types.

## Configuring SMTP Operations

To send email, define a workflow step using `uses: smtp` and provide the required parameters in the `with` block.

### Basic SMTP Configuration

```yaml
name: Send Test Email
jobs:
- name: Email
  steps:
  - name: Send Notification
    uses: smtp
    with:
      addr: smtp.example.com:587
      from: sender@example.com
      to: recipient@example.com
      subject: Probe Test Email
      body: |
        Hello,

        This email was sent via Probe's SMTP plugin.
      my-hostname: localhost
    test: res.code == 0 && res.sent == 1

```

**Required parameters:**
- **`addr`**: Server address including port (e.g., `smtp.gmail.com:587`).
- **`from`**: Envelope sender address.
- **`to`**: Envelope recipient address.

**Optional parameters:**
- **`my-hostname`**: Defaults to "localhost" for the SMTP HELO/EHLO command.
- **`username`** and **`password`**: For AUTH LOGIN/PLAIN.
- **`tls`**: Boolean to enable STARTTLS.

The `test` assertion validates that `res.sent` equals the number of messages transmitted, confirming successful delivery through `mail.Send`.

## Configuring IMAP Operations

IMAP configuration requires connection details and a sequence of commands to execute against the mailbox.

### IMAP Mailbox Inspection

```yaml
name: Check InBOX for Alerts
jobs:
- name: IMAP
  steps:
  - name: List Unseen Mail
    uses: imap
    with:
      host: imap.example.com
      port: 993
      username: user@example.com
      password: "{{env.IMAP_PASSWORD}}"
      tls: true
      timeout: 30s
      strict_host_check: true
      commands:
        - name: select
          mailbox: INBOX
        - name: search
          criteria:
            since: today
            flags: ["unseen"]
        - name: fetch
          sequence: "*"
          dataitem: "BODY[HEADER.FIELDS (FROM TO SUBJECT DATE)]"
    test: res.code == 0 && res.data.search.count > 0

```

**Connection parameters:**
- **`host`** and **`port`**: Server endpoint (defaults to 993 with TLS).
- **`username`** and **`password`**: Authentication credentials.
- **`tls`**: Enable TLS encryption (default: true).
- **`strict_host_check`**: Validate TLS certificates against the hostname.

**Command structure:**
Each item in the `commands` list maps to the `imap.Command` struct in [`imap/client.go`](https://github.com/linyows/probe/blob/main/imap/client.go):

- **`select`**: Opens a mailbox (e.g., `INBOX`, `Sent`).
- **`search`**: Queries messages using criteria like `since`, `flags`, or `text`.
- **`fetch`**: Retrieves message data; `sequence` specifies ID ranges (`*` for all), and `dataitem` defines the fetch attribute.

Results populate `res.data.<command-name>`, allowing assertions like `res.data.search.count` to verify message quantities.

## Chaining SMTP and IMAP Workflows

Probe workflows can verify end-to-end email delivery by combining both plugins using the `needs` dependency syntax.

```yaml
name: Notify and Verify Delivery
jobs:
- name: Notify
  steps:
  - name: Send Alert
    uses: smtp
    with:
      addr: smtp.example.com:587
      from: alerts@example.com
      to: ops@example.com
      subject: "Daily Build Completed"
      body: "Build finished at {{unixtime()}}."
    test: res.sent == 1

- name: Verify
  needs: [Notify]
  steps:
  - name: Confirm Receipt
    uses: imap
    with:
      host: imap.example.com
      port: 993
      username: ops@example.com
      password: "{{env.IMAP_PASSWORD}}"
      commands:
        - name: select
          mailbox: INBOX
        - name: search
          criteria:
            since: "1 hour ago"
            text: "Daily Build Completed"
        - name: fetch
          sequence: "*"
          dataitem: "BODY[HEADER.FIELDS (FROM SUBJECT DATE)]"
    test: res.code == 0 && res.data.search.count > 0

```

The `needs: [Notify]` clause ensures the IMAP verification step only executes after the SMTP plugin reports successful transmission, enabling automated delivery confirmation.

## Summary

- Probe isolates email protocols into standalone plugins communicating via HashiCorp go-plugin, with entry points at [`actions/smtp/main.go`](https://github.com/linyows/probe/blob/main/actions/smtp/main.go) and [`actions/imap/main.go`](https://github.com/linyows/probe/blob/main/actions/imap/main.go).
- **SMTP configuration** requires `addr`, `from`, and `to` parameters in the `with` block, delegating transmission logic to `mail.Send` in [`mail/mail.go`](https://github.com/linyows/probe/blob/main/mail/mail.go).
- **IMAP configuration** requires `host`, `username`, and `password`, executing commands through `imap.Request` in [`imap/client.go`](https://github.com/linyows/probe/blob/main/imap/client.go) using the emersion/go-imap/v2 library.
- Both plugins return `map[string]any` results that become workflow outputs accessible via `{{outputs.<step>.field}}` notation.
- Workflow dependencies (`needs`) enable multi-step validation, such as sending via SMTP then confirming receipt via IMAP.

## Frequently Asked Questions

### What parameters are required to configure the SMTP plugin?

The SMTP plugin requires three parameters in the `with` block: `addr` (server host:port), `from` (sender address), and `to` (recipient address). According to [`actions/smtp/main.go`](https://github.com/linyows/probe/blob/main/actions/smtp/main.go), the `Run` method validates these fields before invoking `mail.Send`. Optional parameters include `username`, `password`, `tls`, and `my-hostname` for authenticated or TLS-encrypted connections.

### How does the IMAP plugin handle TLS connections?

The IMAP plugin defaults to secure connections via `imapclient.DialTLS` as implemented in [`imap/client.go`](https://github.com/linyows/probe/blob/main/imap/client.go). Set `tls: true` (the default) and `strict_host_check: true` to enforce certificate validation against the hostname. For testing environments, set `tls: false` to use `DialInsecure`, though this is not recommended for production workflows handling sensitive data.

### Can I access specific message headers from IMAP fetch results?

Yes. Use the `dataitem` parameter in fetch commands to specify exact header fields, such as `BODY[HEADER.FIELDS (FROM TO SUBJECT DATE)]`. The plugin's `parseBodySection` helper translates this syntax into go-imap structures. Results are stored under `res.data.fetch.messages` as a list, where each message contains the requested header fields as accessible properties (e.g., `{{outputs.Check Inbox.data.fetch.messages[0].subject}}`).

### How do I verify that an SMTP message was actually delivered?

Configure a dependent job using the `needs` syntax to run an IMAP step after the SMTP step completes. In the IMAP step, search for the unique `Message-ID` or subject hash that [`mail/mail.go`](https://github.com/linyows/probe/blob/main/mail/mail.go) automatically appends via `appendIDtoSubject`. Assert that `res.data.search.count` is greater than zero to confirm the message arrived in the target mailbox.