HOWTO: Email Server Amazon? - php

I am trying to build an email server for my domains... What I'm doing now is receiving emails through SES and storing them in a S3 bucket, then when a user access the inbox it fetches the new emails and store them in my EC2 instance database.
Though it works I'm not completely satisfied with this solution, does anyone know any other/better ways to work this receiving-storing-accessing problem?
Thanks in advance.

You can try to install postfix (http://www.postfix.org/) and dovecot (http://www.dovecot.org/) on your EC2 instance.

I've resolved this and posted the answer on another question I'd made here.
But I'll repost it here anyway:
So what I did was storing the email received in an S3 bucket, than notifying my api that a new email has arrived (sending the file name). Finally read from S3, parsed, stored and deleted from S3, inside my api.
SES rules:
Lambda notifying function:
Note that the name of the S3 file created by the first rule is the same as the messages id, hence 'fileName': event.Records[0].ses.mail.messageId.
'use strict';
exports.handler = (event, context, callback) => {
var http = require('http');
var data = JSON.stringify({
'fileName': event.Records[0].ses.mail.messageId,
});
var options = {
host: 'my.host',
port: '80',
path: '/my/path',
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Content-Length': data.length
}
};
var req = http.request(options, function(res) {
var msg = '';
res.setEncoding('utf8');
res.on('data', function(chunk) {
msg += chunk;
});
res.on('end', function() {
console.log(JSON.parse(msg));
context.succeed();
});
});
req.write(data);
req.end();
};
Api function (PHP - Laravel):
Note that I'm using an email parser that's based on Plancake Email Parser (link here) with some changes of my own and if needed I'll edit to show the source.
public function process_incoming_email(Request $request)
{
$current_time = Carbon::now()->setTimezone('Brazil/East'); // ALL TIMEZONES: http://us.php.net/manual/en/timezones.others.php
try
{
if ($request->has('fileName')
{
$file_name = $request->input('fileName');
// GET CREDENTIALS AND AUTHENTICATE
$credentials = CredentialProvider::env();
$s3 = new S3Client([
'version' => 'latest',
'region' => 'my-region',
'credentials' => $credentials
]);
// FECTH S3 OBJECT
$object = $s3->GetObject(['Bucket' => 'my-bucket', 'Key' => $file_name]);
$body = $object['Body']->getContents();
// PARSE S3 OBJECT
$parser = new EmailParser($body);
$receivers = ['to' => $parser->getTo(), 'cc' => $parser->getCc()];
$from = $parser->getFrom();
$body_plain = $parser->getPlainBody();
$body_html = $parser->getHTMLBody();
$subject = $parser->getSubject();
$error_message;
// PROCESS EACH RECEIVER
foreach ($receivers as $type => $type_receivers)
{
foreach ($type_receivers as $receiver)
{
// PROCESS DOMAIN-MATCHING RECEIVERS
if(preg_match("/#(.*)/", $receiver['email'], $matches) && $matches[1] == self::HOST)
{
// INSERT NEW EMAIL
$inserted = DB::table('my-emails')->insert([
// ...
]);
}
}
}
// ADD ERROR LOG IF PARSER COULD NOT FIND EMAILS
if($email_count == 0)
{
DB::table('my-logs')->insert(
['sender' => $request->ip(), 'type' => 'error', 'content' => ($error_message = 'Could not parse received email or find a suitable user receiving email.') . ' File: ' . $file_name]
);
}
// DELETE OBJECT FROM S3 IF INSERTED
else if(count($emails) == $email_count)
{
$s3->deleteObject(['Bucket' => 'my-bucket', 'Key' => $file_name]);
// RETURN SUCCESSFUL JSON RESPONSE
return Response::json(['success' => true, 'receivedAt' => $current_time, 'message' => 'Email successfully received and processed.']);
}
// ADD ERROR LOG IF NOT INSERTED
else
{
DB::table('my-logs')->insert(
['sender' => $request->ip(), 'type' => 'error', 'content' => ($error_message = 'Inserted ' . count($emails) . ' out of ' . $email_count . ' parsed records.') . ' File: ' . $file_name]
);
}
}
else
{
// ERROR: NO fileName FIELD IN RESPONSE
DB::table('my-logs')->insert(
['sender' => $request->ip(), 'type' => 'error', 'content' => ($error_message = 'Incorrect request input format.') . ' Input: ' . json_encode($request->all())]
);
}
}
// ERROR TREATMENT
catch(Exception $ex)
{
DB::table('my-logs')->insert(
['sender' => $request->ip(), 'type' => 'error', 'content' => ($error_message = 'An exception occurred while processing an incoming email.') . ' Details: ' . $ex->getMessage()]
);
}
// RETURN FAILURE JSON RESPONSE
return Response::json(['success' => false, 'receivedAt' => $current_time, 'message' => $error_message]);
}

Related

I can send data using body raw but not using formdata using post method in postman

I am just trying to upload a profile picure with my update profile api. I can send data simply by body raw using json (post) method but for uploading file i am using formdata and in that i can't send any data. i get only response that please provide input details.
Here is my api controller code
public function update_profile()
{
$this->default_file();
$responseData = array();
if(!empty($_POST['u_id']))
{
$id = $_POST['u_id'];
$userData['u_id'] = $id;
$userData['username'] = $_POST['username'];
$userData['usermob'] = $_POST['usermob'];
$userData['userlocation'] = $_POST['userlocation'];
$update_profile = $this->apm->update_profile($userData);
if(!empty($update_profile))
{
$id = $_POST['u_id'];
$userDetails = array();
$userDetails['id'] = $id;
$getUserDetails = $this->apm->getUserDetails($userDetails);
$responseData['u_id'] = $getUserDetails['result']['u_id'];
$responseData['username'] = $getUserDetails['result']['username'];
$responseData['useremail'] = $getUserDetails['result']['useremail'];
$responseData['usermob'] = $getUserDetails['result']['usermob'];
$responseData['userlocation'] = $getUserDetails['result']['userlocation'];
$responseArray = array(
'apiName' => 'update profile',
'version' => '1.0.0',
'responseCode' => 200,
'responseMessage' => "Your profile updated successfully",
'responseData' => $responseData
);
}
else
{
$responseArray = array(
'apiName' => 'update profile',
'version' => '1.0.0',
'responseCode' => 204,
'responseMessage' => "error in updating profile",
'responseData' => null//$responseData
);
}
}
else
{
$responseArray = array(
'apiName' => 'update profile',
'version' => '1.0.0',
'responseCode' => 204,
'responseMessage' => "Sorry, please provide your input details.",
'responseData' => null//$responseData
);
}
echo json_encode($responseArray);
die();
}
i am submitting the code without adding the image part code to check simple submit data using formdata.
Here is my api modal code
public function update_profile($userData)
{
return $this->db->update('users', $userData, array('u_id' => $userData['u_id']));
}
public function getUserDetails($userDetails = array())
{
$arrData = array();
if($userDetails['id'])
{
$where = "u_id='". $userDetails['id']."'";
$this->db->select('*');
$this->db->from('users');
$this->db->where($where);
$result = $this->db->get()->result_array();
if(!empty($result))
{
$arrData['result'] = $result[0];
}
else
{
$arrData['result'] = '';
}
}
return $arrData;
}
This is default file code
function default_file(){
header("Access-Control-Allow-Origin: * ");
header("Access-Control-Allow-Headers: Origin,Content-Type ");
header("Content-Type:application/json ");
$rest_json = file_get_contents("php://input");
$_POST = json_decode($rest_json,true);
}
Please help me in running my code using formdata so that i can upload a image through that api.
FormData objects are not encoded as JSON (which is sensible since JSON doesn't support the File data type and the big benefit of FormData is that it does).
FormData objects will be encoded as multipart/form-data which will be automatically parsed by PHP and used to populate $_POST and $_FILES.
Unfortunately, your code ($_POST = json_decode($rest_json,true);) overwrites the data you want with the results to trying to parse, as JSON, something which isn't JSON.
NB: When using FormData, make sure you don't overwrite the Content-Type header on the request.

Amazon Transcribe Streaming service request in PHP or Laravel?

I'm a PHP developer, I would like to use transcribe real time transcriptions, but I haven't found documentation for PHP about this tool, do you have a strength?
For audio translation, I found it, but I didn't find real time.
Below audio conversion example:
//Storage::disk('temp')->get('1.mp3');
// // https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-transcribe-2017-10-26.html
// https://docs.aws.amazon.com/transcribe/latest/dg/streaming.html
public function transcribeVoice(Request $request)
{
$client = AWS::createClient('transcribeService');
$path = 'https://audio-job.s3.us-east-2.amazonaws.com/audio.mp3';
try {
$result = $client->getTranscriptionJob([
'TranscriptionJobName' => 'audio-job'
]);
if ($result['TranscriptionJob']['TranscriptionJobStatus'] == 'IN_PROGRESS') {
return redirect('/')->with('status', 'Progressing now');
} else if ($result['TranscriptionJob']['TranscriptionJobStatus'] == 'COMPLETED') {
$file = file_get_contents($result['TranscriptionJob']['Transcript']['TranscriptFileUri']);
$json = json_decode($file);
$transcript = $json->results->transcripts[0]->transcript;
$client->deleteTranscriptionJob([
'TranscriptionJobName' => 'audio-job', // REQUIRED
]);
return redirect('/transcribeVoice')->with('result', $transcript);
}
} catch (Aws\TranscribeService\Exception\TranscribeServiceException $e) {
$result = $client->startTranscriptionJob([
'LanguageCode' => 'pt-BR', // REQUIRED
'Media' => [ // REQUIRED
'MediaFileUri' => $path,
],
'MediaFormat' => 'mp3', // REQUIRED
'TranscriptionJobName' => 'audio-job', // REQUIRED
]);
return redirect('/transcribeVoice')->with('status', 'Progressing now');
}
}
}
// StartStreamTranscription – Starts a bi-directional HTTP/2 stream where audio is streamed to Amazon Transcribe and the transcription results are streamed to your application.
// https://docs.aws.amazon.com/aws-sdk-php/v3/api/class-Aws.TranscribeService.TranscribeServiceClient.html

Ajax call to php mail() script failing on success, no json data is returned

I have a problem with an ajax call to php mail() script failing on success, no json data is returned. It's basically just posting a form to a php script, validating & returning some json.
If I encounter either a validation error the json is returned correctly & my jquery executes as expected.
If NO errors are encountered, PHP processes and sends mail correctly but no data is returned to my jquery script.
Here is my code:
<?php
require "gump.class.php";
$gump = new GUMP();
$mailto = 'me#mydomain.com';
$subject = 'A new form inquiry has been submitted.';
$output = array(
'status' => 'success',
'msg' => 'Mail processed successfully',
'success' => 'success',
);
function render_email($input) {
//error_log("render_email " . print_r($input,TRUE), 0);
ob_start();
include "email-template.phtml";
return ob_get_contents();
}
$input = $gump->sanitize($_POST);
$gump->validation_rules(array(
'first_name' => 'required',
'last_name' => 'required',
'email' => 'required',
//'country' => 'required|valid_email',
//'gender' => 'required|exact_len,1',
//'company' => 'required|valid_cc|max_len,2|min_len,1',
//'bio' => 'required'
));
$gump->filter_rules(array(
'first_name' => 'trim|sanitize_string',
'last_name' => 'trim|sanitize_string',
'email' => 'trim|sanitize_string',
));
$validated = $gump->run($_POST);
if($validated === false){
error_log("GUMP: validation Error: " . print_r($gump->get_readable_errors(true),TRUE));
$output = array(
'status' => 'error',
'msg' => '<strong>Validation Error: </strong>' . $gump->get_readable_errors(true),
'error' => 'error',
);
}else{
error_log("GUMP: Successful Validation, processing mail",0);
// ghead & mail the form
$to = $mailto ;
$subject = $subject;
$body = render_email($input);
$headers = "From: Metz Tea <sales#mydomain.com>" . "\r\n";
$headers .= "Reply-To: sales#mydomain.com\r\n";
$headers .= "Return-Path: info#example.com\r\n";
$headers .= "X-Mailer: PHP\n";
$headers .= "MIME-Version: 1.0" . "\r\n";
$headers .= "Content-type:text/html;charset=UTF-8" . "\r\n";
if(!mail($to,$subject,$body,$headers)){
$output = array(
'status' => 'error',
'msg' => 'The system failed to send mail.',
'error' => 'error',
);
};
error_log("mail complete",0);
}
header("HTTP/1.1 200 OK");
header('Content-type: application/json');
$output = json_encode($output);
echo $output;
return;
and the jquery:
$('form').submit(function(event){
event.preventDefault();
})
$(document).on("forminvalid.zf.abide", function(event,frm) {
var modal = $('form').closest('.reveal').attr('id');
$(".success").hide();
$(".alert").show();
console.log("Form id "+event.target.id+" is invalid");
formToTop(modal);
}).on("formvalid.zf.abide", function(event,frm) {
console.log("Form id "+event.target.id+" is VALID");
var modal = $('form').closest('.reveal').attr('id');
var data = frm.serializeArray();
$.ajax({
type : 'POST',
url : 'process.php',
data : data,
dataType : 'json',
encode : true
}).done(function(data) {
console.log('completed successfully '+ data);
if (data.status != 'success') {
console.log('AJAX returned error = ' + data.msg);
$('.callout p').html(data.msg);
$('.alert').show();
formToTop(modal);
} else {
console.log( 'AJAX returned success = ' + data.status);
$('.callout p').html(data.msg);
$('#orderform').find("input[type=text], textarea, select").val("");
$('.alert').hide();
$('.success').show();
$('form').slideUp('500');
formToTop(modal);
}
}).fail(function(data) {
//error
});
event.preventDefault();
});
It must be the mail() function somehow, it does send mail on success, but no data is sent back to the ajax script.
what is my error here?
The problem is here:
function render_email($input) {
//error_log("render_email " . print_r($input,TRUE), 0);
ob_start();
include "email-template.phtml";
return ob_get_contents();
}
You've got side-effects both leaving the contents of your rendered template in the buffer, and leaving buffering enabled. You'll want to change this to:
function render_email($input) {
//error_log("render_email " . print_r($input,TRUE), 0);
ob_start();
include "email-template.phtml";
$ret = ob_get_contents();
ob_end_clean();
return $ret;
}

Header Issue with S3 PUT Command

I'm trying to do a server-less upload to S3 using DropzoneJS. I'm having issues with the AWS Presigned URL specifically, where it's indicating that the x-amz-acl header is unsigned.
Javascript:
var dz = new Dropzone("div#upload", {
url: "tbd",
paramName: "file", // The name that will be used to transfer the file
maxFilesize: 2, // MB
accept: this.getUploadUrl,
method: 'put',
sending: function(file, xhr) {
var _send = xhr.send;
xhr.setRequestHeader('x-amz-acl', 'public-read');
xhr.send = function() {
_send.call(xhr, file);
}
},
});
dz.on('processing', function(file) {
// change url before sending
this.options.url = file.uploadUrl;
});
function getUploadUrl(file, cb) {
var params = {
fileName: file.name,
fileType: file.type,
};
$.getJSON('signput.php', params).done(function(data) {
var decodedUri = decodeURIComponent(data['signedRequest']);
if (!data.signedRequest) {
return cb('Failed to receive an upload url');
}
console.log(decodedUri);
file.uploadUrl = decodedUri;
cb();
}).fail(function() {
return cb('Failed to receive an upload url');
});
}
PHP (called to get presigned url):
$fileName = $_GET['fileName'];
$s3Client = new Aws\S3\S3Client([
'version' => '2006-03-01',
'region' => 'us-west-2',
'credentials' => [
'key' => '__MY__KEY__',
'secret' => '__MY__SECRET__',
],]);
$cmd = $s3Client->getCommand('PutObject', [
'Bucket' => '__MY__BUCKET__',
'Key' => $fileName
]);
$request = $s3Client->createPresignedRequest($cmd, '+20 minutes');
// Get the actual presigned-url
$url = (string) $request->getUri();
$urlArray['signedRequest'] = $url;
$urlArray = json_encode($urlArray);
echo $urlArray;
I've also tried setting x-amz-acl to public-read in the Dropzone headers and S3 getCommand, but it's not working.
The error I get:
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code>
<Message>There were headers present in the request which were not signed</Message>
<HeadersNotSigned>x-amz-acl</HeadersNotSigned>
</Error>
There was one issue - I needed to move the ACL => 'public-read' from the JS code into the signing request.
The Dropzone sending function turns into this:
sending: function(file, xhr) {
var _send = xhr.send;
xhr.send = function() {
_send.call(xhr, file);
}
}
And the PHP signing requests turns into:
$cmd = $s3Client->getCommand('PutObject', [
'Bucket' => '__MY__BUCKET__',
'Key' => $fileName,
'ACL' => 'public-read'
]);
Thanks to Michael for pointing me in the right direction.
Final code for reference...
Javascript:
var dz = new Dropzone("div#upload", {
url: "tbd",
paramName: "file", // The name that will be used to transfer the file
maxFilesize: 2, // MB
accept: this.getUploadUrl,
method: 'put',
sending: function(file, xhr) {
var _send = xhr.send;
xhr.send = function() {
_send.call(xhr, file);
}
},
});
dz.on('processing', function(file) {
// change url before sending
this.options.url = file.uploadUrl;
});
function getUploadUrl(file, cb) {
var params = {
fileName: file.name,
fileType: file.type,
};
$.getJSON('signput.php', params).done(function(data) {
var decodedUri = decodeURIComponent(data['signedRequest']);
if (!data.signedRequest) {
return cb('Failed to receive an upload url');
}
file.uploadUrl = decodedUri;
cb();
}).fail(function() {
return cb('Failed to receive an upload url');
});
}
PHP:
$fileName = $_GET['fileName'];
$s3Client = new Aws\S3\S3Client([
'version' => '2006-03-01',
'region' => 'us-west-2',
'credentials' => [
'key' => '__MY_KEY__',
'secret' => '__MY_SECRET__,
],]);
$cmd = $s3Client->getCommand('PutObject', [
'Bucket' => '__MY_BUCKET__',
'Key' => $fileName,
'ACL' => 'public-read'
]);
$request = $s3Client->createPresignedRequest($cmd, '+20 minutes');
// Get the actual presigned-url
$url = (string) $request->getUri();
$urlArray['signedRequest'] = $url;
$urlArray = json_encode($urlArray);
echo $urlArray;

How to share file for anyone Google Drive API using PHP

I have been trying over and over, but could not reach any result.
the code is generating permission id and I don't know what that means.
Please do help if anyone succeeded in this before, I just want to share file publicly using the google drive api v2.0
$fileId = '18mWN0UWX_z-4A1gag85ou0Im-wvKfMZU-tibdVd8nxY';
$userPermission = new Google_Service_Drive_Permission(array(
'type' => 'anyone',
'role' => 'reader',
'emailAddress' => 'user#example.com'
));
$request = $service->permissions->create(
$fileId, $userPermission, array('fields' => 'id'));
$batch->add($request, 'user');
$domainPermission = new Google_Service_Drive_Permission(array(
'type' => 'domain',
'role' => 'reader',
'domain' => 'example.com'
));
$request = $service->permissions->create(
$fileId, $domainPermission, array('fields' => 'id'));
$batch->add($request, 'domain');
$results = $batch->execute();
foreach ($results as $result) {
if ($result instanceof Google_Service_Exception) {
// Handle error
printf($result);
} else {
printf("Permission ID: %s\n", $result->id);
}
}
} finally {
$service->getClient()->setUseBatch(false);
}
Here is my code snippet which was 2 years old.
$uplodedOriginalFile = new Google_Service_Drive_DriveFile();
$originallinkdata = file_get_contents($downloadlink['originallink']);
$uploadedfile = $service->files->insert($uplodedOriginalFile, array(
'data' => $originallinkdata,
'uploadType' => 'multipart',
));
$newPermission = new Google_Service_Drive_Permission();
//$newPermission->setValue($value);
$newPermission->setType('anyone');
$newPermission->setRole('reader');
try
{
$service->permissions->insert($uploadedfile['id'], $newPermission);
}
catch (Exception $e)
{
print "An error occurred: " . $e->getMessage();
}
$publicOriginallink = "https://googledrive.com/host/".$uploadedfile['id'];
So you just need the inserted file Id and keep the permssion for anyone as reader and append the inserted file Id after "https://googledrive.com/host/ [newly inserted file id which is returned by google drive sdk]"
code snippet worked
$fileid =$createdFile['id'];
//--insert permission to file in public
$newPermission = new Google_Permission();
$newPermission->setType('anyone');
$newPermission->setRole('reader');
try
{$service->permissions->insert($fileid, $newPermission);}
catch (Exception $e){print "An error occurred: " . $e->getMessage();}
$publicOriginallink = "https://googledrive.com/host/".$fileid;
I just made few changes with Jai's code (Google_Permission) to match with my Google APIs Client Library version.

Categories