Sendgrid v3: "Substitutions may not be used with dynamic templating" - java

I am trying to update my API Code from Sendgrid v2, to the actual Sendgrid v3, so my code used to look like this:
public void sendCreatedUserEmail(User user) {
Email from = new Email(FROM);
from.setName(EMAIL_NAME);
String subject = "Hello" + user.getName();
Email to = new Email(user.getEmail());
Content content = new Content("text/html", "Something");
Mail mail = new Mail(from, subject, to, content);
mail.personalization.get(0).addSubstitution("{name1}", user.getName());
mail.personalization.get(0).addSubstitution("{name2}", user.getName());
mail.setTemplateId(USER_TEMPLATE_ID);
SendGrid sg = new SendGrid(SENDGRID_API_KEY);
Request request = new Request();
try {
request.setMethod(Method.POST);
request.setEndpoint("mail/send");
request.setBody(mail.build());
Response response = sg.api(request);
} catch (IOException ex) {
logger.error(ex);
}
}
After some hours of research I changed for v3 to this:
(I separeted everthing for a cleaner view)
public void sendCreatedUserEmail(User user) {
Mail mail = new Mail();
Email from = new Email();
from.setName(EMAIL_NAME);
from.setEmail(FROM);
mail.setFrom(from);
String subject = "Hello, " + user.getName();
mail.setSubject(subject);
Personalization personalization = new Personalization();
Email to = new Email();
to.setEmail(user.getEmail());
to.setName(user.getName());
personalization.addTo(to);
personalization.setSubject(subject);
personalization.addSubstitution("{name2}",user.getName());
personalization.addSubstitution("{name1}",user.getName());
mail.addPersonalization(personalization);
Content content = new Content();
content.setType("text/html");
content.setValue("Something");
mail.addContent(content);
mail.setTemplateId(NEW_USER_TEMPLATE_ID);
SendGrid sg = new SendGrid(SENDGRID_API_KEY);
Request request = new Request();
try {
request.setMethod(Method.POST);
request.setEndpoint("mail/send");
request.setBody(mail.build());
Response response = sg.api(request);
System.out.println(response.getStatusCode());
System.out.println(response.getBody());
System.out.println(response.getHeaders());
} catch (IOException ex) {
logger.error(ex);
}
}
I am getting the following error:
ERROR ROOT - java.io.IOException: Request returned status Code 400Body:{"errors":[{"message":"Substitutions may not be used with dynamic templating","field":"personalizations.0.substitutions","help":"http://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/errors.html#message.personalizations.substitutions"}]}
And I really don't know how to proceed! I've been reading the sendgrid documentation but I couldn't get it.
Some details that might help
- Java8 is the language
- MAVEN for dependencies
- IntelliJ for the IDE
Sorry for the possible mistakes, it's my first post and English is not my main language. Thank you!

V3 of the Sendgrid API uses Dynamic Template Data instead of substitutions.
Try this instead of using addSubstitution:
personalization.addDynamicTemplateData("{name2}",user.getName());
personalization.addDynamicTemplateData("{name1}",user.getName());
Sources:
https://github.com/sendgrid/sendgrid-java/blob/9bc569cbdb908dba609ed0d9d2691dff319ce155/src/main/java/com/sendgrid/helpers/mail/objects/Personalization.java
https://sendgrid.com/docs/ui/sending-email/how-to-send-an-email-with-dynamic-transactional-templates/

Try:
personalization.addDynamicTemplateData("name2",user.getName());
personalization.addDynamicTemplateData("name1",user.getName());

Related

SendGrid send type text/plain and text/html

I have a service that sends email and everything works fine there, but I need to send 2 types of emails text/html and text/plain. I add this to my code:
Content plainContent = new Content("text/plain", "This is plain content");
mail.addContent(htmlContent);
mail.addContent(plainContent);
and the letters stopped coming
#Override
public void send(Message message) throws IOException {
Personalization personalization = new Personalization();
message.getSendTo().forEach(address -> personalization.addTo(new Email(address.getEmail(), address.getName())));
Content htmlContent = new Content(message.getContentType(), message.getContent());
Mail mail = new Mail();
mail.setFrom(new Email(message.getSendFrom().getEmail(), message.getSendFrom().getName()));
mail.addPersonalization(personalization);
mail.setSubject(message.getSubject());
mail.addContent(htmlContent);
Request request = new Request();
request.setMethod(Method.POST);
request.setEndpoint("mail/send");
request.setBody(mail.build());
Response response = sendgrid.api(request);
if (response.getStatusCode() != 202) {
throw new RuntimeException(response.getBody());
}
}
I found the answer to my question:
{"errors":[{"message":"If present, text/plain must be first, followed by text/html, followed by any other content.","field":"content" ,"help":null}]}
It is necessary to simply swap setContent.
First mail.addContent(plainContent);
and then mail.addContent(htmlContent);

Problem with java sendgrid v3 api, request not found?

Basic example from Sendgrids documentation:
// using SendGrid's Java Library
// https://github.com/sendgrid/sendgrid-java
import com.sendgrid.*;
import java.io.IOException;
public class Example {
public static void main(String[] args) throws IOException {
Email from = new Email("test#example.com");
String subject = "Sending with SendGrid is Fun";
Email to = new Email("test#example.com");
Content content = new Content("text/plain", "and easy to do anywhere, even with Java");
Mail mail = new Mail(from, subject, to, content);
SendGrid sg = new SendGrid(System.getenv("SENDGRID_API_KEY"));
Request request = new Request();
try {
request.setMethod(Method.POST);
request.setEndpoint("mail/send");
request.setBody(mail.build());
Response response = sg.api(request);
System.out.println(response.getStatusCode());
System.out.println(response.getBody());
System.out.println(response.getHeaders());
} catch (IOException ex) {
throw ex;
}
}
}
I am using 4.9.3 version (java 8 still), should that Request be com.sendgrid.Request? Cause seems that Request is not in that package?

How to specify filename when using MailGun API

I am currently in the process of integrating MailGun into one of my applications. For my use cases I need to be able to send out attachments. So far, I have been able to send out attachments just fine but my problem is that I am unable to specify the attachment's name. Their documentation found here specifies that the attachment part should be added when including attachment, but does not state how to specify the file's name.
For reference I am using Spring's RestTemplate as my client and I am reading the file as a base64 encoded string which is then trasnformed into a ByteArrayResource. For reference my code is this:
#Override
public EmailDocument sendEmail(EmailDocument email) {
var properties = propProvider.findFor(email.getCompany());
var parts = new LinkedMultiValueMap<String, Object>();
parts.add("from", email.getFrom());
parts.add("to", toCommaString(email.getTo()));
if (!email.getCc().isEmpty()) {
parts.add("cc", toCommaString(email.getCc()));
}
if (!email.getBcc().isEmpty()) {
parts.add("bcc", toCommaString(email.getBcc()));
}
parts.add("subject", email.getSubject());
if (email.getIsHtml()) {
parts.add("html", email.getBody());
} else {
parts.add("text", email.getBody());
}
email.getAttachments().forEach(attachment -> {
var decoded = Base64.getDecoder().decode(attachment.getBytes(StandardCharsets.UTF_8));
parts.add("attachment", new ByteArrayResource(decoded));
});
var header = headerProvider.createHeader("api", properties.getApiKey(), inferMediaType(email));
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(parts, header);
try {
var response = restTemplate.exchange(createDomain(properties.getDomain()), HttpMethod.POST, request, MailGunApiResponse.class);
log.info("Got the following MailGun response {}", response);
if (!response.getStatusCode().is2xxSuccessful()) {
email.setFailureReason(Optional.ofNullable(response.getBody()).map(MailGunApiResponse::getMessage).orElse(null));
email.setRetries(email.getRetries() + 1);
email.setFailed(isFailed(email));
} else {
email.setSent(true);
}
} catch (Exception e) {
log.error("An error has occurred while attempting to send out email {}", email, e);
email.setFailureReason(e.getMessage());
email.setRetries(email.getRetries() + 1);
email.setFailed(isFailed(email));
}
return email;
}
Does anyone know how to specify a filename for the attachment?

Proxy setting not working in Jersey ClientConfig

I'm trying to setup a proxy in my java code using Jersey client but the proxy is not getting set. I went through the Jersey documentation and have implemented the code in the described way. I'm new to Jersey so not sure where I'm going wrong.
Below is the code.
#Override
#CircuitBreaker(name = "documentServiceCreateDocument", ignore = { NullPointerException.class, ArrayIndexOutOfBoundsException.class })
public String createDocument(String name, DocumentType docType, List<SourceData> sourceDatas) {
ClientConfig clientConfig = new ClientConfig().register(MultiPartFeature.class)
.register(ClientTransactionIdFilter.class)
.property(ClientProperties.READ_TIMEOUT, "30000")
.property(ClientProperties.CONNECT_TIMEOUT, "30000")
.property(ClientProperties.PROXY_URI, properties.getProxyUrl);
Client client = ClientBuilder.newClient(clientConfig);
Builder builder = resourceTarget.request().header("Authorization", ***);
List<Cookie> iamCookies = ***
Response response = null;
try {
response = builder.post(body);
} catch (Exception e){
if(response != null) {
logger.info("Response code : " + response.getStatus());
logger.info("Response : " + response.toString());
}
e.printStackTrace();
throw new RuntimeException(e);
}
String docLocation = response.getLocation().toString();
logger.debug("Created Document Service document with location=" + docLocation);
return docLocation;
}
After a long duration, I finally figured out the fix. We need to use the ApacheConnectorProvider in order for the proxy to work.
Add the ApacheConnectorProvider to the ClientConfig as shown below:
ClientConfig clientConfig = new ClientConfig().register(MultiPartFeature.class)
.register(ClientTransactionIdFilter.class)
.property(ClientProperties.READ_TIMEOUT, "30000")
.property(ClientProperties.CONNECT_TIMEOUT, "30000")
.connectorProvider(new ApacheConnectorProvider())
.property(ClientProperties.PROXY_URI, properties.getProxyUrl);
Don't forget to add the jersey-apache-connector dependency to your pom file(if you are using maven). Refer to the below link for jersey-apache-connector dependency details:
https://mvnrepository.com/artifact/org.glassfish.jersey.connectors/jersey-apache-connector/2.6

Outlook OAuth2 access mails

I am following this post: Outlook RestGettingStarted. From my Java app I am trying to get AccessToken and RefreshToken. When I made Authorization code request, it ended into following error:
Sorry, but we’re having trouble signing you in. We received a bad
request.
Additional technical information: Correlation ID:
ed838d66-5f2e-4cfb-9223-a29082ecb26f Timestamp: 2015-08-20 10:20:09Z
AADSTS90011: The 'resource' request parameter is not supported.
NOTE: URL formation is correct as per documentation.
So, I removed "resource" query parameter from my code. And redirected authorize url in browser. On user consent I got authorization code. Using this code I got AccessToken. But when I try to connect with Outlook IMAP server it failed. Java ref Link for details: Java OAuth2
But it gives me error:
[AUTHENTICATIONFAILED] OAuth authentication failed.
NOTE: I added correct scope, and user email.
Then using obtained Access Token I made Mail Rest API call to get Messages from User Inbox. It ended into following error:
HTTP response:
{"error":{"code":"MailboxNotEnabledForRESTAPI","message":"REST API is
not yet supported for this mailbox."}}
Can anyone help me for following:
What is the exact cause for: "AADSTS90011: The 'resource' request parameter is not supported" after following Outlook dev docs.
How to resolve "MailboxNotEnabledForRESTAPI" error.
Is it possible to connect using java mail APIs to Outlook IMAP server with correct AccessToken ?
I ran into this recently, but don't remember which solved it. One main issue is in the documentation in that it is varying. It will tell you to attach "resource", but that is for something else like Azure.
Here is the code I used:
First request to send:
private static final String USER_OAUTH2_AUTHORIZE_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
public String getOAuthDialog(Http.Request request) {
return USER_OAUTH2_AUTHORIZE_URL
+ "?client_id=" + config.getClientId()
+ "&redirect_uri=" + getOutlookLoginRedirect(request)
+ "&response_type=code"
+ "&scope=https%3A%2F%2Foutlook.office.com%2Fmail.send%20" +
"https%3A%2F%2Foutlook.office.com%2Fmail.readwrite%20" +
"offline_access%20openid%20email%20profile"
+ "&state=" + crypto.generateSignedToken();
}
Scope was the hardest thing to figure out. I found a lot of ones that did not work. And it wasn't clear that I needed to separate them with spaces.
Then they will send you a request to your redirect url that was supplied. It will contain a code which you need to exchange for the data you requested in the scope. The redirect url that is supplied needs to be the exact same. Also you need to register the redirect url on your application portal under the Platform->Add Platform->Redirect URI->Add Url
private static final String USER_ACCESS_TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
private Map<String, String> sendOutlookUserOAuthRequest(Http.Request request, String code) {
WSClient ws = WS.client();
HttpParameters params = new HttpParameters();
params.put("client_id", config.getClientId(), true);
params.put("client_secret", config.getClientSecret(), true);
params.put("code", code, true);
params.put("redirect_uri", getOutlookLoginRedirect(request), true);
params.put("grant_type", "authorization_code");
String postParams = OAuthUtil.parametersToString(params);
WSRequest wsRequest = ws.url(USER_ACCESS_TOKEN_URL)
.setMethod("POST")
.setContentType("application/x-www-form-urlencoded")
.setBody(postParams);
WSResponse wsResponse = wsRequest.execute().get(10, TimeUnit.SECONDS);
Map<String, String> result = new HashMap<>();
if (wsResponse.getStatus() != HttpStatus.SC_OK) {
return result;
}
JsonNode node = wsResponse.asJson();
if (node.hasNonNull("access_token")) {
result.put("access_token", node.get("access_token").asText());
}
if (node.hasNonNull("refresh_token")) {
result.put("refresh_token", node.get("refresh_token").asText());
}
if (node.hasNonNull("id_token")) {
String[] tokenSplit = node.get("id_token").asText().split("\\.");
if (tokenSplit.length >= 2) {
try {
JSONObject jsonObject = new JSONObject(new String(Base64.getDecoder().decode(tokenSplit[1])));
if (jsonObject.has("name")) {
result.put("name", jsonObject.get("name").toString());
}
if (jsonObject.has("email")) {
result.put("outlookUid", jsonObject.get("email").toString());
} else if (jsonObject.has("preferred_username")) {
result.put("outlookUid", jsonObject.get("preferred_username").toString());
}
} catch (JSONException e) {
log.error("Error extracting outlookUid from id_token: ", e);
}
}
}
return result;
}
Another request that you might need is to update the refresh token:
private String getAccessTokenFromRefreshToken(User user) {
WSClient ws = WS.client();
HttpParameters params = new HttpParameters();
params.put("client_id", config.getClientId(), true);
params.put("client_secret", config.getClientSecret(), true);
params.put("grant_type", "refresh_token");
params.put("refresh_token", user.getOutlookRefreshToken());
String postParams = OAuthUtil.parametersToString(params);
WSRequest wsRequest = ws.url(USER_ACCESS_TOKEN_URL)
.setMethod("POST")
.setContentType("application/x-www-form-urlencoded")
.setBody(postParams);
WSResponse wsResponse = wsRequest.execute().get(10, TimeUnit.SECONDS);
if (wsResponse.getStatus() != HttpStatus.SC_OK) {
log.error("Failure to refresh outlook access token for user: " + user +
". Received status: " + wsResponse.getStatus() + " : " + wsResponse.getStatusText());
return null;
}
JsonNode node = wsResponse.asJson();
if (node.hasNonNull("access_token")) {
String accessToken = node.get("access_token").asText();
return accessToken;
} else {
log.error("Outlook refresh token failure, 'access_token' not present in response body: " + wsResponse.getBody());
return null;
}
}
One issue I ran into that took far longer than I would have hoped was in getting the clientId and clientSecret. This was because the language microsoft uses wasn't the most explicit. Client Id and application id are used interchangeably. The client secret is also the password that you create on the Application Portal, not to be confused with the Private Key that you can generate.
So you actually want the application_id and the password, although they refer to them as client_id and client_secret with no direct indication as to the lines drawn.
This is all assuming you have set up an application on the Outlook Application Portal. https://apps.dev.microsoft.com/
I hope this helps, although I assume you probably already solved this.
I faced the same problem with Java mail. You need to add service principals for your application on the Azure AD.
Find complete steps explained in Medium article Complete guide: Java Mail IMAP OAuth2.0 Connect Outlook | by Ritik Sharma | Dec, 2022.

Categories