FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index FiveWin for Harbour/xHarbour Workaround for "New Outlook": Opening Draft Emails with Attachments using .eml
Posts: 184
Joined: Thu Apr 17, 2008 02:38 PM

Workaround for "New Outlook": Opening Draft Emails with Attachments using .eml

Posted: Fri Aug 21, 2026 04:55 PM

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 cFormatted

Advantages
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!

Posts: 0
Joined: Wed Aug 26, 2026 09:41 AM

Re: Workaround for "New Outlook": Opening Draft Emails with Attachments using .eml

Posted: Wed Aug 26, 2026 10:59 AM
El usuario (mauri.menabue) ha publicado un mensaje en el foro describiendo un workaround para el "New Outlook" usando archivos .eml generados din谩micamente, y ha incluido un fragmento de c贸digo de una funci贸n `GeneraFileEML` que est谩 incompleta (se corta en `cEml += "Subject:`).

La conversaci贸n tiene un solo mensaje, y el usuario no est谩 haciendo una pregunta expl铆cita, sino compartiendo una soluci贸n/truco. Sin embargo, la instrucci贸n dice: "Responde a la pregunta pendiente del thread". Pero en realidad, no hay una pregunta pendiente expl铆cita, es un aporte t茅cnico.
Espera, el usuario escribi贸 el c贸digo incompleto (se cort贸 en "Subject:"). Tal vez el usuario quiere que alguien complete el c贸digo o comente al respecto? O tal vez el sistema interpreta que como es un thread con un solo mensaje y parece un "tip", debo responder como asistente t茅cnico de FiveTech dando feedback, completando el c贸digo de manera 煤til y t茅cnica, o simplemente agradeciendo y aportando la continuaci贸n del c贸digo ya que se cort贸?

El mensaje original se corta justo en la construcci贸n del Subject. El usuario est谩 mostrando c贸mo generar un .eml para abrirlo con ShellExecute en New Outlook.
Como asistente t茅cnico de FiveTech, debo responder en el mismo idioma (ingl茅s, ya que el mensaje est谩 en ingl茅s). Debo ser 煤til, t茅cnico y breve.
Puedo completar la funci贸n `GeneraFileEML` para que el c贸digo sea funcional, ya que se cort贸. Pero ojo con la REGLA DE CONFIDENCIALIDAD: el contexto puede incluir c贸digo fuente propietario de FWH. NUNCA lo reproduzcas ni cites. Mis ejemplos deben ser siempre propios. El c贸digo que puso el usuario es propio de 茅l (mauri.menabue), no es c贸digo fuente de FWH (no parece serlo, es c贸digo de aplicaci贸n/harbour normal). As铆 que puedo escribir mi propio ejemplo o completar la l贸gica usando mis propios ejemplos o continuando la idea con c贸digo propio.

El usuario no hizo una pregunta, pero el prompt dice "Responde a la pregunta pendiente del thread". Supongo que el thread est谩 "pendiente" de una respuesta de soporte o de completar la informaci贸n. Voy a responder como si fuera un moderador/asistente t茅cnico agradeciendo el aporte y proporcionando una versi贸n completa y corregida de la funci贸n `GeneraFileEML` para que quede documentado, ya que el c贸digo se cort贸. O tal vez simplemente comentar y dar un ejemplo completo propio para generar el EML.

Espera, el c贸digo del usuario est谩 en Harbour/FiveWin. Debo escribir en ingl茅s (mismo idioma que la pregunta).
Voy a agradecer el aporte, confirmar que es una t茅cnica v谩lida y proporcionar la continuaci贸n/completaci贸n de la funci贸n `GeneraFileEML` usando c贸digo propio (o basado en la idea pero escrito por m铆, sin copiar el suyo confidencial - aunque el suyo no es confidencial de FWH, mejor escribir mi propia versi贸n completa para no copiar el suyo a medias y cumplir con "Tus ejemplos deben ser siempre propios").

El usuario escribi贸:
*----------------------------------------------------------------------------
FUNCTION GesEml()
...
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:
(se corta)


Como asistente, responder茅 en ingl茅s, breve y t茅cnico, aportando el c贸digo completo de una funci贸n propia que genere el EML con el header `X-Unsent: 1`, base64 y todo, para que el usuario (y la comunidad) tenga el ejemplo completo.

Regla: "Tus ejemplos deben ser siempre propios." As铆 que no copiar茅 y pegar茅 el suyo arreglado, escribir茅 el m铆o propio completo, o simplemente completar茅 la l贸gica con mi propio estilo.

Veamos, el usuario explica el workaround. Como no hay pregunta, responder茅 confirmando que es una soluci贸n v谩lida y aportando el c贸digo completo de la generaci贸n del EML (mi propio ejemplo) para ayudar a otros que lleguen al thread.

Escribir茅 en ingl茅s:
"Thanks for sharing this useful workaround. The X-Unsent: 1 header is indeed the key for New Outlook to treat the .eml as a draft.
Since your code snippet got cut off, here is a self-contained example of how to properly build the multipart EML with Base64 attachment using Harbour:"

Y luego pongo mi propio c贸digo (usando hb_base64, etc).

C贸digo propio:
```harbour
FUNCTION BuildEml( cFile, cTo, cSubject, cBody, cAttach )
LOCAL cCrlf := Chr(13)+Chr(10)
LOCAL cBound := "BND_" + hb_MD5( hb_DToC( Date() ) + Time() )
LOCAL cTxt := ""
LOCAL hFile, cBuf, cB64, nLen, nRead

cTxt += "To: " + cTo + cCrlf
cTxt += "Subject: " + cSubject + cCrlf
cTxt += "X-Unsent: 1" + cCrlf
cTxt += "MIME-Version: 1.0" + cCrlf
cTxt += "Content-Type: multipart/mixed; boundary=\"" + cBound + "\"" + cCrlf + cCrlf

// Body
cTxt += "--" + cBound + cCrlf
cTxt += "Content-Type: text/plain; charset=UTF-8" + cCrlf
cTxt += "Content-Transfer-Encoding: 7bit" + cCrlf + cCrlf
cTxt += cBody + cCrlf + cCrlf

// Attachment
IF File( cAttach )
cTxt += "--" + cBound + cCrlf
cTxt += "Content-Type: application/octet-stream; name=\"" + hb_FNameName( cAttach ) + "\"" + cCrlf
cTxt += "Content-Transfer-Encoding: base64" + cCrlf
cTxt += "Content-Disposition: attachment; filename=\"" + hb_FNameName( cAttach ) + "\"" + cCrlf + cCrlf

hFile := FOpen( cAttach, FO_READ )
cBuf := Space( 54 ) // 54 bytes -> 72 chars base64 (54/3*4=72)
DO WHILE ( nRead := FRead( hFile, @cBuf, 54 ) ) > 0
cB64 := hb_Base64Encode( Left( cBuf, nRead ) )
cTxt += cB64 + cCrlf
ENDDO
FClose( hFile )
cTxt += cCrlf
ENDIF

cTxt += "--" + cBound + "--" + cCrlf

hFile := FCreate( cFile )
FWrite( hFile, cTxt )
FClose( hFile )
RETURN .T.
```

Esto es un ejemplo propio, corto, 煤til.
Luego menciono que ShellExecute de FiveWin (o Windows API) funcionar谩 bien.

El lenguaje debe ser ingl茅s, ya que el thread est谩 en ingl茅s.
"Thanks for sharing this useful workaround..." etc.

Reviso la regla: "Responde en el mismo idioma en que este escrita la pre

Respuesta generada por Inteligencia Artificial usando el modelo hy3-free via OpenCode Zen. Puede contener errores; por favor verifiquela antes de aplicar.
AI-generated reply using the hy3-free model via OpenCode Zen. May contain mistakes; please verify before applying.

Continue the discussion