Skip to content

Instantly share code, notes, and snippets.

@iolaru
Created February 29, 2016 12:27
Show Gist options
  • Save iolaru/05e115096f6e440e5698 to your computer and use it in GitHub Desktop.
Save iolaru/05e115096f6e440e5698 to your computer and use it in GitHub Desktop.
Code example provided by user for sending emails with PDF attachments from Salesforce.com using Sendgrid API v2
global class SendGrid {
private static final String ENCODING = 'UTF-8';
private static final String API_URL = 'https://api.sendgrid.com/api/mail.send.json';
private transient String username;
private transient String password;
private transient String apikey;
private enum AUTHTYPE {KEY, USER_PASS}
private AUTHTYPE selAuthType;
private List<string> tos = new List<String>();
public SendGrid(String username, String password) {
this.username = username;
this.password = password;
this.apikey = null;
selAuthType = AUTHTYPE.USER_PASS;
}
public SendGrid(String apiKey) {
this.apikey = apikey;
this.apikey = '12345766';
selAuthType = AUTHTYPE.KEY;
}
public String credentialsToWebFormat() {
String output = '';
if(selAuthType == AUTHTYPE.USER_PASS) {
output += 'api_user=' + this.username;
output += '&api_key=' + this.password;
}
return output;
}
public void sendTrigger(SendGrid.Email email) {
verifyAuthentication();
String body = this.credentialsToWebFormat() + email.toWebFormat();
Map<String, String> sgHeaders = new Map<String, String>{
'Content-Type' => 'application/x-www-form-urlencoded',
'Content-Length' => String.valueof(body.length())
};
//HTTPCalloutHandler.HTTPCalloutFuture(API_URL, 'POST', body, sgHeaders, apiKey);
}
public SendGridResponse send(SendGrid.Email email) {
verifyAuthentication();
String messageBoundry = generateMessageBoundry();
String body = email.toMultipartFormat(messageBoundry);
Blob bodyBlob = EncodingUtil.base64Decode(body);
Map<String, String> sgHeaders = new Map<String, String>{
'Content-Type' => 'multipart/form-data;boundary="'+messageBoundry+'"',
'Content-Length' => String.valueof(body.length())
};
//return null;
HTTPResult res = HTTPCalloutHandler.HTTPCallout(API_URL, 'POST', bodyBlob, sgHeaders, apiKey);
if(res.success) {
return new SendGridResponse(res.statusCode, res.body);
} else {
throw new SendGridException(res.status);
}
/*HttpRequest req = new HttpRequest();
Http http = new Http();
req.setEndpoint(API_URL);
req.setMethod('POST');
req.setHeader('Content-Type','');
req.setBody(body);
req.setHeader('Content-Length',String.valueof(body.length()));
try {
HttpResponse res = http.send(req);
return new SendGridResponse(res.getStatusCode(), res.getBody());
} catch (Exception e) {
throw new SendGridException(e);
}*/
}
private String generateMessageBoundry() {
String hashString = '1000' + String.valueOf(Datetime.now().formatGMT('yyyy-MM-dd HH:mm:ss.SSS'));
Blob hash = Crypto.generateDigest('MD5', Blob.valueOf(hashString));
String hexString = EncodingUtil.convertToHex(hash);
return '----------------------------'+hexString;
}
global class Email {
public Smtpapi.Header smtpapi;
public String fromm;
public String fromname;
public String replyto;
public List<String> bcc = new List<String>();
public String subject;
public String text;
public String html;
public Map<String, String> headers = new Map<String, String>();
public Map<String, String> toEmailName = new Map<String, String>();
public Map<String, Blob> files = new Map<String, Blob>();
public Email() {
this.smtpapi = new Smtpapi.Header();
}
public Email addTo(String toEmail) {
return addTo(toEmail, toEmail);
}
public Email addTo(String toEmail, String toName) {
this.smtpapi.addTo(toEmail);
this.toEmailName.put(toEmail, toName);
return this;
}
public Email setTos(List<String> tos) {
this.smtpapi.setTos(tos);
return this;
}
public Email setFrom(String email) {
this.fromm = email;
return this;
}
public Email setFromName(String name) {
this.fromname = name;
return this;
}
public Email setReplyTo(String email) {
this.replyto = email;
return this;
}
public Email addBcc(String email) {
this.bcc.add(email);
return this;
}
public Email setSubject(String subject) {
this.subject = subject;
return this;
}
public Email setText(String text) {
this.text = text;
return this;
}
public Email setHtml(String html) {
this.html = html;
return this;
}
public Email addSubstitution(String key, List<String> val) {
this.smtpapi.addSubstitution(key, val);
return this;
}
public Email addUniqueArg(String key, String val) {
this.smtpapi.addUniqueArg(key, val);
return this;
}
public Email addCategory(String category) {
this.smtpapi.addCategory(category);
return this;
}
public Email addSection(String key, String val) {
this.smtpapi.addSection(key, val);
return this;
}
public Email addFilter(String filter_name, String parameter_name, String parameter_value) {
this.smtpapi.addFilter(filter_name, parameter_name, parameter_value);
return this;
}
public Email addHeader(String key, String val) {
this.headers.put(key, val);
return this;
}
public Email addAttachmentStream(String filename, String content) {
this.files.put(filename, Blob.valueOf(content));
return this;
}
public Email addAttachmentStream(String filename, Blob content) {
this.files.put(filename, content);
return this;
}
public String toWebFormat() {
String output = '';
// updateMissingTo - There needs to be at least 1 to address,
// or else the mail won't send.
if (!this.smtpapi.to.isEmpty() && !String.isBlank(this.fromm)) {
for(String t :this.smtpapi.to) {
String encoded = EncodingUtil.urlEncode(t, ENCODING);
output += '&to[]=' + encoded;
}
}
if (!String.isBlank(this.fromm)) {
String encoded = EncodingUtil.urlEncode(this.fromm, ENCODING);
output += '&from=' + encoded;
}
if (!String.isBlank(this.fromname)) {
String encoded = EncodingUtil.urlEncode(this.fromname, ENCODING);
output += '&fromname=' + encoded;
}
if (!String.isBlank(this.replyto)) {
String encoded = EncodingUtil.urlEncode(this.replyto, ENCODING);
output += '&replyto=' + encoded;
}
if (!this.bcc.isEmpty()) {
for (String bcc_email : this.bcc) {
String encoded = EncodingUtil.urlEncode(bcc_email, ENCODING);
output += '&bcc[]=' + encoded;
}
}
if (!String.isBlank(this.subject)) {
String encoded = EncodingUtil.urlEncode(this.subject, ENCODING);
output += '&subject=' + encoded;
}
if (!String.isBlank(this.text)) {
String encoded = EncodingUtil.urlEncode(this.text, ENCODING);
output += '&text=' + encoded;
}
if (!String.isBlank(this.html)) {
String encoded = EncodingUtil.urlEncode(this.html, ENCODING);
output += '&html=' + encoded;
}
if (this.smtpapi.jsonString() != '{}') {
String encoded = EncodingUtil.urlEncode(this.smtpapi.jsonString(), ENCODING);
output += '&x-smtpapi=' + encoded;
}
if (!this.headers.isEmpty()) {
String serialized_headers = JSON.serialize(this.headers);
String encoded = EncodingUtil.urlEncode(serialized_headers, ENCODING);
output += '&headers=' + encoded;
}
/*if (!this.files.isEmpty()) {
for (String filename : this.files.keySet()){
String value = this.files.get(filename);
String encoded = EncodingUtil.urlEncode(value, ENCODING);
output += '&files['+filename+']=' + encoded;
}
}*/
return output;
}
//Allows attaching Binary files
private String toMultipartFormat(String boundry) {
String header = '';
if (!this.smtpapi.to.isEmpty() && !String.isBlank(this.fromm)) {
for(String t :this.smtpapi.to) {
header += getPartString('to[]',t, boundry);
header += getPartString('toname[]',this.toEmailName.get(t), boundry);
}
}
if (!String.isBlank(this.fromm)) {
header += getPartString('from',this.fromm, boundry);
}
if (!String.isBlank(this.fromname)) {
header += getPartString('fromname',this.fromname, boundry);
}
if (!String.isBlank(this.replyto)) {
header += getPartString('replyto',this.replyto, boundry);
}
if (!this.bcc.isEmpty()) {
for (String bcc_email : this.bcc) {
header += getPartString('bcc[]',bcc_email, boundry);
}
}
if (!String.isBlank(this.subject)) {
header += getPartString('subject',this.subject, boundry);
}
if (!String.isBlank(this.text)) {
header += getPartString('text',this.text, boundry);
}
if (!String.isBlank(this.html)) {
header += getPartString('html',this.html, boundry);
}
if (this.smtpapi.jsonString() != '{}') {
//header += getPartString('x-smtpapi',this.smtpapi.jsonString(), boundry);
}
if (!this.headers.isEmpty()) {
String serialized_headers = JSON.serialize(this.headers);
header += getPartString('headers',serialized_headers, boundry);
}
String footer = '--'+boundry+'--';
String encodedBody = '';
String headerEncoded = EncodingUtil.base64Encode(Blob.valueOf(header+'\r\n\r\n'));
while(headerEncoded.endsWith('=')) {
header += ' ';
headerEncoded = EncodingUtil.base64Encode(Blob.valueOf(header+'\r\n\r\n'));
}
if (!this.files.isEmpty()) {
encodedBody = headerEncoded + encodeWithAttachments(footer, boundry, this.files);
} else {
encodedBody = headerEncoded + EncodingUtil.base64Encode(Blob.valueOf('\r\n' + footer));
}
return encodedBody;
}
private String encodeWithAttachments(String footer, String boundry, Map<String, Blob> atts) {
String attachmentsStr = '';
String lastPrepend = '';
for(String fileName :atts.keySet()) {
Blob fileBlob = atts.get(fileName);
String fHeader = lastPrepend + '--'+boundry+'\r\n';
fHeader += 'Content-Disposition: form-data; name="files['+fileName+']"; filename="'+fileName+'"\r\nContent-Type: application/octet-stream';
String fHeaderEncoded = EncodingUtil.base64Encode(Blob.valueOf(fheader+'\r\n\r\n'));
while(fHeaderEncoded.endsWith('=')) {
fHeader += ' ';
fHeaderEncoded = EncodingUtil.base64Encode(Blob.valueOf(fHeader+'\r\n\r\n'));
}
String fbodyEncoded = EncodingUtil.base64Encode(fileBlob);
String last4Bytes = fbodyEncoded.substring(fbodyEncoded.length()-4,fbodyEncoded.length());
if(last4Bytes.endsWith('==')) {
last4Bytes = last4Bytes.substring(0,2) + '0K';
fBodyEncoded = fbodyEncoded.substring(0,fbodyEncoded.length()-4) + last4Bytes;
lastPrepend = '';
} else if(last4Bytes.endsWith('=')) {
last4Bytes = last4Bytes.substring(0,3) + 'N';
fBodyEncoded = fbodyEncoded.substring(0,fbodyEncoded.length()-4) + last4Bytes;
lastPrepend = '\n';
} else {
lastPrepend = '\r\n';
}
attachmentsStr += fHeaderEncoded + fBodyEncoded;
}
footer = lastPrepend + footer;
return attachmentsStr + EncodingUtil.base64Encode(Blob.valueOf(footer));
}
private String getPartString(String valType, String val, String boundry) {
String part = '';
part += '--'+boundry+'\r\n';
part += 'Content-Disposition: form-data; name="'+valType+'"\r\n\r\n'+val+'\r\n';
return part;
}
}
private void verifyAuthentication() {
if(this.selAuthType == AUTHTYPE.USER_PASS && (this.username == null || this.password == null)) {
throw new SendGridException('Username and Password Missing');
}
if(this.selAuthType == AUTHTYPE.KEY && this.apikey == null) {
throw new SendGridException('API Key is missing');
}
}
public class SendGridException extends Exception {}
public class SendGridResponse {
public Integer code { get; private set; }
public Boolean success { get; private set; }
public String message { get; private set; }
public SendGridResponse(Integer code, String body) {
this.code = code;
this.success = code == 200;
this.message = body;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment