├── .gitignore ├── AmazonEmailMessage.pas ├── AmazonEmailService.pas ├── AmazonEmailServiceConfiguration.pas ├── AmazonEmailServiceRegions.pas ├── AmazonEmailServiceRequests.pas ├── BuildQueryParameters.pas ├── EncodeQueryParams.pas ├── LICENSE ├── PopulateResponseInfo.pas ├── PrepareRequestSignature.pas ├── README.md └── tests ├── AmazonEmailService.groupproj ├── AmazonEmailServiceConfigurationTests.pas ├── AmazonEmailServiceRegionsTests.pas ├── AmazonEmailServiceRequestsTests.pas ├── AmazonEmailServiceTests.dpr ├── AmazonEmailServiceTests.dproj ├── BuildQueryParametersTests.pas ├── DunitXTestRunner.pas ├── EncodeQueryParamsTests.pas ├── PopulateResponseInfoTests.pas ├── PrepareRequestSignatureTests.pas ├── README.md ├── libeay32.dll └── ssleay32.dll /.gitignore: -------------------------------------------------------------------------------- 1 | # Uncomment these types if you want even more clean repository. But be careful. 2 | # It can make harm to an existing project source. Read explanations below. 3 | # 4 | # Resource files are binaries containing manifest, project icon and version info. 5 | # They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files. 6 | *.res 7 | # 8 | # Type library file (binary). In old Delphi versions it should be stored. 9 | # Since Delphi 2009 it is produced from .ridl file and can safely be ignored. 10 | #*.tlb 11 | # 12 | # Diagram Portfolio file. Used by the diagram editor up to Delphi 7. 13 | # Uncomment this if you are not using diagrams or use newer Delphi version. 14 | #*.ddp 15 | # 16 | # Visual LiveBindings file. Added in Delphi XE2. 17 | # Uncomment this if you are not using LiveBindings Designer. 18 | #*.vlb 19 | # 20 | # Deployment Manager configuration file for your project. Added in Delphi XE2. 21 | # Uncomment this if it is not mobile development and you do not use remote debug feature. 22 | #*.deployproj 23 | # 24 | 25 | # Delphi compiler-generated binaries (safe to delete) 26 | *.exe 27 | *.dll 28 | *.bpl 29 | *.bpi 30 | *.dcp 31 | *.so 32 | *.apk 33 | *.drc 34 | *.map 35 | *.dres 36 | *.rsm 37 | *.tds 38 | *.dcu 39 | 40 | # Delphi autogenerated files (duplicated info) 41 | *.cfg 42 | *Resource.rc 43 | *.stat 44 | 45 | # Delphi local files (user-specific info) 46 | *.local 47 | *.identcache 48 | *.projdata 49 | *.tvsconfig 50 | *.dsk 51 | *.skincfg 52 | 53 | # Delphi history and backups 54 | __history/ 55 | *.~* 56 | 57 | #DunitX autogenerated files 58 | dunitx-results.xml 59 | 60 | #Dcov generated files 61 | tests/coverage 62 | Delphi-Code-Coverage-*.log 63 | -------------------------------------------------------------------------------- /AmazonEmailMessage.pas: -------------------------------------------------------------------------------- 1 | unit AmazonEmailMessage; 2 | 3 | interface 4 | 5 | type 6 | TBodyType = (btHTML, btText); 7 | 8 | TEmailMessage = record 9 | FromName: string; 10 | FromAddress: string; 11 | Recipients: TArray; 12 | ReplyTo: TArray; 13 | CC: TArray; 14 | BCC: TArray; 15 | Subject: string; 16 | Body: string; 17 | BodyType: TBodyType; 18 | end; 19 | 20 | implementation 21 | 22 | end. 23 | -------------------------------------------------------------------------------- /AmazonEmailService.pas: -------------------------------------------------------------------------------- 1 | unit AmazonEmailService; 2 | 3 | interface 4 | 5 | uses 6 | IPPeerAPI, 7 | Data.Cloud.CloudAPI, 8 | IPPeerClient, 9 | System.Classes, 10 | AmazonEmailMessage; 11 | 12 | type 13 | TAmazonEmailService = class 14 | private 15 | FRegion: string; 16 | FAWSAccessKey: string; 17 | FAWSSecretKey: string; 18 | procedure IssueRequest(const QueryParameters: TStringStream; out Response: TCloudResponseInfo); 19 | procedure PopulateResponseInfo(const ResponseInfo: TCloudResponseInfo; const Peer: IIPHTTP); overload; 20 | procedure PopulateResponseInfo(const ResponseInfo: TCloudResponseInfo; const E: EIPHTTPProtocolExceptionPeer); overload; 21 | function BuildQueryParameters(const EmailMessage: TEmailMessage): TStringStream; 22 | procedure PrepareRequest(const Peer: IIPHTTP); 23 | public 24 | constructor Create(const Region, AWSAccessKey, AWSSecretKey: string); overload; 25 | constructor Create; overload; 26 | function Send(const EmailMessage: TEmailMessage; out Response: TCloudResponseInfo): Boolean; 27 | 28 | class function SendMail(const EmailMessage: TEmailMessage): Boolean; overload; 29 | class function SendMail(const EmailMessage: TEmailMessage; out Response: TCloudResponseInfo): Boolean; overload; 30 | end; 31 | 32 | implementation 33 | 34 | uses 35 | ActiveX, 36 | DateUtils, 37 | SysUtils, 38 | AmazonEmailServiceConfiguration, 39 | AmazonEmailServiceRegions, 40 | AmazonEmailServiceRequests, 41 | BuildQueryParameters, 42 | PopulateResponseInfo; 43 | 44 | constructor TAmazonEmailService.Create(const Region, AWSAccessKey, AWSSecretKey: string); 45 | begin 46 | FRegion := TAmazonEmailServiceRegions.FormatServiceURL(Region); 47 | FAWSAccessKey := AWSAccessKey; 48 | FAWSSecretKey := AWSSecretKey; 49 | end; 50 | 51 | constructor TAmazonEmailService.Create; 52 | var 53 | Configuration: TAmazonEmailServiceConfiguration; 54 | Region: string; 55 | AccessKey: string; 56 | SecretKey: string; 57 | begin 58 | Configuration := TAmazonEmailServiceConfiguration.Create; 59 | try 60 | Configuration.GetFromEnvironment(Region, AccessKey, SecretKey); 61 | finally 62 | Configuration.Free; 63 | end; 64 | 65 | Create(Region, AccessKey, SecretKey); 66 | end; 67 | 68 | procedure TAmazonEmailService.IssueRequest(const QueryParameters: TStringStream; out Response: TCloudResponseInfo); 69 | var 70 | Peer: IIPHTTP; 71 | begin 72 | Peer := PeerFactory.CreatePeer('', IIPHTTP, nil) as IIPHTTP; 73 | try 74 | Peer.IOHandler := PeerFactory.CreatePeer('', IIPSSLIOHandlerSocketOpenSSL, nil) as IIPSSLIOHandlerSocketOpenSSL; 75 | 76 | PrepareRequest(Peer); 77 | try 78 | Peer.DoPost(FRegion, QueryParameters); 79 | PopulateResponseInfo(Response, Peer); 80 | except 81 | on E: EIPHTTPProtocolExceptionPeer do 82 | PopulateResponseInfo(Response, E) 83 | else 84 | raise; 85 | end; 86 | finally 87 | if Assigned(Peer) then 88 | begin 89 | Peer.FreeIOHandler; 90 | Peer := nil; 91 | end; 92 | end; 93 | end; 94 | 95 | procedure TAmazonEmailService.PopulateResponseInfo(const ResponseInfo: TCloudResponseInfo; const Peer: IIPHTTP); 96 | var 97 | PopulateResponseInfo: TPopulateResponseInfo; 98 | begin 99 | PopulateResponseInfo := TPopulateResponseInfo.Create; 100 | try 101 | PopulateResponseInfo.FromPeer(ResponseInfo, Peer); 102 | finally 103 | PopulateResponseInfo.Free; 104 | end; 105 | end; 106 | 107 | procedure TAmazonEmailService.PopulateResponseInfo(const ResponseInfo: TCloudResponseInfo; const E: EIPHTTPProtocolExceptionPeer); 108 | var 109 | PopulateResponseInfo: TPopulateResponseInfo; 110 | begin 111 | PopulateResponseInfo := TPopulateResponseInfo.Create; 112 | try 113 | PopulateResponseInfo.FromExceptionPeer(ResponseInfo, E); 114 | finally 115 | PopulateResponseInfo.Free; 116 | end; 117 | end; 118 | 119 | procedure TAmazonEmailService.PrepareRequest(const Peer: IIPHTTP); 120 | var 121 | AmazonEmailServiceRequests: TAmazonEmailServiceRequests; 122 | begin 123 | AmazonEmailServiceRequests := TAmazonEmailServiceRequests.Create(FAWSAccessKey, FAWSSecretKey); 124 | try 125 | AmazonEmailServiceRequests.PrepareRequest(Peer); 126 | finally 127 | AmazonEmailServiceRequests.Free; 128 | end; 129 | end; 130 | 131 | function TAmazonEmailService.BuildQueryParameters(const EmailMessage: TEmailMessage): TStringStream; 132 | begin 133 | Result := TBuildQueryParameters.GetQueryParams(EmailMessage); 134 | end; 135 | 136 | function TAmazonEmailService.Send(const EmailMessage: TEmailMessage; out Response: TCloudResponseInfo): Boolean; 137 | var 138 | QueryParameters: TStringStream; 139 | begin 140 | CoInitialize(nil); 141 | try 142 | Response := TCloudResponseInfo.Create; 143 | 144 | QueryParameters := BuildQueryParameters(EmailMessage); 145 | try 146 | IssueRequest(QueryParameters, Response); 147 | Result := (Response <> nil) and (Response.StatusCode = 200); 148 | finally 149 | if Assigned(QueryParameters) then 150 | QueryParameters.Free; 151 | end; 152 | finally 153 | CoUninitialize; 154 | end; 155 | end; 156 | 157 | class function TAmazonEmailService.SendMail(const EmailMessage: TEmailMessage): Boolean; 158 | var 159 | Response: TCloudResponseInfo; 160 | begin 161 | try 162 | Result := TAmazonEmailService.SendMail(EmailMessage, Response); 163 | finally 164 | Response.Free; 165 | end; 166 | end; 167 | 168 | class function TAmazonEmailService.SendMail(const EmailMessage: TEmailMessage; 169 | out Response: TCloudResponseInfo): Boolean; 170 | var 171 | AmazonEmailService: TAmazonEmailService; 172 | begin 173 | AmazonEmailService := TAmazonEmailService.Create; 174 | try 175 | Result := AmazonEmailService.Send(EmailMessage, Response); 176 | finally 177 | AmazonEmailService.Free; 178 | end; 179 | end; 180 | 181 | end. 182 | -------------------------------------------------------------------------------- /AmazonEmailServiceConfiguration.pas: -------------------------------------------------------------------------------- 1 | unit AmazonEmailServiceConfiguration; 2 | 3 | interface 4 | 5 | type 6 | TAmazonEmailServiceConfiguration = class 7 | private 8 | procedure AssertValue(const VarValue, Description: string); 9 | public 10 | procedure GetFromEnvironment(var Region, AccessKey, SecretKey: string); 11 | end; 12 | 13 | const 14 | AWS_REGION = 'AWS_REGION'; 15 | AWS_ACCESS_KEY_ID = 'AWS_ACCESS_KEY_ID'; 16 | AWS_SECRET_ACCESS_KEY = 'AWS_SECRET_ACCESS_KEY'; 17 | 18 | implementation 19 | 20 | uses 21 | SysUtils, 22 | Windows; 23 | 24 | procedure TAmazonEmailServiceConfiguration.AssertValue(const VarValue, Description: string); 25 | begin 26 | if VarValue.Trim = '' then 27 | raise EArgumentNilException.Create(Format('No Amazon %s provided.', [Description])); 28 | end; 29 | 30 | procedure TAmazonEmailServiceConfiguration.GetFromEnvironment(var Region, AccessKey, SecretKey: string); 31 | begin 32 | Region := GetEnvironmentVariable(AWS_REGION); 33 | AssertValue(Region, 'Region'); 34 | 35 | AccessKey := GetEnvironmentVariable(AWS_ACCESS_KEY_ID); 36 | AssertValue(AccessKey, 'Access Key'); 37 | 38 | SecretKey := GetEnvironmentVariable(AWS_SECRET_ACCESS_KEY); 39 | AssertValue(SecretKey, 'Secret Access Key'); 40 | end; 41 | 42 | end. 43 | -------------------------------------------------------------------------------- /AmazonEmailServiceRegions.pas: -------------------------------------------------------------------------------- 1 | unit AmazonEmailServiceRegions; 2 | 3 | interface 4 | 5 | type 6 | TAmazonEmailServiceRegions = class 7 | public 8 | class function FormatServiceURL(const Region: string): string; 9 | end; 10 | 11 | implementation 12 | 13 | uses 14 | SysUtils; 15 | 16 | class function TAmazonEmailServiceRegions.FormatServiceURL(const Region: string): string; 17 | const 18 | Protocol = 'https'; 19 | begin 20 | Result := Format('%s://email.%s.amazonaws.com', [Protocol, Region]); 21 | end; 22 | 23 | end. 24 | -------------------------------------------------------------------------------- /AmazonEmailServiceRequests.pas: -------------------------------------------------------------------------------- 1 | unit AmazonEmailServiceRequests; 2 | 3 | interface 4 | 5 | uses 6 | IPPeerAPI; 7 | 8 | type 9 | TAmazonEmailServiceRequests = class 10 | private 11 | FAWSAccessKey: string; 12 | FAWSSecretKey: string; 13 | FDateRequest: TDateTime; 14 | function GetCurrentDate: string; 15 | function GetSignature(const StringToSign: string): string; 16 | public 17 | constructor Create(const AWSAccessKey, AWSSecretKey: string); 18 | procedure PrepareRequest(const Peer: IIPHTTP); 19 | property DateRequest: TDateTime read FDateRequest; 20 | end; 21 | 22 | implementation 23 | 24 | uses 25 | DateUtils, 26 | SysUtils, 27 | PrepareRequestSignature; 28 | 29 | constructor TAmazonEmailServiceRequests.Create(const AWSAccessKey, AWSSecretKey: string); 30 | begin 31 | FAWSAccessKey := AWSAccessKey; 32 | FAWSSecretKey := AWSSecretKey; 33 | end; 34 | 35 | function TAmazonEmailServiceRequests.GetCurrentDate: string; 36 | const 37 | FORMAT_HTTP_DATE = 'ddd, dd mmm yyyy hh:nn:ss "GMT"'; 38 | begin 39 | FDateRequest := Now; 40 | Result := FormatDateTime(FORMAT_HTTP_DATE, TTimeZone.Local.ToUniversalTime(FDateRequest), TFormatSettings.Create('en-US')); 41 | end; 42 | 43 | function TAmazonEmailServiceRequests.GetSignature(const StringToSign: string): string; 44 | var 45 | PrepareRequestSignature: TPrepareRequestSignature; 46 | begin 47 | PrepareRequestSignature := TPrepareRequestSignature.Create(FAWSAccessKey, FAWSSecretKey); 48 | try 49 | Result := PrepareRequestSignature.GetSignature(StringToSign); 50 | finally 51 | PrepareRequestSignature.Free; 52 | end; 53 | end; 54 | 55 | procedure TAmazonEmailServiceRequests.PrepareRequest(const Peer: IIPHTTP); 56 | var 57 | CurrentTime: string; 58 | AuthorizationHeader: string; 59 | begin 60 | Peer.GetRequest.ContentType := 'application/x-www-form-urlencoded'; 61 | Peer.GetRequest.ContentLength := 230; 62 | 63 | CurrentTime := GetCurrentDate; 64 | Peer.GetRequest.CustomHeaders.AddValue('Date', CurrentTime); 65 | 66 | AuthorizationHeader := Format('AWS3-HTTPS AWSAccessKeyId=%s, Algorithm=HmacSHA256, Signature=%s', 67 | [FAWSAccessKey, GetSignature(CurrentTime)]); 68 | Peer.GetRequest.CustomHeaders.AddValue('X-Amzn-Authorization', AuthorizationHeader); 69 | end; 70 | 71 | end. 72 | -------------------------------------------------------------------------------- /BuildQueryParameters.pas: -------------------------------------------------------------------------------- 1 | unit BuildQueryParameters; 2 | 3 | interface 4 | 5 | uses 6 | AmazonEmailMessage, 7 | AmazonEmailService, 8 | System.Classes; 9 | 10 | type 11 | TBuildQueryParameters = class 12 | public 13 | class function GetQueryParams(const EmailMessage: TEmailMessage): TStringStream; 14 | end; 15 | 16 | implementation 17 | 18 | uses 19 | SysUtils, 20 | EncodeQueryParams, 21 | System.NetEncoding; 22 | 23 | class function TBuildQueryParameters.GetQueryParams(const EmailMessage: TEmailMessage): TStringStream; 24 | const 25 | Action = 'SendEmail'; 26 | var 27 | I: Integer; 28 | BodyType, Source: string; 29 | begin 30 | Result := TStringStream.Create(EmptyStr, TEncoding.UTF8); 31 | try 32 | Result.WriteString('Action=' + ACTION); 33 | 34 | Source := Format('=?utf-8?B?%s?= <%s>', [TNetEncoding.Base64.Encode(EmailMessage.FromName), EmailMessage.FromAddress]); 35 | Result.WriteString(Format('&Source=%s', [TEncodeQueryParams.Encode(Source)])); 36 | 37 | for I := Low(EmailMessage.Recipients) to High(EmailMessage.Recipients) do 38 | Result.WriteString(Format('&Destination.ToAddresses.member.%d=%s', [I+1, TEncodeQueryParams.Encode(EmailMessage.Recipients[I])])); 39 | 40 | for I := Low(EmailMessage.ReplyTo) to High(EmailMessage.ReplyTo) do 41 | Result.WriteString(Format('&ReplyToAddresses.member.%d=%s', [I+1, TEncodeQueryParams.Encode(EmailMessage.ReplyTo[I])])); 42 | 43 | Result.WriteString('&Message.Subject.Charset=UTF-8'); 44 | Result.WriteString(Format('&Message.Subject.Data=%s', [TEncodeQueryParams.Encode(EmailMessage.Subject)])); 45 | 46 | if EmailMessage.BodyType = btHTML then 47 | BodyType := 'Html' 48 | else 49 | BodyType := 'Text'; 50 | Result.WriteString(Format('&Message.Body.%s.Charset=UTF-8', [BodyType])); 51 | Result.WriteString(Format('&Message.Body.%s.Data=%s', [BodyType, TEncodeQueryParams.Encode(EmailMessage.Body)])); 52 | except 53 | Result.Free; 54 | raise 55 | end; 56 | end; 57 | 58 | end. 59 | -------------------------------------------------------------------------------- /EncodeQueryParams.pas: -------------------------------------------------------------------------------- 1 | unit EncodeQueryParams; 2 | 3 | interface 4 | 5 | type 6 | TEncodeQueryParams = class 7 | public 8 | class function Encode(const Str: string): string; overload; static; 9 | class function Encode(const Str: UTF8String): string; overload; static; 10 | end; 11 | 12 | implementation 13 | 14 | uses 15 | SysUtils; 16 | 17 | class function TEncodeQueryParams.Encode(const Str: string): string; 18 | begin 19 | Result := Encode(UTF8Encode(Str)); 20 | end; 21 | 22 | class function TEncodeQueryParams.Encode(const Str: UTF8String): string; 23 | const 24 | SAFE_CHARS = ['A'..'Z', 'a'..'z', '0', '1'..'9', '-', '_', '~', '.']; 25 | var 26 | Ch: AnsiChar; 27 | begin 28 | Result := ''; 29 | for Ch in Str do 30 | if not CharInSet(Ch, SAFE_CHARS) then 31 | Result := Result + '%'+ IntToHex(Ord(Ch), 2) 32 | else 33 | Result := Result + WideChar(Ch); 34 | end; 35 | 36 | end. 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Monde Sistemas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /PopulateResponseInfo.pas: -------------------------------------------------------------------------------- 1 | unit PopulateResponseInfo; 2 | 3 | interface 4 | 5 | uses 6 | Data.Cloud.CloudAPI, 7 | IPPeerAPI; 8 | 9 | type 10 | TPopulateResponseInfo = class 11 | public 12 | procedure FromPeer(const ResponseInfo: TCloudResponseInfo; const Peer: IIPHTTP); overload; 13 | procedure FromExceptionPeer(const ResponseInfo: TCloudResponseInfo; const ExceptionPeer: EIPHTTPProtocolExceptionPeer); overload; 14 | end; 15 | 16 | implementation 17 | 18 | uses 19 | Classes, 20 | SysUtils, 21 | XmlIntf, 22 | XmlDoc; 23 | 24 | { TPopulateResponseInfo } 25 | 26 | procedure TPopulateResponseInfo.FromExceptionPeer(const ResponseInfo: TCloudResponseInfo; 27 | const ExceptionPeer: EIPHTTPProtocolExceptionPeer); 28 | const 29 | NODE_ERRORS = 'Errors'; 30 | NODE_ERROR = 'Error'; 31 | NODE_ERROR_MESSAGE = 'Message'; 32 | NODE_ERROR_CODE = 'Code'; 33 | NODE_REQUEST_ID = 'RequestId'; 34 | NODE_RESPONSE_METADATA = 'ResponseMetadata'; 35 | var 36 | XmlDoc: IXMLDocument; 37 | IsErrors: Boolean; 38 | Aux, ErrorNode, MessageNode: IXMLNode; 39 | ErrorCode, ErrorMsg: string; 40 | begin 41 | if ExceptionPeer.ErrorMessage.IsEmpty then 42 | Exit; 43 | 44 | XmlDoc := TXMLDocument.Create(nil); 45 | XmlDoc.LoadFromXML(ExceptionPeer.ErrorMessage); 46 | 47 | ResponseInfo.StatusCode := ExceptionPeer.ErrorCode; 48 | ResponseInfo.StatusMessage := ExceptionPeer.Message; 49 | 50 | IsErrors := AnsiPos(Format('<%s>', [NODE_ERRORS]), ExceptionPeer.ErrorMessage) > 0; 51 | 52 | //Parse the error and update the ResponseInfo StatusMessage 53 | if IsErrors or (AnsiPos(' 0) then 54 | begin 55 | //Amazon has different formats for returning errors as XML 56 | if IsErrors then 57 | begin 58 | ErrorNode := xmlDoc.DocumentElement.ChildNodes.FindNode(NODE_ERRORS); 59 | ErrorNode := ErrorNode.ChildNodes.FindNode(NODE_ERROR); 60 | end 61 | else 62 | ErrorNode := xmlDoc.DocumentElement.ChildNodes.FindNode(NODE_ERROR); 63 | 64 | if (ErrorNode <> nil) and (ErrorNode.HasChildNodes) then 65 | begin 66 | MessageNode := ErrorNode.ChildNodes.FindNode(NODE_ERROR_MESSAGE); 67 | 68 | if (MessageNode <> nil) then 69 | ErrorMsg := MessageNode.Text; 70 | 71 | if ErrorMsg <> EmptyStr then 72 | begin 73 | //Populate the error code 74 | Aux := ErrorNode.ChildNodes.FindNode(NODE_ERROR_CODE); 75 | if (Aux <> nil) then 76 | ErrorCode := Aux.Text; 77 | ResponseInfo.StatusMessage := Format('%s - %s (%s)', [ResponseInfo.StatusMessage, ErrorMsg, ErrorCode]); 78 | end; 79 | end; 80 | 81 | //populate the RequestId, which is structured differently than if this is not an error ResponseInfo 82 | Aux := xmlDoc.DocumentElement.ChildNodes.FindNode(NODE_REQUEST_ID); 83 | if (Aux <> nil) and (Aux.IsTextElement) then 84 | begin 85 | if not Assigned(ResponseInfo.Headers) then 86 | ResponseInfo.Headers := TStringList.Create; 87 | ResponseInfo.Headers.Values[NODE_REQUEST_ID] := Aux.Text; 88 | end; 89 | end 90 | //Otherwise, it isn't an error, but try to pase the RequestId anyway. 91 | else 92 | begin 93 | Aux := xmlDoc.DocumentElement.ChildNodes.FindNode(NODE_RESPONSE_METADATA); 94 | if Aux <> nil then 95 | begin 96 | Aux := Aux.ChildNodes.FindNode(NODE_REQUEST_ID); 97 | if Aux <> nil then 98 | if not Assigned(ResponseInfo.Headers) then 99 | ResponseInfo.Headers := TStringList.Create; 100 | ResponseInfo.Headers.Values[NODE_REQUEST_ID] := Aux.Text; 101 | end; 102 | end; 103 | end; 104 | 105 | procedure TPopulateResponseInfo.FromPeer(const ResponseInfo: TCloudResponseInfo; const Peer: IIPHTTP); 106 | begin 107 | ResponseInfo.StatusCode := Peer.ResponseCode; 108 | ResponseInfo.StatusMessage := Peer.ResponseText; 109 | end; 110 | 111 | end. 112 | -------------------------------------------------------------------------------- /PrepareRequestSignature.pas: -------------------------------------------------------------------------------- 1 | unit PrepareRequestSignature; 2 | 3 | interface 4 | 5 | uses 6 | Data.Cloud.CloudAPI; 7 | 8 | type 9 | TPrepareRequestSignature = class 10 | private 11 | FConnectionInfo: TCloudConnectionInfo; 12 | public 13 | constructor Create(AWSAccessKey, AWSSecretKey: string); 14 | destructor Destroy; override; 15 | 16 | function GetSignature(const StringToSign: string): string; 17 | end; 18 | 19 | implementation 20 | 21 | uses 22 | Data.Cloud.AmazonAPI; 23 | 24 | constructor TPrepareRequestSignature.Create(AWSAccessKey, AWSSecretKey: string); 25 | begin 26 | FConnectionInfo := TCloudConnectionInfo.Create(nil); 27 | FConnectionInfo.AccountName := AWSAccessKey; 28 | FConnectionInfo.AccountKey := AWSSecretKey; 29 | end; 30 | 31 | destructor TPrepareRequestSignature.Destroy; 32 | begin 33 | if Assigned(FConnectionInfo) then 34 | FConnectionInfo.Free; 35 | inherited; 36 | end; 37 | 38 | function TPrepareRequestSignature.GetSignature(const StringToSign: string): string; 39 | var 40 | AmazonEmailAuthentication: TAmazonAuthentication; 41 | begin 42 | AmazonEmailAuthentication := TAmazonAuthentication.Create(FConnectionInfo); 43 | try 44 | Result := AmazonEmailAuthentication.BuildAuthorizationString(StringToSign); 45 | finally 46 | AmazonEmailAuthentication.Free; 47 | end; 48 | end; 49 | 50 | end. 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # delphi-aws-ses 2 | 3 | Amazon Simple Email Service ([AWS SES](http://aws.amazon.com/ses)) library for Delphi applications. 4 | 5 | ## Using 6 | 7 | If you call the class method `TAmazonEmailService.SendMail` the library will look for the following environment variables: `AWS_REGION`, `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`. 8 | 9 | ```Delphi 10 | var 11 | EmailMessage: TEmailMessage; 12 | begin 13 | EmailMessage.Recipients := TArray.Create('email@example.com', 'email2@example.com'); 14 | EmailMessage.FromName := 'John Doe' 15 | EmailMessage.FromAddress := 'email@mail.com'; 16 | EmailMessage.Subject := 'This is the subject line with HTML.'; 17 | EmailMessage.Body := 'Hello. I hope you are having a good day.'; 18 | 19 | TAmazonEmailService.SendMail(EmailMessage); 20 | end; 21 | ``` 22 | 23 | 24 | You may also manually instantiate the class and pass parameters to the constructor method: 25 | 26 | ```Delphi 27 | var 28 | AmazonEmailService: TAmazonEmailService; 29 | begin 30 | // ... 31 | AmazonEmailService := TAmazonEmailService.Create(AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY); 32 | try 33 | AmazonEmailService.Send(EmailMessage); 34 | finally 35 | AmazonEmailService.Free; 36 | end; 37 | 38 | ``` 39 | 40 | ### Body Type 41 | 42 | **Declaration:** `TBodyType = (btHTML, btText);` 43 | 44 | The email body can be sent in the following formats: 45 | 46 | * HTML - If the recipient's email client can interpret HTML, the body can include formatted text and hyperlinks 47 | * Plain text - If the recipient's email client is text-based, the body must not contain any nonprintable characters. 48 | 49 | By default, the email will have HTML-enabled. To use text-based email will need you to set the EmailBody parameter values to `btText`. 50 | 51 | ### Response Info 52 | 53 | It's also possible to get the response information, setting as a parameter to the SendMail method a variable of type TCloudResponseInfo. 54 | 55 | ```Delphi 56 | var 57 | ResponseInfo: TCloudResponseInfo; 58 | begin 59 | // ... 60 | TAmazonEmailService.SendMail(EmailMessage, ResponseInfo); 61 | // ... 62 | ``` 63 | 64 | For example, if the email was sent successfully will be returned: 65 | 66 | ```Delphi 67 | Response.StatusCode = 200 68 | Response.StatusMessage = 'HTTP/1.1 200 OK' 69 | ``` 70 | 71 | ## Executing the tests 72 | 73 | You need DUnitX do run the tests. 74 | 75 | * Clone the [DUnitX](https://github.com/VSoftTechnologies/DUnitX/) repository locally 76 | * Define a `DUNITX` environment variable, pointing to the DUnitX clone directory. 77 | 78 | ## Contributing 79 | 80 | If you got something that's worth including into the project please [submit a Pull Request](https://github.com/monde-sistemas/delphi-aws-ses/pulls) or [open an issue](https://github.com/monde-sistemas/delphi-aws-ses/issues) for further discussion. 81 | 82 | ## License 83 | 84 | This software is open source, licensed under the The MIT License (MIT). See [LICENSE](https://github.com/monde-sistemas/delphi-aws-ses/blob/master/LICENSE) for details. 85 | -------------------------------------------------------------------------------- /tests/AmazonEmailService.groupproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | {23D32098-FE48-49B3-AF28-4F9AA4AE61D8} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Default.Personality.12 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/AmazonEmailServiceConfigurationTests.pas: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monde-sistemas/delphi-aws-ses/98b37371a06a73024fdde234050f024d5eee6ea2/tests/AmazonEmailServiceConfigurationTests.pas -------------------------------------------------------------------------------- /tests/AmazonEmailServiceRegionsTests.pas: -------------------------------------------------------------------------------- 1 | unit AmazonEmailServiceRegionsTests; 2 | 3 | interface 4 | 5 | uses 6 | DUnitX.TestFramework, 7 | AmazonEmailServiceRegions; 8 | 9 | type 10 | [TestFixture] 11 | TAmazonEmailServiceRegionsTests = class 12 | published 13 | procedure FormatServiceURL_WithRegion_ReturnServiceURL; 14 | end; 15 | 16 | implementation 17 | 18 | uses 19 | SysUtils; 20 | 21 | procedure TAmazonEmailServiceRegionsTests.FormatServiceURL_WithRegion_ReturnServiceURL; 22 | var 23 | ServiceURL: string; 24 | begin 25 | ServiceURL := TAmazonEmailServiceRegions.FormatServiceURL('eu-west-1'); 26 | Assert.AreEqual('https://email.eu-west-1.amazonaws.com', ServiceURL); 27 | end; 28 | 29 | initialization 30 | TDUnitX.RegisterTestFixture(TAmazonEmailServiceRegionsTests); 31 | 32 | end. 33 | -------------------------------------------------------------------------------- /tests/AmazonEmailServiceRequestsTests.pas: -------------------------------------------------------------------------------- 1 | unit AmazonEmailServiceRequestsTests; 2 | 3 | interface 4 | 5 | uses 6 | DUnitX.TestFramework, 7 | AmazonEmailServiceRequests; 8 | 9 | type 10 | [TestFixture] 11 | TAmazonEmailServiceRequestsTests = class 12 | strict private 13 | FAmazonEmailServiceRequests: TAmazonEmailServiceRequests; 14 | function GetCurrentDate(const ADateTime: TDateTime): string; 15 | public 16 | [Setup] 17 | procedure SetUp; 18 | [TearDown] 19 | procedure TearDown; 20 | published 21 | procedure PrepareRequest_Peer_AssignRequests; 22 | end; 23 | 24 | implementation 25 | 26 | uses 27 | DateUtils, 28 | SysUtils, 29 | IPPeerAPI; 30 | 31 | function TAmazonEmailServiceRequestsTests.GetCurrentDate(const ADateTime: TDateTime): string; 32 | const 33 | FORMAT_HTTP_DATE = 'ddd, dd mmm yyyy hh:nn:ss "GMT"'; 34 | begin 35 | Result := FormatDateTime(FORMAT_HTTP_DATE, TTimeZone.Local.ToUniversalTime(ADateTime), TFormatSettings.Create('en-US')); 36 | end; 37 | 38 | procedure TAmazonEmailServiceRequestsTests.PrepareRequest_Peer_AssignRequests; 39 | var 40 | Peer: IIPHTTP; 41 | begin 42 | Peer := PeerFactory.CreatePeer('', IIPHTTP, nil) as IIPHTTP; 43 | try 44 | FAmazonEmailServiceRequests.PrepareRequest(Peer); 45 | 46 | Assert.AreEqual('application/x-www-form-urlencoded', Peer.GetRequest.ContentType); 47 | Assert.AreEqual(230, Peer.GetRequest.ContentLength); 48 | Assert.AreEqual(GetCurrentDate(FAmazonEmailServiceRequests.DateRequest), Peer.GetRequest.CustomHeaders.Values['Date']); 49 | Assert.AreEqual('AWS3-HTTPS AWSAccessKeyId=AKIAJQF6P3QUHRSJPZCA, Algorithm=HmacSHA256, Signature=', Copy(Peer.GetRequest.CustomHeaders.Values['X-Amzn-Authorization'], 1, 80)); 50 | Assert.AreEqual(124, Length(Peer.GetRequest.CustomHeaders.Values['X-Amzn-Authorization'])); 51 | finally 52 | Peer := nil; 53 | end; 54 | end; 55 | 56 | procedure TAmazonEmailServiceRequestsTests.SetUp; 57 | const 58 | FAKE_AWS_ACCESS_KEY = 'AKIAJQF6P3QUHRSJPZCA'; 59 | FAKE_AWS_SECRET_KEY = 'BeVo2wwiGIg25t4jKxsqmzS3ljSxrdZfl/SJ+32K'; 60 | begin 61 | inherited; 62 | FAmazonEmailServiceRequests := TAmazonEmailServiceRequests.Create(FAKE_AWS_ACCESS_KEY, FAKE_AWS_SECRET_KEY); 63 | end; 64 | 65 | procedure TAmazonEmailServiceRequestsTests.TearDown; 66 | begin 67 | inherited; 68 | FAmazonEmailServiceRequests.Free; 69 | end; 70 | 71 | initialization 72 | TDUnitX.RegisterTestFixture(TAmazonEmailServiceRequestsTests); 73 | 74 | end. 75 | -------------------------------------------------------------------------------- /tests/AmazonEmailServiceTests.dpr: -------------------------------------------------------------------------------- 1 | program AmazonEmailServiceTests; 2 | 3 | {$APPTYPE CONSOLE} 4 | 5 | uses 6 | ActiveX, 7 | PrepareRequestSignatureTests in 'PrepareRequestSignatureTests.pas', 8 | EncodeQueryParamsTests in 'EncodeQueryParamsTests.pas', 9 | PopulateResponseInfoTests in 'PopulateResponseInfoTests.pas', 10 | AmazonEmailServiceRegionsTests in 'AmazonEmailServiceRegionsTests.pas', 11 | BuildQueryParametersTests in 'BuildQueryParametersTests.pas', 12 | AmazonEmailServiceRequestsTests in 'AmazonEmailServiceRequestsTests.pas', 13 | DunitXTestRunner in 'DunitXTestRunner.pas', 14 | AmazonEmailServiceConfigurationTests in 'AmazonEmailServiceConfigurationTests.pas'; 15 | 16 | {$R *.RES} 17 | 18 | begin 19 | CoInitialize(nil); 20 | try 21 | TDUnitXTestRunner.RunTests; 22 | finally 23 | CoUninitialize; 24 | end; 25 | end. 26 | -------------------------------------------------------------------------------- /tests/AmazonEmailServiceTests.dproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | {EA1C69F3-72E3-4B8D-A13E-31ED3822699A} 4 | 16.1 5 | None 6 | True 7 | Debug 8 | Win32 9 | 1 10 | Console 11 | AmazonEmailServiceTests.dpr 12 | 13 | 14 | true 15 | 16 | 17 | true 18 | Base 19 | true 20 | 21 | 22 | true 23 | Base 24 | true 25 | 26 | 27 | true 28 | Base 29 | true 30 | 31 | 32 | true 33 | Cfg_1 34 | true 35 | true 36 | 37 | 38 | true 39 | Base 40 | true 41 | 42 | 43 | ..\;$(DUNITX);$(DCC_UnitSearchPath) 44 | AmazonEmailServiceTests 45 | System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace) 46 | CONSOLE_TESTRUNNER;$(DCC_Define) 47 | . 48 | false 49 | false 50 | false 51 | false 52 | false 53 | 54 | 55 | 1033 56 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) 57 | dxThemeRS21;dxPScxSchedulerLnkRS21;cxSchedulerRibbonStyleEventEditorRS21;cxSchedulerRS21;dxDBXServerModeRS21;DBXSqliteDriver;dxSkinsdxDLPainterRS21;fmx;cxVerticalGridRS21;IndySystem;dxSpreadSheetRS21;cxTreeListdxBarPopupMenuRS21;tethering;frxe21;vclib;cxPivotGridRS21;DBXInterBaseDriver;DataSnapClient;DataSnapServer;DataSnapCommon;dxPScxPivotGridLnkRS21;frx21;frxIBX21;DataSnapProviderClient;dxGaugeControlRS21;DBXSybaseASEDriver;cxEditorsRS21;DbxCommonDriver;dxPSPrVwRibbonRS21;vclimg;cxGridRS21;cxPivotGridOLAPRS21;dbxcds;dxPsPrVwAdvRS21;DatasnapConnectorsFreePascal;MetropolisUILiveTile;dxTileControlRS21;vcldb;vcldsnap;cxTreeListRS21;dxSpellCheckerRS21;dxdborRS21;cxSpreadSheetRS21;DBXDb2Driver;dxBarExtItemsRS21;dxWizardControlRS21;frxDBX21;DBXOracleDriver;CustomIPTransport;vclribbon;dxtrmdRS21;dsnap;IndyIPServer;fmxase;vcl;IndyCore;dxBarExtDBItemsRS21;dxGDIPlusRS21;DBXMSSQLDriver;IndyIPCommon;CloudService;dxSkinsdxBarPainterRS21;frxADO21;dxPSdxSpreadSheetLnkRS21;Editors_XE7;soapserver;dxDockingRS21;dxdbtrRS21;inetdbxpress;dxLayoutControlRS21;dsnapxml;dxPScxGridLnkRS21;dxPSdxFCLnkRS21;dxSkinscxSchedulerPainterRS21;adortl;dxPSLnksRS21;dxPSdxDBOCLnkRS21;madBasic_;fsADO21;cxLibraryRS21;bindcompfmx;dxComnRS21;cxDataRS21;dxTabbedMDIRS21;RESTBackendComponents;rtl;dbrtl;DbxClientDriver;dxorgcRS21;dxPScxExtCommonRS21;bindcomp;inetdb;dxPScxSSLnkRS21;dxPSdxOCLnkRS21;dxPScxTLLnkRS21;dxPSdxLCLnkRS21;DbxDevartPostgreSQLDriver210;DBXOdbcDriver;frxDB21;dxMapControlRS21;madDisAsm_;xmlrtl;DataSnapNativeClient;svnui;ibxpress;Cadena_XE7;cxExportRS21;IndyProtocols;DBXMySQLDriver;cxPivotGridChartRS21;dxFlowChartRS21;soaprtl;vclactnband;bindengine;bindcompdbx;fsDB21;bindcompvcl;cxPageControlRS21;dxCoreRS21;dxmdsRS21;vclie;cxSchedulerTreeBrowserRS21;DbxDevartInterBaseDriver210;fs21;madExcept_;vcltouch;dxPSCoreRS21;dxSkinscxPCPainterRS21;dxServerModeRS21;emsclient;dxPScxCommonRS21;dxPSdxDBTVLnkRS21;dxSkinsdxRibbonPainterRS21;VclSmp;VCLRESTComponents;DBXInformixDriver;dxADOServerModeRS21;dxBarDBNavRS21;dxRibbonCustomizationFormRS21;DataSnapConnectors;DataSnapServerMidas;dsnapcon;DBXFirebirdDriver;cxSchedulerGridRS21;inet;dxRibbonRS21;fmxobj;dxNavBarRS21;dxSkinsdxNavBarPainterRS21;soapmidas;vclx;cxBarEditItemRS21;dxPScxVGridLnkRS21;svn;DBXSybaseASADriver;dxBarRS21;fmxdae;dxSkinsCoreRS21;RESTComponents;dbexpress;DataSnapIndy10ServerTransport;fsIBX21;IndyIPClient;$(DCC_UsePackage) 58 | CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= 59 | 60 | 61 | DBXSqliteDriver;fmx;IndySystem;tethering;vclib;DBXInterBaseDriver;DataSnapClient;DataSnapServer;DataSnapCommon;DataSnapProviderClient;DBXSybaseASEDriver;DbxCommonDriver;vclimg;dbxcds;DatasnapConnectorsFreePascal;MetropolisUILiveTile;vcldb;vcldsnap;DBXDb2Driver;DBXOracleDriver;CustomIPTransport;vclribbon;dsnap;IndyIPServer;fmxase;vcl;IndyCore;DBXMSSQLDriver;IndyIPCommon;CloudService;soapserver;inetdbxpress;dsnapxml;adortl;bindcompfmx;RESTBackendComponents;rtl;dbrtl;DbxClientDriver;bindcomp;inetdb;DBXOdbcDriver;xmlrtl;DataSnapNativeClient;ibxpress;IndyProtocols;DBXMySQLDriver;soaprtl;vclactnband;bindengine;bindcompdbx;bindcompvcl;vclie;vcltouch;emsclient;VclSmp;VCLRESTComponents;DBXInformixDriver;DataSnapConnectors;DataSnapServerMidas;dsnapcon;DBXFirebirdDriver;inet;fmxobj;soapmidas;vclx;DBXSybaseASADriver;fmxdae;RESTComponents;dbexpress;DataSnapIndy10ServerTransport;IndyIPClient;$(DCC_UsePackage) 62 | 63 | 64 | DEBUG;$(DCC_Define) 65 | true 66 | false 67 | true 68 | true 69 | true 70 | 71 | 72 | false 73 | None 74 | 1033 75 | false 76 | 77 | 78 | false 79 | RELEASE;$(DCC_Define) 80 | 0 81 | 0 82 | 83 | 84 | 85 | MainSource 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | Cfg_2 97 | Base 98 | 99 | 100 | Base 101 | 102 | 103 | Cfg_1 104 | Base 105 | 106 | 107 | 108 | Delphi.Personality.12 109 | Application 110 | 111 | 112 | 113 | AmazonEmailServiceTests.dpr 114 | 115 | 116 | Embarcadero DataSnap FireDAC 117 | Microsoft Office 2000 Sample Automation Server Wrapper Components 118 | Microsoft Office XP Sample Automation Server Wrapper Components 119 | File C:\Program Files (x86)\FastReports\FastReport 5\LibD21\dclfsbde21.bpl not found 120 | File C:\Program Files (x86)\FastReports\FastReport 5\LibD21\dclfrxbde21.bpl not found 121 | FastScript 1.9 Tee Components 122 | (untitled) 123 | 124 | 125 | 126 | 127 | 128 | AmazonEmailServiceTests.exe 129 | true 130 | 131 | 132 | 133 | 134 | true 135 | 136 | 137 | true 138 | 139 | 140 | 141 | 142 | 1 143 | .dylib 144 | 145 | 146 | 0 147 | .bpl 148 | 149 | 150 | Contents\MacOS 151 | 1 152 | .dylib 153 | 154 | 155 | 1 156 | .dylib 157 | 158 | 159 | 160 | 161 | 1 162 | .dylib 163 | 164 | 165 | 0 166 | .dll;.bpl 167 | 168 | 169 | Contents\MacOS 170 | 1 171 | .dylib 172 | 173 | 174 | 1 175 | .dylib 176 | 177 | 178 | 179 | 180 | 1 181 | 182 | 183 | 1 184 | 185 | 186 | 187 | 188 | Contents 189 | 1 190 | 191 | 192 | 193 | 194 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 195 | 1 196 | 197 | 198 | 199 | 200 | res\drawable-normal 201 | 1 202 | 203 | 204 | 205 | 206 | library\lib\x86 207 | 1 208 | 209 | 210 | 211 | 212 | 1 213 | 214 | 215 | 1 216 | 217 | 218 | 219 | 220 | ../ 221 | 1 222 | 223 | 224 | 225 | 226 | library\lib\armeabi-v7a 227 | 1 228 | 229 | 230 | 231 | 232 | 1 233 | 234 | 235 | 1 236 | 237 | 238 | 239 | 240 | res\drawable-xlarge 241 | 1 242 | 243 | 244 | 245 | 246 | res\drawable-xhdpi 247 | 1 248 | 249 | 250 | 251 | 252 | 1 253 | 254 | 255 | 1 256 | 257 | 258 | 259 | 260 | res\drawable-xxhdpi 261 | 1 262 | 263 | 264 | 265 | 266 | library\lib\mips 267 | 1 268 | 269 | 270 | 271 | 272 | res\drawable 273 | 1 274 | 275 | 276 | 277 | 278 | Contents\MacOS 279 | 1 280 | 281 | 282 | 1 283 | 284 | 285 | 0 286 | 287 | 288 | 289 | 290 | Contents\MacOS 291 | 1 292 | .framework 293 | 294 | 295 | 0 296 | 297 | 298 | 299 | 300 | res\drawable-small 301 | 1 302 | 303 | 304 | 305 | 306 | ../ 307 | 1 308 | 309 | 310 | 311 | 312 | Contents\MacOS 313 | 1 314 | 315 | 316 | 1 317 | 318 | 319 | Contents\MacOS 320 | 0 321 | 322 | 323 | 324 | 325 | classes 326 | 1 327 | 328 | 329 | 330 | 331 | 1 332 | 333 | 334 | 1 335 | 336 | 337 | 338 | 339 | 1 340 | 341 | 342 | 1 343 | 344 | 345 | 346 | 347 | res\drawable 348 | 1 349 | 350 | 351 | 352 | 353 | Contents\Resources 354 | 1 355 | 356 | 357 | 358 | 359 | 1 360 | 361 | 362 | 363 | 364 | 1 365 | 366 | 367 | 1 368 | 369 | 370 | 371 | 372 | 1 373 | 374 | 375 | library\lib\armeabi-v7a 376 | 1 377 | 378 | 379 | 0 380 | 381 | 382 | Contents\MacOS 383 | 1 384 | 385 | 386 | 1 387 | 388 | 389 | 390 | 391 | library\lib\armeabi 392 | 1 393 | 394 | 395 | 396 | 397 | res\drawable-large 398 | 1 399 | 400 | 401 | 402 | 403 | 0 404 | 405 | 406 | 0 407 | 408 | 409 | 0 410 | 411 | 412 | Contents\MacOS 413 | 0 414 | 415 | 416 | 0 417 | 418 | 419 | 420 | 421 | 1 422 | 423 | 424 | 1 425 | 426 | 427 | 428 | 429 | res\drawable-ldpi 430 | 1 431 | 432 | 433 | 434 | 435 | res\values 436 | 1 437 | 438 | 439 | 440 | 441 | 1 442 | 443 | 444 | 1 445 | 446 | 447 | 448 | 449 | res\drawable-mdpi 450 | 1 451 | 452 | 453 | 454 | 455 | res\drawable-hdpi 456 | 1 457 | 458 | 459 | 460 | 461 | 1 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | True 473 | False 474 | 475 | 476 | DUnit / Delphi Win32 477 | Console 478 | D:\Monde\aws-ses\Playground\Playground.dproj 479 | 480 | 481 | 482 | 12 483 | 484 | 485 | 486 | 487 | 488 | -------------------------------------------------------------------------------- /tests/BuildQueryParametersTests.pas: -------------------------------------------------------------------------------- 1 | unit BuildQueryParametersTests; 2 | 3 | interface 4 | 5 | uses 6 | Classes, 7 | DUnitX.TestFramework, 8 | AmazonEmailMessage, 9 | BuildQueryParameters; 10 | 11 | type 12 | [TestFixture] 13 | TBuildQueryParametersTests = class 14 | strict private 15 | FEmailMessage: TEmailMessage; 16 | private 17 | procedure AddRecipient(const Address: string); 18 | public 19 | [Setup] 20 | procedure SetUp; 21 | published 22 | procedure GetQueryParams_WithHTMLBody_EncodedParamsReturned; 23 | procedure GetQueryParams_WithTextBody_EncodedParamsReturned; 24 | procedure GetQueryParams_MutipleRecipients_RecipientsAdded; 25 | procedure GetQueryParams_WithReplyToAddresses_AddressesAdded; 26 | procedure GetQueryParams_FromNameSpecified_FromNameEncoded; 27 | end; 28 | 29 | implementation 30 | 31 | uses 32 | AmazonEmailService; 33 | 34 | procedure TBuildQueryParametersTests.AddRecipient(const Address: string); 35 | begin 36 | SetLength(FEmailMessage.Recipients, Length(FEmailMessage.Recipients) + 1); 37 | FEmailMessage.Recipients[High(FEmailMessage.Recipients)] := Address; 38 | end; 39 | 40 | procedure TBuildQueryParametersTests.GetQueryParams_MutipleRecipients_RecipientsAdded; 41 | const 42 | ExpectedRecipients = '&Destination.ToAddresses.member.1=emailFrom%40mail.com' + 43 | '&Destination.ToAddresses.member.2=emailFrom2%40mail.com'; 44 | var 45 | EncodedParams: TStringStream; 46 | begin 47 | AddRecipient('emailFrom2@mail.com'); 48 | 49 | EncodedParams := TBuildQueryParameters.GetQueryParams(FEmailMessage); 50 | try 51 | Assert.Contains(EncodedParams.DataString, ExpectedRecipients); 52 | finally 53 | EncodedParams.Free; 54 | end; 55 | end; 56 | 57 | procedure TBuildQueryParametersTests.GetQueryParams_FromNameSpecified_FromNameEncoded; 58 | const 59 | ExpectedSource = '&Source=%3D%3Futf-8%3FB%3FW0FDTUVdIEpvaG4gRG9l%3F%3D%20%3Cemail%40mail.com%3E'; 60 | var 61 | EncodedParams: TStringStream; 62 | begin 63 | FEmailMessage.FromName := '[ACME] John Doe'; 64 | EncodedParams := TBuildQueryParameters.GetQueryParams(FEmailMessage); 65 | try 66 | Assert.Contains(EncodedParams.DataString, ExpectedSource); 67 | finally 68 | EncodedParams.Free; 69 | end; 70 | end; 71 | 72 | procedure TBuildQueryParametersTests.GetQueryParams_WithHTMLBody_EncodedParamsReturned; 73 | const 74 | EXPECTED_RETURN = 'Action=SendEmail' + 75 | '&Source=%3D%3Futf-8%3FB%3F%3F%3D%20%3Cemail%40mail.com%3E' + 76 | '&Destination.ToAddresses.member.1=emailFrom%40mail.com' + 77 | '&Message.Subject.Charset=UTF-8' + 78 | '&Message.Subject.Data=This%20is%20the%20subject%20line%20with%20HTML.' + 79 | '&Message.Body.Html.Charset=UTF-8' + 80 | '&Message.Body.Html.Data=%3C%21DOCTYPE%20html%3E%3Chtml%3E%3Cbody%3E%3Cp%3EThis%20is%20' + 81 | 'an%20email%20link%3A%3Ca%20href%3D%22mailto%3Asomeone%40example.com%3FSubject%3DHello' + 82 | '%2520again%22%20target%3D%22_top%22%3ESend%20Mail%3C%2Fa%3E%3C%2Fp%3E%3Cp%3E%3Cb%3ENote' + 83 | '%3A%3C%2Fb%3E%20Spaces%20between%20words%20should%20be%20replaced%20by%20%2520%20to%20' + 84 | 'ensure%20that%20the%20browser%20will%20display%20the%20text%20properly.%3C%2Fp%3E%3C%2' + 85 | 'Fbody%3E%3C%2Fhtml%3E'; 86 | var 87 | EncodedParams: TStringStream; 88 | begin 89 | FEmailMessage.BodyType := btHTML; 90 | FEmailMessage.Subject := 'This is the subject line with HTML.'; 91 | FEmailMessage.Body := '' + 92 | '' + 93 | '' + 94 | '

' + 95 | 'This is an email link:' + 96 | 'Send Mail' + 97 | '

' + 98 | '

' + 99 | 'Note: Spaces between words should be replaced by %20 to ensure that the browser will display the text properly.' + 100 | '

' + 101 | '' + 102 | ''; 103 | 104 | EncodedParams := TBuildQueryParameters.GetQueryParams(FEmailMessage); 105 | try 106 | Assert.AreEqual(EXPECTED_RETURN, EncodedParams.DataString); 107 | finally 108 | EncodedParams.Free; 109 | end; 110 | end; 111 | 112 | procedure TBuildQueryParametersTests.GetQueryParams_WithReplyToAddresses_AddressesAdded; 113 | const 114 | ExpectedRecipients = '&ReplyToAddresses.member.1=emailtoreply1%40mail.com' + 115 | '&ReplyToAddresses.member.2=emailtoreply2%40mail.com'; 116 | var 117 | EncodedParams: TStringStream; 118 | begin 119 | FEmailMessage.ReplyTo := TArray.Create('emailtoreply1@mail.com', 'emailtoreply2@mail.com'); 120 | 121 | EncodedParams := TBuildQueryParameters.GetQueryParams(FEmailMessage); 122 | try 123 | Assert.Contains(EncodedParams.DataString, ExpectedRecipients); 124 | finally 125 | EncodedParams.Free; 126 | end; 127 | end; 128 | 129 | procedure TBuildQueryParametersTests.GetQueryParams_WithTextBody_EncodedParamsReturned; 130 | const 131 | EXPECTED_RETURN = 'Action=SendEmail' + 132 | '&Source=%3D%3Futf-8%3FB%3F%3F%3D%20%3Cemail%40mail.com%3E' + 133 | '&Destination.ToAddresses.member.1=emailFrom%40mail.com' + 134 | '&Message.Subject.Charset=UTF-8' + 135 | '&Message.Subject.Data=This%20is%20the%20subject%20line.' + 136 | '&Message.Body.Text.Charset=UTF-8' + 137 | '&Message.Body.Text.Data=Hello.%20I%20hope%20you%20are%20having%20a%20good%20day.'; 138 | var 139 | EncodedParams: TStringStream; 140 | begin 141 | FEmailMessage.BodyType := btText; 142 | FEmailMessage.Subject := 'This is the subject line.'; 143 | FEmailMessage.Body := 'Hello. I hope you are having a good day.'; 144 | 145 | EncodedParams := TBuildQueryParameters.GetQueryParams(FEmailMessage); 146 | try 147 | Assert.AreEqual(EXPECTED_RETURN, EncodedParams.DataString); 148 | finally 149 | EncodedParams.Free; 150 | end; 151 | end; 152 | 153 | procedure TBuildQueryParametersTests.SetUp; 154 | begin 155 | inherited; 156 | FEmailMessage.FromAddress := 'email@mail.com'; 157 | FEmailMessage.Recipients := TArray.Create('emailFrom@mail.com'); 158 | end; 159 | 160 | initialization 161 | TDUnitX.RegisterTestFixture(TBuildQueryParametersTests); 162 | 163 | end. 164 | -------------------------------------------------------------------------------- /tests/DunitXTestRunner.pas: -------------------------------------------------------------------------------- 1 | unit DUnitXTestRunner; 2 | 3 | interface 4 | 5 | type 6 | TDUnitXTestRunner = class 7 | class procedure RunTests; 8 | end; 9 | 10 | implementation 11 | 12 | uses 13 | DUnitX.AutoDetect.Console, 14 | DUnitX.Loggers.Console, 15 | DUnitX.Loggers.Xml.NUnit, 16 | DUnitX.TestRunner, 17 | DUnitX.TestFramework, 18 | System.SysUtils; 19 | 20 | class procedure TDUnitXTestRunner.RunTests; 21 | var 22 | runner : ITestRunner; 23 | results : IRunResults; 24 | logger : ITestLogger; 25 | nunitLogger : ITestLogger; 26 | begin 27 | try 28 | ReportMemoryLeaksOnShutdown := True; 29 | 30 | runner := TDUnitX.CreateRunner; 31 | runner.UseRTTI := True; 32 | logger := TDUnitXConsoleLogger.Create(true); 33 | nunitLogger := TDUnitXXMLNUnitFileLogger.Create; 34 | runner.AddLogger(logger); 35 | runner.AddLogger(nunitLogger); 36 | 37 | results := runner.Execute; 38 | 39 | {$IFNDEF CI} 40 | System.Write('Done.. press key to quit.'); 41 | System.Readln; 42 | {$ENDIF} 43 | except 44 | on E: Exception do 45 | begin 46 | System.Writeln(E.ClassName, ': ', E.Message); 47 | {$IFNDEF CI} 48 | System.Readln; 49 | {$ENDIF} 50 | end; 51 | end; 52 | end; 53 | 54 | end. 55 | -------------------------------------------------------------------------------- /tests/EncodeQueryParamsTests.pas: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monde-sistemas/delphi-aws-ses/98b37371a06a73024fdde234050f024d5eee6ea2/tests/EncodeQueryParamsTests.pas -------------------------------------------------------------------------------- /tests/PopulateResponseInfoTests.pas: -------------------------------------------------------------------------------- 1 | unit PopulateResponseInfoTests; 2 | 3 | interface 4 | 5 | uses 6 | DUnitX.TestFramework, 7 | PopulateResponseInfo, 8 | Data.Cloud.CloudAPI; 9 | 10 | type 11 | [TestFixture] 12 | TPopulateResponseInfoTests = class 13 | strict private 14 | FPopulateResponseInfo: TPopulateResponseInfo; 15 | FResponseInfo: TCloudResponseInfo; 16 | public 17 | [Setup] 18 | procedure SetUp; 19 | [TearDown] 20 | procedure TearDown; 21 | published 22 | procedure PopulateResponseInfoFromExceptionPeer_WhenSignatureDoesNotMatch_Populate; 23 | procedure PopulateResponseInfoFromExceptionPeer_WithInvalidParameterValue_Populate; 24 | end; 25 | 26 | implementation 27 | 28 | uses 29 | IPPeerAPI, 30 | SysUtils; 31 | 32 | { TPopulateResponseInfoTests } 33 | 34 | procedure TPopulateResponseInfoTests.PopulateResponseInfoFromExceptionPeer_WhenSignatureDoesNotMatch_Populate; 35 | const 36 | ERROR_MESSAGE = ''+ 37 | ' ' + 38 | ' Sender' + 39 | ' SignatureDoesNotMatch' + 40 | ' The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.' + 41 | ' ' + 42 | ' 1c37cce2-095f-11e5-b310-9f55ca999f88' + 43 | ''; 44 | STATUS_MESSAGE = 'HTTP/1.1 403 Forbidden'; 45 | STATUS_CODE = 403; 46 | var 47 | ExceptionPeer: EIPHTTPProtocolExceptionPeer; 48 | MsgError: string; 49 | begin 50 | ExceptionPeer := EIPHTTPProtocolExceptionPeer.Create(nil, ERROR_MESSAGE, STATUS_MESSAGE, STATUS_CODE); 51 | try 52 | FPopulateResponseInfo.FromExceptionPeer(FResponseInfo, ExceptionPeer); 53 | Assert.AreEqual(STATUS_CODE, FResponseInfo.StatusCode); 54 | MsgError := Format('%s - %s (%s)', [STATUS_MESSAGE, 'The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.', 'SignatureDoesNotMatch']); 55 | Assert.AreEqual(MsgError, FResponseInfo.StatusMessage); 56 | finally 57 | ExceptionPeer.Free; 58 | end; 59 | end; 60 | 61 | procedure TPopulateResponseInfoTests.PopulateResponseInfoFromExceptionPeer_WithInvalidParameterValue_Populate; 62 | const 63 | ERROR_MESSAGE = '' + 64 | ' ' + 65 | ' Sender' + 66 | ' InvalidParameterValue' + 67 | ' Missing final ''@domain''' + 68 | ' ' + 69 | ' 4dbff96d-0962-11e5-ac94-dbc2e43ecbb8' + 70 | ''; 71 | STATUS_MESSAGE = 'HTTP/1.1 400 Bad Request'; 72 | STATUS_CODE = 400; 73 | var 74 | ExceptionPeer: EIPHTTPProtocolExceptionPeer; 75 | MsgError: string; 76 | begin 77 | ExceptionPeer := EIPHTTPProtocolExceptionPeer.Create(nil, ERROR_MESSAGE, STATUS_MESSAGE, STATUS_CODE); 78 | try 79 | FPopulateResponseInfo.FromExceptionPeer(FResponseInfo, ExceptionPeer); 80 | Assert.AreEqual(STATUS_CODE, FResponseInfo.StatusCode); 81 | MsgError := Format('%s - %s (%s)', [STATUS_MESSAGE, 'Missing final ''@domain''', 'InvalidParameterValue']); 82 | Assert.AreEqual(MsgError, FResponseInfo.StatusMessage); 83 | finally 84 | ExceptionPeer.Free; 85 | end; 86 | end; 87 | 88 | procedure TPopulateResponseInfoTests.SetUp; 89 | begin 90 | inherited; 91 | FPopulateResponseInfo := TPopulateResponseInfo.Create; 92 | FResponseInfo := TCloudResponseInfo.Create; 93 | end; 94 | 95 | procedure TPopulateResponseInfoTests.TearDown; 96 | begin 97 | inherited; 98 | FPopulateResponseInfo.Free; 99 | FResponseInfo.Free; 100 | end; 101 | 102 | initialization 103 | TDUnitX.RegisterTestFixture(TPopulateResponseInfoTests); 104 | 105 | end. 106 | -------------------------------------------------------------------------------- /tests/PrepareRequestSignatureTests.pas: -------------------------------------------------------------------------------- 1 | unit PrepareRequestSignatureTests; 2 | 3 | interface 4 | 5 | uses 6 | DUnitX.TestFramework, 7 | PrepareRequestSignature; 8 | 9 | type 10 | [TestFixture] 11 | TPrepareRequestSignatureTests = class 12 | strict private 13 | FPrepareRequestSignature: TPrepareRequestSignature; 14 | public 15 | [Setup] 16 | procedure SetUp; 17 | [TearDown] 18 | procedure TearDown; 19 | published 20 | procedure GetSignature_WithCorrectStringToSign_ReturnValidSignature; 21 | end; 22 | 23 | implementation 24 | 25 | uses 26 | { It needed make sure IPPeerCommon (or an alternative IP Implementarion unit) is in the uses clause } 27 | IPPeerClient; 28 | 29 | { TPrepareRequestSignatureTests } 30 | 31 | procedure TPrepareRequestSignatureTests.GetSignature_WithCorrectStringToSign_ReturnValidSignature; 32 | var 33 | StringToSign: string; 34 | ReturnedSignature: string; 35 | begin 36 | StringToSign := 'Tue, 02 Jun 2015 15:01:19 GMT'; 37 | ReturnedSignature := FPrepareRequestSignature.GetSignature(StringToSign); 38 | Assert.AreEqual('QufC+soH9Cq9LAnOmTGgAs5dA4cjTptgGZhj3KuhQKs=', ReturnedSignature); 39 | end; 40 | 41 | procedure TPrepareRequestSignatureTests.SetUp; 42 | const 43 | AWSAccessKey = 'AKIAJQF6P3QUHRSJPZCA'; 44 | AWSSecretKey = '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'; 45 | begin 46 | inherited; 47 | FPrepareRequestSignature := TPrepareRequestSignature.Create(AWSAccessKey, AWSSecretKey); 48 | end; 49 | 50 | procedure TPrepareRequestSignatureTests.TearDown; 51 | begin 52 | inherited; 53 | FPrepareRequestSignature.Free; 54 | end; 55 | 56 | initialization 57 | TDUnitX.RegisterTestFixture(TPrepareRequestSignatureTests); 58 | 59 | end. 60 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | 3 | [DunitX](https://github.com/VSoftTechnologies/DUnitX/) 4 | 5 | ## Executing the tests 6 | 7 | * Clone the [DunitX](https://github.com/VSoftTechnologies/DUnitX/) repository locally 8 | * Define a `DUNITX` environment variable, pointing to the DunitX clone directory. 9 | -------------------------------------------------------------------------------- /tests/libeay32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monde-sistemas/delphi-aws-ses/98b37371a06a73024fdde234050f024d5eee6ea2/tests/libeay32.dll -------------------------------------------------------------------------------- /tests/ssleay32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monde-sistemas/delphi-aws-ses/98b37371a06a73024fdde234050f024d5eee6ea2/tests/ssleay32.dll --------------------------------------------------------------------------------