Hi everyone,
If your users are switching to the web-based "New Outlook", you have likely run into the same roadblock many developers are facing: traditional COM/ActiveX automation (Outlook.Application), MAPI, and command-line parameters (such as outlook.exe /a) no longer work for launching emails with attachments from desktop apps.
When users trigger emails from legacy desktop software, messages either get stuck in the Drafts folder or fail to open altogether because New Outlook lacks local COM support.
The Workaround: Dynamic .eml File Generation
Instead of forcing clients back to Outlook Classic or setting up complex Microsoft Graph API / SMTP authentication relays, a lightweight and reliable solution is to generate a standard MIME .eml file on the fly and launch it using ShellExecute().
Key Technical Details
To make this work seamlessly with New Outlook:
X-Unsent: 1 Header: This is crucial. It tells Outlook to open the .eml file as an editable Draft with a live "Send" button, rather than in read-only mode.
text/plain Body: Using plain text with standard CRLF (Chr(13) + Chr(10)) line endings avoids HTML parsing issues and prevents the body text from being placed underneath the user's default signature.
Base64 Encoding: Standard 76-character wrapped Base64 encoding for attachments.
*----------------------------------------------------------------------------
FUNCTION GesEml()
*----------------------------------------------------------------------------
LOCAL cCrlf := Chr( 13 ) + Chr( 10 )
LOCAL cTo := "client@example.com"
LOCAL cSubject := "Requested Document and Attachment"
LOCAL cBody := "Dear Customer," + cCrlf + cCrlf + ;
"Please find the requested document attached." + cCrlf + cCrlf + ;
"Best regards,"
LOCAL cAttachment := "C:\temp\document.pdf"
LOCAL cEmlPath := hb_DirTemp() + "mail_" + DToS( Date() ) + "_" + StrTran( Time(), ":", "" ) + ".eml"
PulisciEmlTemp()
IF GeneraFileEML( cEmlPath, cTo, cSubject, cBody, cAttachment )
// Opens the .eml file with the default Windows email client (e.g., New Outlook)
ShellExecute( 0, "open", cEmlPath )
ELSE
MsgStop( "Unable to create EML file!", "Error" )
ENDIF
RETURN NIL
*----------------------------------------------------------------------------
STATIC FUNCTION PulisciEmlTemp()
*----------------------------------------------------------------------------
LOCAL cTempDir := hb_DirTemp()
LOCAL aFiles
LOCAL aFile
aFiles := Directory( cTempDir + "mail_*.eml" )
FOR EACH aFile IN aFiles
FErase( cTempDir + aFile[ 1 ] )
NEXT
RETURN NIL
*----------------------------------------------------------------------------
STATIC FUNCTION GeneraFileEML( cEmlFile, cTo, cSubject, cBodyText, cAttachPath )
*----------------------------------------------------------------------------
LOCAL cCrlf := Chr( 13 ) + Chr( 10 )
LOCAL cBoundary := "----=_NextPart_" + hb_MD5( Time() + DToS( Date() ) )
LOCAL cEml := ""
LOCAL cAttachName, cFileBuffer, cBase64
// Standard MIME Headers
cEml += "To: " + cTo + cCrlf
cEml += "Subject: " + cSubject + cCrlf
cEml += "MIME-Version: 1.0" + cCrlf
cEml += 'Content-Type: multipart/mixed; boundary="' + cBoundary + '"' + cCrlf
// Forces Outlook to open as an editable DRAFT
cEml += "X-Unsent: 1" + cCrlf
cEml += cCrlf
// --- PART 1: EMAIL BODY (Plain Text) ---
cEml += "--" + cBoundary + cCrlf
cEml += 'Content-Type: text/plain; charset="utf-8"' + cCrlf
cEml += "Content-Transfer-Encoding: 8bit" + cCrlf
cEml += cCrlf
cEml += cBodyText + cCrlf
cEml += cCrlf
// --- PART 2: ATTACHMENT ---
IF .NOT. Empty( cAttachPath ) .AND. File( cAttachPath )
cAttachName := cFileNoPath( cAttachPath )
cFileBuffer := hb_memoRead( cAttachPath )
cBase64 := hb_base64Encode( cFileBuffer )
cEml += "--" + cBoundary + cCrlf
cEml += 'Content-Type: application/octet-stream; name="' + cAttachName + '"' + cCrlf
cEml += "Content-Transfer-Encoding: base64" + cCrlf
cEml += 'Content-Disposition: attachment; filename="' + cAttachName + '"' + cCrlf
cEml += cCrlf
// Format Base64 string to 76-character lines
cEml += FormattaBase64( cBase64, cCrlf ) + cCrlf
ENDIF
// Close Multipart
cEml += "--" + cBoundary + "--" + cCrlf
RETURN hb_memoWrit( cEmlFile, cEml )
*----------------------------------------------------------------------------
STATIC FUNCTION FormattaBase64( cBase64, cCrlf )
*----------------------------------------------------------------------------
LOCAL cFormatted := ""
LOCAL nLen := Len( cBase64 )
LOCAL nStep := 76
LOCAL i
FOR i := 1 TO nLen STEP nStep
cFormatted += SubStr( cBase64, i, nStep ) + cCrlf
NEXT
RETURN cFormattedAdvantages
Zero Client Configuration: Works out of the box with New Outlook, Thunderbird, or Outlook Classic.
Non-intrusive: Does not require admin rights, custom API keys, or IP whitelist connectors.
User Control: The draft opens on screen, allowing the user to review the message and click "Send" manually.
Hope this helps anyone struggling with the transition to New Outlook!