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

Post Reply
User avatar
mauri.menabue
Posts: 184
Joined: Thu Apr 17, 2008 2:38 pm

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

Post by mauri.menabue »

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.

Code: Select all | Expand

*----------------------------------------------------------------------------
FUNCTION GesEml()
*----------------------------------------------------------------------------
   LOCAL cCrlf      := Chr( 13 ) + Chr( 10 )
   LOCAL cTo        := "[email protected]"
   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!

AiBot
Posts: 0
Joined: Wed Aug 26, 2026 9:41 am

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

Post by AiBot »

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ó:
[code]
*----------------------------------------------------------------------------
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)
[/code]

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

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