---
title: Send email with attachment
summary: Sends a message through the attachment-capable SOAP endpoint, normalizing whichever attachment source the caller supplied — stream, file path, or byte array — into the single byte-array shape `SendWithAttach` expects.
status: draft
owner: SAIF Documentation Generator
last_reviewed: 2026-07-30
library: SAIF.Framework.Mail
reference_type: package
operation_id: MAIL-OP-004
trigger: Caller invokes any `Mail.SendMailWithAttachment(...)` overload.
---

# Send email with attachment

This operation is the library's single-attachment send path. Four public overloads let a caller supply the attachment as an open `Stream`, a file path, or an already-loaded `Byte()` array, with or without a message body; all four converge on the same `SendWithAttach` SOAP call once the attachment has been normalized to bytes.

| Field | Value |
| --- | --- |
| **Operation ID** | MAIL-OP-004 |
| **Trigger** | Caller invokes any `Mail.SendMailWithAttachment(...)` overload. |
| **Inputs** | `inToAddress`, `inFromAddress`, `inSubject`, optional `inMessage`, one attachment source (`Stream`, file path, or `Byte()`), and `inAttachName`. |
| **Outputs** | An attachment byte payload for `SendWithAttach`; SOAP dispatch; the function returns `True`. |
| **Primary components** | `Mail.SendMailWithAttachment`, `GetStreamAsBytes`, `GetFileAsBytes`, `SAIFMail.clsSAIFMessage`, `AppGlobal.EnvironmentService` |
| **Entry point** | `SAIF.Framework.Mail\MailServices.vb` |

## Steps

1. Determine the message body from the overload the caller selected.
   - The stream/no-body overload always uses `String.Empty` as the message; it accepts no body parameter.
   - The stream/body, file/body, and byte-array/body overloads forward the caller-supplied `inMessage` unchanged.
2. Normalize the attachment source into the byte array `SendWithAttach` requires.
   - For the stream overloads, call the local `GetStreamAsBytes(inStream)` helper ([BR-005](../business-rules.md#br-005-a-nothing-stream-input-yields-a-nothing-byte-array-instead-of-an-exception)).
     - If `inStream Is Nothing`, the helper returns `Nothing` immediately rather than raising an exception ([BR-005](../business-rules.md#br-005-a-nothing-stream-input-yields-a-nothing-byte-array-instead-of-an-exception)).
     - Otherwise the helper allocates a buffer sized from `inStream.Length` and reads starting at the stream's current position.

     > **Origin**: These sub-steps are implemented in [Read a stream into bytes](read-stream-into-bytes.md) and are executed on behalf of this operation.
   - For the file-path overload, call the local `GetFileAsBytes(inFilePath)` helper ([BR-004](../business-rules.md#br-004-file-handles-are-always-released-but-the-original-exceptions-stack-trace-is-reset)).
     - The helper opens the file, allocates a buffer sized from `FS.Length`, reads the file's bytes, and closes the stream in `Finally` regardless of outcome ([BR-004](../business-rules.md#br-004-file-handles-are-always-released-but-the-original-exceptions-stack-trace-is-reset)).
     - If opening or reading the file raises an exception, the helper re-throws it with `Throw ex` ([BR-004](../business-rules.md#br-004-file-handles-are-always-released-but-the-original-exceptions-stack-trace-is-reset)).

     > **Origin**: These sub-steps are implemented in [Read a file into bytes](read-file-into-bytes.md) and are executed on behalf of this operation.
   - For the byte-array overload, the caller-supplied `inAttachment` array is forwarded unchanged — no copy, validation, or size check is performed.
3. Create a new `SAIFMail.clsSAIFMessage` SOAP client.
4. Look up the mail endpoint by calling `AppGlobal.EnvironmentService.GetConfigSetting("legacyMailServiceUrl")` and assign the result to the proxy's `Url` property ([BR-001](../business-rules.md#br-001-mail-is-always-routed-through-the-configured-legacy-mail-service-url)).
   - `AppGlobal.EnvironmentService` lazily instantiates the environment-service implementation appropriate to the current execution context.
   - `GetConfigSetting` returns the resolved setting value from an override or cached configuration data, or throws if the key cannot be found.

   > **Origin**: Steps 4.1–4.2 are implemented in [Retrieve a configuration setting](../../saif-framework-security-environmentapi/operations/retrieve-configuration-setting.md) (`SAIF.Framework.Security.EnvironmentApi`) and are executed on behalf of this operation.
5. Call `SendWithAttach(inToAddress, inFromAddress, inSubject, message, attachmentBytes, inAttachName)`.
6. Dispose the SOAP proxy and clear the module-level reference.
7. Return `True` once the dispatch path completes ([BR-003](../business-rules.md#br-003-send-operations-always-report-success-regardless-of-actual-outcome)).
   - On failure: exceptions from attachment normalization, configuration lookup, or the SOAP call itself all propagate to the caller unmodified, because this method never catches them.

## Process flow

The following diagram visualizes this operation's control flow.

```mermaid
flowchart TD
    Start([SendMailWithAttachment call]) --> Source{Attachment source}
    Source -->|Stream| Stream[Read stream into bytes]
    Source -->|File path| File[Read file into bytes]
    Source -->|Byte array| Bytes[Reuse caller bytes]
    Stream --> Proxy[Create clsSAIFMessage]
    File --> Proxy
    Bytes --> Proxy
    Proxy --> Url[Resolve legacyMailServiceUrl]
    Url --> Send[Call SendWithAttach]
    Send --> Dispose[Dispose proxy]
    Dispose --> End([Return True])
```

## Failure and edge-case behavior

- Both byte-conversion helpers size their buffer with `Length` as a VB array upper bound rather than an element count, so an attachment converted from a stream or a file always carries one extra, zero-valued trailing byte.
- Neither helper rewinds its source before reading; a stream that has already been partially consumed produces an attachment payload that is only partially populated, and there is no compensating logic in this operation.
- A `Nothing` stream input becomes a `Nothing` attachment array rather than an exception, so `SendWithAttach` can be invoked with no attachment data even though an attachment overload was selected.
- The operation always calls `SendWithAttach`; it never selects the generated proxy's `SendWithAttachAsText` method, so there is no plain-text-specific attachment path distinct from the one described here.

## Related documentation
- [Operations and processes](../operations-processes.md)
- [Overview](../overview.md)
- [Architecture](../architecture.md)
- [Dependencies](../dependencies.md)
- [Business rules](../business-rules.md)
- [Configuration](../configuration.md)
- [API reference](../api/index.md)

## Sources
- SAIF.Framework.Mail\MailServices.vb
- SAIF.Framework.Mail\Web References\SAIFMail\Reference.vb
- SAIF.Framework.Security\AppGlobal.vb
- SAIF.Framework.Security.EnvironmentApi\Core\Services\IEnvironmentService.cs
- SAIF.Framework.Security.EnvironmentApi\Services.Common\EnvironmentServiceBase.cs
