2
API - Can I send emails via API insteald os WebForms ?
Question asked by Curtis Kropar www.HawaiianHope.org - 7/19/2018 at 9:27 AM
Answered
HI All.
I have been looking for info and cant seem to find anything useful.
Previous questions regarding API's generally fall into 2 categories, either not answered or answered with "documentation is coming later" (smarterPeeps seem to have been saying that for like 2 years now)
 
Simple question. 
Instead of using Webforms, ASP scrips and CDONTS/CDOsys, can i send emails from web pages using API calls into smartermail ?
We are currently using SM Enterprise 14.xxxxxx, cant tell if this is possible.
Will consider upgrading to 15, 16 or 17 or 32 or 54 if it gets the job done.. but cant find anything to base decisions on
 
I want to take our database of over 13,000 contacts (for our org, and thousands of more contacts for other orgs we serve) and generate emails directly, and have them signed with the proper smartermail DKIM / reverse whatevers and dns lookups so that they get delivered.  Emails sent with the CDOsys are not always delivered and often getting tagged as spam

www.HawaiianHope.org - Providing technology services to non profit organizations, low income families, homeless shelters, clean and sober houses and prisoner reentry programs. Since 2015, We have refurbished over 11,000 Computers !

6 Replies

Reply to Thread
0
Matt Petty Replied
Employee Post Marked As Answer
The APIs for authentication are there, which is for writing a service or program is probably the most important. The other APIs are not documented but they can be reverse engineered using a browsers network console which unfortunately isn't the easiest process. However, if you are interested I have still included some documentation of the mail api as it is today.

POST /api/v1/mail/message-put
Fields are bcc, cc, from, subject, to, messagePlainText, messageHTML
There are more but these are the common sending variables. I'd say the ones provided in my example are the bare minimum 'subject', 'to', and 'messagePlainText'.

Here is a video example using Postman.
 
Matt Petty Senior Software Developer SmarterTools Inc. www.smartertools.com
0
Employee Replied
Employee Post
Along with Matt's response, we have an example from an internal tool that we wouldn't mind sharing if you want to see a code example.
0
Awesome !
So, this will work with 14 right now ?
And.. if i understand correctly, sending through the API will then "sign" the emails with the proper DKIM, PTR, whatever... LMNOP... that is needed to show that it is a legit email from a legit domain.  yes ?
www.HawaiianHope.org - Providing technology services to non profit organizations, low income families, homeless shelters, clean and sober houses and prisoner reentry programs. Since 2015, We have refurbished over 11,000 Computers !
0
Employee Replied
Employee Post
Hello Curtis, sorry we weren't clear, this is with the new api in 16+. Sending messages through the api though will put them in the spool so they get processed by SmarterMail normally.
0
Resurrecting this from 6 years ago.... 
Now running Build 9014, I have still not gotten the ASP API thing going. But I really need to get this done here in the next few weeks.   Sorry, but I need a sample to see it. Apparently I am doing something wrong and just really not sure what that is.
So, Can someone provide me a full example of making the connection and sending a basic email with ASP ?

www.HawaiianHope.org - Providing technology services to non profit organizations, low income families, homeless shelters, clean and sober houses and prisoner reentry programs. Since 2015, We have refurbished over 11,000 Computers !
0
Tony Scholz Replied
Employee Post
Hello Curtis, 

I am not familiar with ASP but have working examples in both Powershell and Python. I will add both below in case you can convert them over. 

I included an example converted from Powershell to ASP by ChatGPT (https://chatgpt.com/share/67367ce8-3ed4-800f-92f8-bdb673fd3ced), I have not tested this. 

Powershell

    $API = $null;
    $APIHost = 'localhost';
    $HTTPS = $false;
    $API = @{ 
        authUserName = 'admin'
        authPassword = 'admin'
        Method       = 'POST'
        ContentType  = 'application/json'
        URI          = 'http'+$(if($HTTPS -eq $true){'s'})+'://'+$APIHost+'/'
        ShowAuthData = $true
        rootAPIURI   = 'http'+$(if($HTTPS -eq $true){'s'})+'://'+$APIHost+'/Documentation/api#/reference/SmarterMail.Web.Controllers.'
        debug        = $false
    };

    function PrimaryAuth {
    <#
    .Synopsis
        Primary API auth call
    .DESCRIPTION
        Primary Auth Call that will add the appropriate header s for refreshing, calling headers
    .EXAMPLE
        PrimaryAuth
    .EXAMPLE
        $r=(PrimaryAuth -twoFactorCode '' -retrieveAutoLoginToken $true );
        Start-Process -FilePath Chrome -ArgumentList ($r.autoLoginUrl);
    #>
        [OutputType([Object])]
        [CmdletBinding()]
        Param (
            [Parameter(Mandatory=$false)]
            [string]$twoFactorCode,

            [Parameter(Mandatory=$false)]
            [ValidateSet($true,$false)]
            [Boolean]$retrieveAutoLoginToken,

            [Parameter(Mandatory=$false)]
            [string]$autoLoginToken
        )

        $APIAuth = @{};
        $APIAuth = @{
            Uri=('http' + $(if ($HTTPS -eq $true ) {'s'}) + '://' + $APIHost + '/api/v1/auth/authenticate-user');
            Body =@{
                username=$API.authUserName
                password=$API.authPassword
                language=$null
                twoFactorCode=$twoFactorCode
                retrieveAutoLoginToken=$retrieveAutoLoginToken
            }
        };

        $Auth = Invoke-RestMethod -Uri $APIAuth.Uri -ContentType $API.ContentType -Body ($APIAuth.Body|ConvertTo-Json -Compress) -Method POST;

        # SERVER ADMIN AUTH TOKEN 
        $API.Remove('Headers');
        $API.Add('Headers', @{ 'Authorization' = "Bearer $($Auth.accessToken)" });

        # REFRESH
        $API.Remove('Refresh');
        $API.Add('Refresh' , @{"token" = $($Auth.refreshToken)});

        # EXPIRATION
        if ( $Auth.accessTokenExpiration ) { 
            $API.Remove('accessTokenExpiration');
            [DateTime]$ATExp = $Auth.accessTokenExpiration;
            $API.Add('accessTokenExpiration', $ATExp);
        }

        if ($API.ShowAuthData) {return $Auth};
    };

    function impersonate_user {
    <#
    .Synopsis
        Impersonate a user
    .DESCRIPTION
        
    .EXAMPLE
        This example will work with the newer builds
            ImpersonateUser 'admin@domain.tld' 
        
        This example works with the Pre BETA builds
        BETA went live with this version --> Build 8495 (Apr 5, 2023)
            ImpersonateUser 'admin@domain.tld' -Beta
    .EXAMPLE
        $r=(PrimaryAuth -twoFactorCode '' -retrieveAutoLoginToken $true );
        Start-Process -FilePath Chrome -ArgumentList ($r.autoLoginUrl);
    .NOTES
        The trigger used to be BETA and now it is preBeta. I Added Beta as 
        as aliase to work with older code. This was changed as it was orginally
        added when the code was still in Beta but is it now in the Public Branch.
    #>
        [CmdletBinding()]
        [Alias("ImpersonateUser")]
        Param (
            [Parameter(Mandatory=$true)][string]$user_email,
            [Parameter()][alias('beta')][switch]$preBeta
        )

        if (!$preBeta) {
            $URI = $('http' + $(if ($HTTPS -eq $true ) {'s'}) + '://' + $APIHost + '/api/v1/settings/domain/impersonate-user/' )
            $Impersonate = Invoke-RestMethod -Method POST -Uri $URI -ContentType $API.ContentType -Headers $API.Headers -Body "{`"email`":`"$user_email`"}"
        } else {
            $URI = $('http' + $(if ($HTTPS -eq $true ) {'s'}) + '://' + $APIHost + '/api/v1/settings/domain/impersonate-user/' + $user_email )
            $Impersonate = Invoke-RestMethod -Method POST -Uri $URI -ContentType $API.ContentType -Headers $API.Headers 
        }

        # IMPERSONATE AUTH TOKEN
        $API.Remove('HeaderImpersonate')
        $API.Add('HeaderImpersonate', @{ 'Authorization' = "Bearer $($Impersonate.impersonateAccessToken)" })
        $API.ImpersonateObject=$Impersonate;

        # REFRESH
        $API.Remove('impersonateRefresh');
        $API.Add('impersonateRefresh' , @{"token" = $($Impersonate.impersonateRefreshToken)});

        # EXPIRATION
        $API.Remove('impersonateAccessTokenExpiration');
        [DateTime]$ATExp = $Impersonate.impersonateAccessTokenExpiration;
        $API.Add('impersonateAccessTokenExpiration', $ATExp);

        if ($API.ShowAuthData) {return $Impersonate;}
    };

    function UpdateURImethod {
    <#
    .Synopsis
       Pass in new API Method and new API URI
    .DESCRIPTION
       This can be called at teh beigging of any new call to update the $API array 
       with a new METHOD and a new URI 
    #>
	    [CmdletBinding()]
	    Param (
		    [Parameter(Mandatory=$true,
			    HelpMessage="GET or POST method")]
		    [ValidateNotNullOrEmpty()]
		    [ValidateSet("get", "post")]
		    [String]$a,

		    [Parameter(Mandatory=$true)]
		    [ValidateNotNullOrEmpty()]
		    [String]$b
	    )
        
        $b = $b.TrimStart('/');
	    $API.uri = 'http' + $(if ($HTTPS -eq $true ) {'s'}) + '://' + $APIHost + '/' + $b; $API.Method = $a; 
    };

    function ConnectPrimary {
        [CmdletBinding()]
        Param (
            [Parameter(Mandatory=$false)]
            [String]$json_body,

            [Parameter(Mandatory=$false)]
            [String]$filePath
        ) 
        Begin {
            $params = @{
                Uri          = $API.Uri
                ContentType  = $API.ContentType
                Method       = $API.Method
                Headers      = $API.Headers
            }
        }
        Process { 
            if ($json_body) {$params.Add("Body", $json_body)}
            if ($filePath) {$params.Add("OutFile", $filePath)}

            try { 
                $call = Invoke-RestMethod @params
            } 
            catch { 
                $params
            }
        } 
        end { return $call }
    }

    function ConnectImpersonate {
        [CmdletBinding()]
        Param (
            [Parameter(Mandatory=$false)]
            [ValidateScript({
                try {
                    $jsonObject = $null
                    return ($_ | ConvertFrom-Json)
                }
                catch {throw "Invalid JSON format for the body."}
            })]
            [String]$json_body,

            [Parameter(Mandatory=$false)]
            [ValidateScript({
                if (Test-Path -Path $_ -PathType Leaf) {
                    return $true
                } else {throw "Invalid file path."}
            })]
            [String]$filePath
        ) 
        begin {
            $params = @{
                Uri          = $API.Uri
                ContentType  = $API.ContentType
                Method       = $API.Method
                Headers      = $API.HeaderImpersonate
            }
        }
        process {
            if ($json_body) {$params.Add("Body", $json_body)}
            if ($filePath) {$params.Add("OutFile", $filePath)}

            try { 
                $call = Invoke-RestMethod @params
            } 
            catch { 
                $Error[0];$params
            }
        }
        end { return $call }
    }

PrimaryAuth;
ImpersonateUser 'admin@ascholz.io';

UpdateURImethod 'POST' 'api/v1/mail/message-put'

for ($i=0;$i -le 2;$i++ ) {
    $MSG = @{
	    to = "all@ascholz.io"
	    cc = ""
	    bcc = ""
	    #date = 1679957034265
	    from = "admin@ascholz.io"
	    replyTo = "admin@ascholz.io"
	    subject = "!!! Test MSG $((Get-Date).ToString("HH:mm:ss:ffff"))"
	    messageHTML = 'Test Message Here'
	    ownerEmailAddress = ""
	    actions = @{}
	    readReceiptRequested = $false
	    deliveryReceiptRequested = $false
	    priority = 1
	    inlineToRemove = @()
	    selectedFrom = ":admin@ascholz.io"
    }
    ConnectImpersonate ($MSG|ConvertTo-Json -Compress)
    sleep -Seconds 1;
} 


Python

full code with the appropriate methods from the functions file. 

import requests
import json
import logging
from logging.handlers import TimedRotatingFileHandler, RotatingFileHandler
from typing import Literal, Optional
from pprint import pprint
from datetime import datetime, timezone
from colorama import  Fore, Back, Style, init
import random

class API:
    ContentType = 'application/json'

    # Primary auth headers
    headers = {'Authorization': ''}
    refresh = {'token': ''}
    accessToken = None
    refreshToken = None
    accessTokenExpiration = None
    
    # Impersonation headers
    impersonateObject = None
    HeaderImpersonate = {'Authorization': ''}
    impersonateRefresh = {'token': ''}
    impersonateRefreshToken = None
    impersonateAccessTokenExpiration = None
    impersonateAccessToken = None

    def __init__(self, u='admin', p='admin', h='sup-ascholz.st.local', s=False, ShowAuthData: bool = False, debug: bool = False):
        self.username = u
        self.password = p
        self.host = h
        self.secure = s
        self.uri = f"http{'s' if s else ''}://{self.host}/"
        self.ShowAuthData = ShowAuthData
        self.debug = debug
        self.authenticate()
        self.get_global_mail_settings()

    def authenticate(self):
        """
        Authenticates and retrieves access and refresh tokens for API access.
        """
        authUri = f"{self.uri}api/v1/auth/authenticate-user"
        auth_obj = {'username': self.username, 'password': self.password}
        headers = {'Content-Type': self.ContentType}

        response = requests.post(authUri, json=auth_obj, headers=headers)

        if response.status_code == 200:
            auth_data = response.json()
            self.accessToken = auth_data.get('accessToken')
            self.refreshToken = auth_data.get('refreshToken')
            self.accessTokenExpiration = auth_data.get('accessTokenExpiration')
            self.headers['Authorization'] = f"Bearer {self.accessToken}"
            self.refresh['token'] = self.refreshToken
            if self.ShowAuthData:
                print("Authentication Successful:", auth_data)
            else: 
                print(f"{Fore.GREEN}Authentication Successful!{Style.RESET_ALL} {self.host}")
        else:
            print(f"Error: Received status code {response.status_code}")
            print("Response content:", response.text)

    def refresh_auth(self):
        """
        Refreshes the authentication token using the stored refresh token.
        """
        _uri = 'api/v1/auth/refresh-token'
        _body = self.refresh
        if self.debug: print(Fore.MAGENTA + f'accessTokenExpiration: {self.accessTokenExpiration}' + Style.RESET_ALL)
        _results = requests.post(url=_uri, json=_body, headers=self.headers)
        self.accessToken = _results.json()['accessToken']
        self.refreshToken = _results.json()['refreshToken']
        self.accessTokenExpiration = _results.json()['accessTokenExpiration']
        self.headers['Authorization'] = ("Bearer " + self.accessToken)
        self.refresh['token'] = self.refreshToken
        if self.ShowAuthData:
                print("Authentication Successful:", _results.json())

    def impersonate_user(self, user_email, preBeta=False):
        if preBeta:
            uri = f"{self.uri}api/v1/settings/domain/impersonate-user/{user_email}"
            response = requests.post(uri, headers=self.headers)
        else:
            uri = f"{self.uri}api/v1/settings/domain/impersonate-user/"
            payload = {"email": user_email}
            response = requests.post(uri, headers=self.headers, json=payload)

        if response.status_code == 200:
            impersonate_data = response.json()

            # Update the API class instance with impersonation tokens and expiration
            self.HeaderImpersonate = {'Authorization': f"Bearer {impersonate_data['impersonateAccessToken']}"}
            self.impersonateRefresh = {'token': impersonate_data['impersonateRefreshToken']}
            self.impersonateAccessTokenExpiration = impersonate_data.get('impersonateAccessTokenExpiration')
            self.impersonateRefreshTokenExpiration = impersonate_data.get('impersonateRefreshTokenExpiration')
            self.impersonateObject = impersonate_data  # Store the full response if needed

            if self.ShowAuthData:
                print("Impersonate Data:", impersonate_data)  # Show the auth data if required

            return impersonate_data  # Optionally return the data if further use is required

        else:
            print(f"Error: Received status code {response.status_code}")
            print("Response content:", response.text)
            return None

def cImpersonate(api_class, method, uri_path, json_body=None):
    uri = api_class.uri + uri_path
    if json_body:
        # print('JSON body passed')
        if str(method).upper() == "POST":
            c_primary_data = requests.post(uri, headers=api_class.HeaderImpersonate, json=json_body)
        elif str(method).upper() == "GET":
            c_primary_data = requests.get(uri, headers=api_class.HeaderImpersonate, json=json_body)
        else:
            return print("The METHOD needs to be either GET or POST")
    elif not json_body:
        # print('JSON body NOT passed')
        if str(method).upper() == "POST":
            c_primary_data = requests.post(uri, headers=api_class.HeaderImpersonate)
        elif str(method).upper() == "GET":
            c_primary_data = requests.get(uri, headers=api_class.HeaderImpersonate)
        else:
            return print("The METHOD needs to be either GET or POST")
    return c_primary_data

API_connection = API()
API_connection.impersonate_user(user_email='admin@ascholz.io')

MSG = {
    'to': "all@ascholz.io",
    'cc': "",
    'bcc': "",
    'from': "admin@ascholz.io",
    'replyTo': "admin@ascholz.io",
    'subject': "!!! Test MSG !!!",
    'messageHTML': 'Test Message Here',
    'ownerEmailAddress': "",
    'actions': {},
    'readReceiptRequested': False,
    'deliveryReceiptRequested': False,
    'priority': 1,
    'inlineToRemove': [],
    'selectedFrom': ":admin@ascholz.io"
}

results = cImpersonate(API_connection, 'POST', 'api/v1/mail/message-put', json_body=MSG)
results.status_code

ASP (Converted from Powershell by ChatGPT This has NOT been tested)

<%
Option Explicit

Dim API, APIHost, HTTPS, authUserName, authPassword
APIHost = "localhost"
HTTPS = False
authUserName = "admin"
authPassword = "admin"

' Initialize API object
Set API = CreateObject("Scripting.Dictionary")
API.Add "authUserName", authUserName
API.Add "authPassword", authPassword
API.Add "Method", "POST"
API.Add "ContentType", "application/json"
API.Add "URI", "http" & IIf(HTTPS, "s", "") & "://" & APIHost & "/"
API.Add "ShowAuthData", True
API.Add "rootAPIURI", "http" & IIf(HTTPS, "s", "") & "://" & APIHost & "/Documentation/api#/reference/SmarterMail.Web.Controllers."

' Function for primary authentication
Function PrimaryAuth(twoFactorCode, retrieveAutoLoginToken)
    Dim authUri, authBody, authToken, xhr
    authUri = "http" & IIf(HTTPS, "s", "") & "://" & APIHost & "/api/v1/auth/authenticate-user"
    
    authBody = "{""username"":""" & API.Item("authUserName") & """, ""password"":""" & API.Item("authPassword") & """, ""language"":null, ""twoFactorCode"":""" & twoFactorCode & """, ""retrieveAutoLoginToken"":" & LCase(CStr(retrieveAutoLoginToken)) & "}"
    
    Set xhr = Server.CreateObject("MSXML2.ServerXMLHTTP")
    xhr.Open "POST", authUri, False
    xhr.setRequestHeader "Content-Type", API.Item("ContentType")
    xhr.Send authBody
    
    If xhr.Status = 200 Then
        Dim response
        response = xhr.responseText
        ' Process and extract tokens from response
        ' For brevity, assuming JSON is parsed to extract accessToken, refreshToken, and accessTokenExpiration
        API.Remove "Headers"
        API.Add "Headers", "Authorization: Bearer " & ExtractJsonValue(response, "accessToken")
    Else
        Response.Write "Failed to authenticate. Status: " & xhr.Status
    End If
    Set xhr = Nothing
End Function

' Function to send an email message
Function SendEmailMessage(toEmail, fromEmail, subject, messageHTML)
    Dim msgUri, msgBody, xhr
    msgUri = API.Item("URI") & "api/v1/mail/message-put"
    
    msgBody = "{""to"":""" & toEmail & """, ""from"":""" & fromEmail & """, ""subject"":""" & subject & """, ""messageHTML"":""" & messageHTML & """}"
    
    Set xhr = Server.CreateObject("MSXML2.ServerXMLHTTP")
    xhr.Open "POST", msgUri, False
    xhr.setRequestHeader "Content-Type", API.Item("ContentType")
    xhr.setRequestHeader "Authorization", API.Item("Headers")
    xhr.Send msgBody
    
    If xhr.Status = 200 Then
        Response.Write "Email sent successfully.<br>"
    Else
        Response.Write "Failed to send email. Status: " & xhr.Status & "<br>"
    End If
    Set xhr = Nothing
End Function

' Function to extract JSON values (simplified JSON parsing)
Function ExtractJsonValue(jsonString, key)
    Dim posKey, posValue, endPos
    posKey = InStr(jsonString, """" & key & """")
    If posKey > 0 Then
        posValue = InStr(posKey, jsonString, ":") + 1
        endPos = InStr(posValue, jsonString, ",")
        If endPos = 0 Then endPos = InStr(posValue, jsonString, "}")
        ExtractJsonValue = Mid(jsonString, posValue, endPos - posValue)
        ExtractJsonValue = Replace(Replace(ExtractJsonValue, """", ""), "}", "")
    Else
        ExtractJsonValue = ""
    End If
End Function

' Authenticate and send messages in a loop
Call PrimaryAuth("", "true")

Dim i
For i = 0 To 2
    Dim toEmail, fromEmail, subject, messageHTML
    toEmail = "all@ascholz.io"
    fromEmail = "admin@ascholz.io"
    subject = "!!! Test MSG " & Time
    messageHTML = "Test Message Here"
    
    Call SendEmailMessage(toEmail, fromEmail, subject, messageHTML)
    ' Sleep for 1 second between requests
    Call Sleep(1)
Next

' Sleep function
Sub Sleep(seconds)
    Dim objShell
    Set objShell = Server.CreateObject("WScript.Shell")
    objShell.Run "cmd /c ping -n " & seconds + 1 & " 127.0.0.1>nul", 0, True
    Set objShell = Nothing
End Sub
%>

Tony Scholz System/Network Administrator SmarterTools Inc. www.smartertools.com

Reply to Thread